Skip to content

Commit 243c003

Browse files
Implement :hook filter
Change: hook-filter
1 parent 1664c30 commit 243c003

File tree

5 files changed

+146
-1
lines changed

5 files changed

+146
-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
fn josh_commit_signature<'a>() -> JoshResult<git2::Signature<'a>> {
@@ -117,6 +121,7 @@ pub struct Transaction {
117121
t2: std::cell::RefCell<Transaction2>,
118122
repo: git2::Repository,
119123
ref_prefix: String,
124+
filter_hook: Option<std::sync::Arc<dyn FilterHook + Send + Sync>>,
120125
}
121126

122127
impl Transaction {
@@ -190,6 +195,7 @@ impl Transaction {
190195
}),
191196
repo,
192197
ref_prefix: ref_prefix.unwrap_or("").to_string(),
198+
filter_hook: None,
193199
}
194200
}
195201

@@ -363,6 +369,18 @@ impl Transaction {
363369
None
364370
}
365371

372+
pub fn lookup_filter_hook(&self, hook: &str, from: git2::Oid) -> JoshResult<filter::Filter> {
373+
if let Some(h) = &self.filter_hook {
374+
return h.filter_for_commit(from, hook);
375+
}
376+
Err(josh_error("missing filter hook"))
377+
}
378+
379+
pub fn with_filter_hook(mut self, hook: std::sync::Arc<dyn FilterHook + Send + Sync>) -> Self {
380+
self.filter_hook = Some(hook);
381+
self
382+
}
383+
366384
pub fn insert(&self, filter: filter::Filter, from: git2::Oid, to: git2::Oid, store: bool) {
367385
let mut t2 = self.t2.borrow_mut();
368386
t2.commit_map

josh-core/src/filter/mod.rs

Lines changed: 56 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,51 @@ 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+
let extra_parents = commit
1085+
.parents()
1086+
.map(|parent| {
1087+
rs_tracing::trace_scoped!("hook parent", "id": parent.id().to_string());
1088+
1089+
let pcw = transaction.lookup_filter_hook(&hook, parent.id())?;
1090+
let f = opt::optimize(to_filter(Op::Subtract(commit_filter, pcw)));
1091+
1092+
let r = apply_to_commit2(&to_op(f), &parent, transaction);
1093+
r
1094+
})
1095+
.collect::<JoshResult<Option<Vec<_>>>>()?;
1096+
1097+
let extra_parents = some_or!(extra_parents, { return Ok(None) });
1098+
1099+
let extra_parents: Vec<_> = extra_parents
1100+
.into_iter()
1101+
.filter(|&oid| oid != git2::Oid::zero())
1102+
.collect();
1103+
1104+
let filtered_parent_ids: Vec<_> =
1105+
normal_parents.into_iter().chain(extra_parents).collect();
1106+
1107+
let tree_data = apply(
1108+
transaction,
1109+
commit_filter,
1110+
Apply::from_commit(commit)?.with_parents(filtered_parent_ids.clone()),
1111+
)?;
1112+
return Some(history::create_filtered_commit(
1113+
commit,
1114+
filtered_parent_ids,
1115+
tree_data,
1116+
transaction,
1117+
filter,
1118+
))
1119+
.transpose();
1120+
}
10661121
_ => {
10671122
let filtered_parent_ids = commit
10681123
.parent_ids()
@@ -1238,6 +1293,7 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos
12381293
Op::Chain(a, b) => {
12391294
return apply(transaction, *b, apply(transaction, *a, x.clone())?);
12401295
}
1296+
Op::Hook(_) => Err(josh_error("not applicable to tree")),
12411297
}
12421298
}
12431299

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)