Skip to content

Commit 2201f22

Browse files
Implement :hook filter
Change: hook-filter
1 parent b200f7a commit 2201f22

File tree

5 files changed

+148
-1
lines changed

5 files changed

+148
-1
lines changed

josh-core/src/cache.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ use super::*;
22
use std::collections::HashMap;
33
use std::sync::{LazyLock, RwLock};
44

5+
pub trait FilterHook {
6+
fn filter_for_commit(&self, commit_oid: git2::Oid, arg: &str) -> JoshResult<filter::Filter>;
7+
}
8+
59
const CACHE_VERSION: u64 = 24;
610

711
lazy_static! {
@@ -73,6 +77,7 @@ pub struct Transaction {
7377
t2: std::cell::RefCell<Transaction2>,
7478
repo: git2::Repository,
7579
ref_prefix: String,
80+
filter_hook: Option<std::sync::Arc<dyn FilterHook + Send + Sync>>,
7681
}
7782

7883
impl Transaction {
@@ -146,6 +151,7 @@ impl Transaction {
146151
}),
147152
repo,
148153
ref_prefix: ref_prefix.unwrap_or("").to_string(),
154+
filter_hook: None,
149155
}
150156
}
151157

@@ -319,6 +325,18 @@ impl Transaction {
319325
None
320326
}
321327

