Skip to content

Commit f232237

Browse files
Add :rev(...) filter
Change: start-filter
1 parent 1a16413 commit f232237

File tree

10 files changed

+419
-4
lines changed

10 files changed

+419
-4
lines changed

Cargo.lock

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ handlebars = "4.3.5"
3131
hex = "0.4.3"
3232
hyper-reverse-proxy = "0.5.1"
3333
indoc = "1.0.7"
34+
itertools = "0.9"
3435
juniper = "0.15.10"
3536
lazy_static = "1.4.0"
3637
log = "0.4.17"

docs/src/reference/filters.md

+11
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,17 @@ These filter do not modify git trees, but instead only operate on the commit gra
9191
Produce a filtered history that does not contain any merge commits. This is done by
9292
simply dropping all parents except the first on every commit.
9393

94+
### Filter specific parts of the history **:rev(<sha_0>:filter_0,...,<sha_N>:filter_N)**
95+
Produce a history where the commits specified by `<sha_N>` are replaced by the result of applying
96+
`:filter_N` to it.
97+
98+
It will appear like all *ancestors* of `<sha_N>` are also filtered with `<filter_N>`. If an
99+
ancestor also has a matching entry in the `:rev(...)` it's filter will *replace* `<filter_N>`
100+
for all further ancestors (and so on).
101+
102+
This special value `0000000000000000000000000000000000000000` can be used as a `<sha_n>` to filter
103+
commits that don't match any of the other shas.
104+
94105
Filter order matters
95106
--------------------
96107