328+
pub fn lookup_filter_hook(&self, hook: &str, from: git2::Oid) -> JoshResult<filter::Filter> {
329+
if let Some(h) = &self.filter_hook {
330+
return h.filter_for_commit(from, hook);
331+
}
332+
Err(josh_error("missing filter hook"))
333+
}
334+
335+
pub fn with_filter_hook(mut self, hook: std::sync::Arc<dyn FilterHook + Send + Sync>) -> Self {
336+
self.filter_hook = Some(hook);
337+
self
338+
}
339+
322340
pub fn insert(&self, filter: filter::Filter, from: git2::Oid, to: git2::Oid, store: bool) {
323341
let mut t2 = self.t2.borrow_mut();
324342
t2.commit_map

josh-core/src/filter/mod.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ pub fn message(m: &str) -> Filter {
197197
to_filter(Op::Message(m.to_string()))
198198
}
199199

200+
pub fn hook(h: &str) -> Filter {
201+
to_filter(Op::Hook(h.to_string()))
202+
}
203+
200204
pub fn squash(ids: Option<&[(git2::Oid, Filter)]>) -> Filter {
201205
if let Some(ids) = ids {
202206
to_filter(Op::Squash(Some(
@@ -276,6 +280,8 @@ enum Op {
276280

277281
RegexReplace(Vec<(regex::Regex, String)>),
278282

283+
Hook(String),
284+
279285
Index,
280286
Invert,
281287

@@ -388,6 +394,7 @@ fn nesting2(op: &Op) -> usize {
388394
Op::Compose(filters) => 1 + filters.iter().map(|f| nesting(*f)).fold(0, |a, b| a.max(b)),
389395
Op::Exclude(filter) => 1 + nesting(*filter),
390396
Op::Workspace(_) => usize::MAX / 2, // divide by 2 to make sure there is enough headroom to avoid overflows
397+
Op::Hook(_) => usize::MAX / 2, // divide by 2 to make sure there is enough headroom to avoid overflows
391398
Op::Chain(a, b) => 1 + nesting(*a).max(nesting(*b)),
392399
Op::Subtract(a, b) => 1 + nesting(*a).max(nesting(*b)),
393400
Op::Rev(filters) => {
@@ -629,6 +636,9 @@ fn spec2(op: &Op) -> String {
629636
Op::Message(m) => {
630637
format!(":{}", parse::quote(m))
631638
}
639+
Op::Hook(hook) => {
640+
format!(":hook={}", parse::quote(hook))
641+
}
632642
}
633643
}
634644

@@ -1063,6 +1073,53 @@ fn apply_to_commit2(
10631073
let filtered_tree = repo.find_tree(filtered_tree)?;
10641074
Apply::from_commit(commit)?.with_tree(filtered_tree)
10651075
}
1076+
Op::Hook(hook) => {
1077+
let commit_filter = transaction.lookup_filter_hook(&hook, commit.id())?;
1078+
let normal_parents = commit
1079+
.parent_ids()
1080+
.map(|x| transaction.get(filter, x))
1081+
.collect::<Option<Vec<_>>>();
1082+
let normal_parents = some_or!(normal_parents, { return Ok(None) });
1083+
1084+
// Compute the difference between the current commit's filter and each parent's filter.
1085+
// This determines what new content should be contributed by that parent in the filtered history.
1086+
let extra_parents = commit
1087+
.parents()
1088+
.map(|parent| {
1089+
rs_tracing::trace_scoped!("hook parent", "id": parent.id().to_string());
1090+
1091+
let pcw = transaction.lookup_filter_hook(&hook, parent.id())?;
1092+
let f = opt::optimize(to_filter(Op::Subtract(commit_filter, pcw)));
1093+
1094+
let r = apply_to_commit2(&to_op(f), &parent, transaction);
1095+
r
1096+
})
1097+
.collect::<JoshResult<Option<Vec<_>>>>()?;
1098+
1099+
let extra_parents = some_or!(extra_parents, { return Ok(None) });
1100+
1101+
let extra_parents: Vec<_> = extra_parents
1102+
.into_iter()
1103+
.filter(|&oid| oid != git2::Oid::zero())
1104+
.collect();
1105+
1106+
let filtered_parent_ids: Vec<_> =
1107+
normal_parents.into_iter().chain(extra_parents).collect();
1108+
1109+
let tree_data = apply(
1110+
transaction,
1111+
commit_filter,
1112+
Apply::from_commit(commit)?.with_parents(filtered_parent_ids.clone()),
1113+
)?;
1114+
return Some(history::create_filtered_commit(
1115+
commit,
1116+
filtered_parent_ids,
1117+
tree_data,
1118+
transaction,
1119+
filter,
1120+
))
1121+
.transpose();
1122+
}
10661123
_ => {
10671124
let filtered_parent_ids = commit
10681125
.parent_ids()
@@ -1238,6 +1295,7 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos
12381295
Op::Chain(a, b) => {
12391296
return apply(transaction, *b, apply(transaction, *a, x.clone())?);
12401297
}
1298+
Op::Hook(_) => Err(josh_error("not applicable to tree")),
12411299
}
12421300
}
12431301

josh-core/src/filter/parse.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ fn make_op(args: &[&str]) -> JoshResult<Op> {
5656
["INDEX"] => Ok(Op::Index),
5757
["INVERT"] => Ok(Op::Invert),
5858
["FOLD"] => Ok(Op::Fold),
59+
["hook", arg] => Ok(Op::Hook(arg.to_string())),
5960
_ => Err(josh_error(
6061
formatdoc!(
6162
r#"

josh-filter/src/bin/josh-filter.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,32 @@ fn make_app() -> clap::Command {
127127
.arg(clap::Arg::new("version").action(clap::ArgAction::SetTrue).long("version").short('v'))
128128
}
129129

130+
struct GitNotesFilterHook {
131+
repo: std::sync::Mutex<git2::Repository>,
132+
}
133+
134+
impl josh::cache::FilterHook for GitNotesFilterHook {
135+
fn filter_for_commit(
136+
&self,
137+
commit_oid: git2::Oid,
138+
arg: &str,
139+
) -> josh::JoshResult<josh::filter::Filter> {
140+
let notes_ref = if arg.starts_with("refs/") {
141+
arg.to_string()
142+
} else {
143+
format!("refs/notes/{}", arg)
144+
};
145+
let repo = self.repo.lock().unwrap();
146+
let note = repo
147+
.find_note(Some(notes_ref.as_str()), commit_oid)
148+
.map_err(|_| josh::josh_error("missing git note for commit"))?;
149+
let msg = note
150+
.message()
151+
.ok_or_else(|| josh::josh_error("empty git note"))?;
152+
josh::filter::parse(msg)
153+
}
154+
}
155+
130156
fn run_filter(args: Vec<String>) -> josh::JoshResult<i32> {
131157
let args = make_app().get_matches_from(args);
132158

@@ -146,7 +172,16 @@ fn run_filter(args: Vec<String>) -> josh::JoshResult<i32> {
146172

147173
let mut filterobj = josh::filter::parse(&specstr)?;
148174

149-
let transaction = josh::cache::Transaction::open_from_env(!args.get_flag("no-cache"))?;
175+
let mut transaction = josh::cache::Transaction::open_from_env(!args.get_flag("no-cache"))?;
176+
let repo_for_hook = git2::Repository::open_ext(
177+
transaction.repo().path(),
178+
git2::RepositoryOpenFlags::NO_SEARCH,
179+
&[] as &[&std::ffi::OsStr],
180+
)?;
181+
let hook = GitNotesFilterHook {
182+
repo: std::sync::Mutex::new(repo_for_hook),
183+
};
184+
transaction = transaction.with_filter_hook(std::sync::Arc::new(hook));
150185

151186
let repo = transaction.repo();
152187
let input_ref = args.get_one::<String>("input").unwrap();

tests/filter/hook_notes.t

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
$ git init -q 1> /dev/null
2+
3+
$ mkdir a
4+
$ mkdir b
5+
$ mkdir c
6+
$ echo a > a/f
7+
$ echo b > b/g
8+
$ echo c > c/h
9+
$ git add .
10+
$ git commit -m init 1> /dev/null
11+
$ git notes add -m '::a' -f
12+
13+
$ echo a > a/f2
14+
$ echo b > b/g2
15+
$ git add .
16+
$ git commit -m "add f2" 1> /dev/null
17+
$ git notes add -m '::a' -f
18+
19+
$ echo a > c/f3
20+
$ git add .
21+
$ git commit -m "add f3" 1> /dev/null
22+
$ git notes add -m ':[::a,::b]' -f
23+
24+
25+
$ josh-filter -s :hook=commits HEAD --update refs/josh/filtered
26+
[2] ::b
27+
[3] :hook="commits"
28+
29+
$ git log --graph --pretty=%s refs/josh/filtered
30+
* add f3
31+
|\
32+
| * add f2
33+
| * init
34+
* add f2
35+
* init

0 commit comments

Comments
 (0)