src/filter/grammar.pest

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ char = {
1818

1919
filter_spec = { (
2020
filter_group
21+
| filter_rev
2122
| filter_presub
2223
| filter_subdir
2324
| filter_nop
@@ -32,6 +33,15 @@ filter_presub = { CMD_START ~ ":" ~ argument }
3233
filter = { CMD_START ~ cmd ~ "=" ~ (argument ~ ("," ~ argument)*)? }
3334
filter_noarg = { CMD_START ~ cmd }
3435

36+
filter_rev = {
37+
CMD_START ~ "rev" ~ "("
38+
~ NEWLINE*
39+
~ (argument ~ filter_spec)?
40+
~ (CMD_SEP+ ~ (argument ~ filter_spec))*
41+
~ NEWLINE*
42+
~ ")"
43+
}
44+
3545
argument = { string | PATH }
3646

3747
cmd = { ALNUM+ }

src/filter/mod.rs

+50-3
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ enum Op {
9898
Fold,
9999
Paths,
100100
Squash(Option<std::collections::HashMap<git2::Oid, (String, String, String)>>),
101+
Rev(std::collections::HashMap<git2::Oid, Filter>),
101102
Linear,
102103

103104
RegexReplace(regex::Regex, String),
@@ -194,6 +195,13 @@ fn nesting2(op: &Op) -> usize {
194195
Op::Workspace(_) => usize::MAX,
195196
Op::Chain(a, b) => 1 + nesting(*a).max(nesting(*b)),
196197
Op::Subtract(a, b) => 1 + nesting(*a).max(nesting(*b)),
198+
Op::Rev(filters) => {
199+
1 + filters
200+
.values()
201+
.map(|filter| nesting(*filter))
202+
.max()
203+
.unwrap_or(0)
204+
}
197205
_ => 0,
198206
}
199207
}
@@ -223,6 +231,14 @@ fn spec2(op: &Op) -> String {
223231
Op::Exclude(b) => {
224232
format!(":exclude[{}]", spec(*b))
225233
}
234+
Op::Rev(filters) => {
235+
let mut v = filters
236+
.iter()
237+
.map(|(k, v)| format!("{}{}", k, spec(*v)))
238+
.collect::<Vec<_>>();
239+
v.sort();
240+
format!(":rev({})", v.join(","))
241+
}
226242
Op::Workspace(path) => {
227243
format!(":workspace={}", parse::quote(&path.to_string_lossy()))
228244
}
@@ -384,6 +400,32 @@ fn apply_to_commit2(
384400
rs_tracing::trace_scoped!("apply_to_commit", "spec": spec(filter), "commit": commit.id().to_string());
385401

386402
let filtered_tree = match &to_op(filter) {
403+
Op::Rev(filters) => {
404+
let nf = *filters
405+
.get(&git2::Oid::zero())
406+
.unwrap_or(&to_filter(Op::Nop));
407+
408+
for (id, startfilter) in filters {
409+
if *id == commit.id() {
410+
let mut f2 = filters.clone();
411+
f2.remove(id);
412+
f2.insert(git2::Oid::zero(), *startfilter);
413+
let op = if f2.len() == 1 {
414+
to_op(*startfilter)
415+
} else {
416+
Op::Rev(f2)
417+
};
418+
if let Some(start) = apply_to_commit2(&op, &commit, transaction)? {
419+
transaction.insert(filter, commit.id(), start, true);
420+
return Ok(Some(start));
421+
} else {
422+
return Ok(None);
423+
}
424+
}
425+
}
426+
427+
apply(transaction, nf, commit.tree()?)?
428+
}
387429
Op::Squash(Some(ids)) => {
388430
if let Some(_) = ids.get(&commit.id()) {
389431
commit.tree()?
@@ -619,7 +661,7 @@ fn apply2<'a>(
619661
Op::Squash(None) => Ok(tree),
620662
Op::Squash(Some(_)) => Err(josh_error("not applicable to tree")),
621663
Op::Linear => Ok(tree),
622-
664+
Op::Rev(_) => Err(josh_error("not applicable to tree")),
623665
Op::RegexReplace(regex, replacement) => {
624666
tree::regex_replace(tree.id(), &regex, &replacement, transaction)
625667
}
@@ -712,7 +754,11 @@ pub fn unapply<'a>(
712754
parent_tree: git2::Tree<'a>,
713755
) -> JoshResult<git2::Tree<'a>> {
714756
if let Ok(inverted) = invert(filter) {
715-
let matching = apply(transaction, chain(filter, inverted), parent_tree.clone())?;
757+
let matching = apply(
758+
transaction,
759+
chain(invert(inverted)?, inverted),
760+
parent_tree.clone(),
761+
)?;
716762
let stripped = tree::subtract(transaction, parent_tree.id(), matching.id())?;
717763
let new_tree = apply(transaction, inverted, tree)?;
718764

@@ -733,7 +779,8 @@ pub fn unapply<'a>(
733779
}
734780

735781
if let Op::Chain(a, b) = to_op(filter) {
736-
let p = apply(transaction, a, parent_tree.clone())?;
782+
let i = if let Ok(i) = invert(a) { invert(i)? } else { a };
783+
let p = apply(transaction, i, parent_tree.clone())?;
737784
return unapply(
738785
transaction,
739786
a,

src/filter/opt.rs

+2
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ fn step(filter: Filter) -> Filter {
328328
Op::Prefix(path)
329329
}
330330
}
331+
Op::Rev(filters) => Op::Rev(filters.into_iter().map(|(i, f)| (i, step(f))).collect()),
331332
Op::Compose(filters) if filters.is_empty() => Op::Empty,
332333
Op::Compose(filters) if filters.len() == 1 => to_op(filters[0]),
333334
Op::Compose(mut filters) => {
@@ -426,6 +427,7 @@ pub fn invert(filter: Filter) -> JoshResult<Filter> {
426427
Op::File(path) => Some(Op::File(path)),
427428
Op::Prefix(path) => Some(Op::Subdir(path)),
428429
Op::Glob(pattern) => Some(Op::Glob(pattern)),
430+
Op::Rev(_) => Some(Op::Nop),
429431
_ => None,
430432
};
431433

src/filter/parse.rs

+12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::*;
22
use indoc::{formatdoc, indoc};
3+
use itertools::Itertools;
34

45
fn make_op(args: &[&str]) -> JoshResult<Op> {
56
match args {
@@ -103,6 +104,17 @@ fn parse_item(pair: pest::iterators::Pair<Rule>) -> JoshResult<Op> {
103104
_ => Err(josh_error("parse_item: no match {:?}")),
104105
}
105106
}
107+
Rule::filter_rev => {
108+
let v: Vec<_> = pair.into_inner().map(|x| unquote(x.as_str())).collect();
109+
110+
let hm = v
111+
.iter()
112+
.tuples()
113+
.map(|(oid, filter)| Ok((git2::Oid::from_str(oid)?, parse(filter)?)))
114+
.collect::<JoshResult<_>>()?;
115+
116+
Ok(Op::Rev(hm))
117+
}
106118
_ => Err(josh_error("parse_item: no match")),
107119
}
108120
}

0 commit comments

Comments
 (0)