Skip to content

Commit e48826e

Browse files
Add :rev(...) filter
Change: start-filter
1 parent e084d74 commit e48826e

File tree

10 files changed

+418
-5
lines changed

10 files changed

+418
-5
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ 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 part of the history **:at_commit=<sha>[:filter]**
95+
Produce a history where the commit specified by `<sha>` is replaced by the result of applying
96+
`:filter` to it.
97+
This means also all parents of this specific commit appear filtered with `:filter` and all
98+
descendent commits will be left unchanged. However all commits hashes will still be different
99+
due to the filtered parents.
100+
94101
Filter order matters
95102
--------------------
96103

src/filter/grammar.pest

Lines changed: 10 additions & 0 deletions
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

Lines changed: 46 additions & 4 deletions
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,12 +231,20 @@ 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
}
229245
Op::RegexReplace(regex, replacement) => {
230246
format!(
231-
":replace={},{}",
247+
":replace={};{}",
232248
parse::quote(&regex.to_string()),
233249
parse::quote(&replacement)
234250
)
@@ -384,6 +400,27 @@ 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+
if let Some(start) = apply_to_commit2(&Op::Rev(f2), &commit, transaction)? {
414+
transaction.insert(filter, commit.id(), start, true);
415+
return Ok(Some(start));
416+
} else {
417+
return Ok(None);
418+
}
419+
}
420+
}
421+
422+
apply(transaction, nf, commit.tree()?)?
423+
}
387424
Op::Squash(Some(ids)) => {
388425
if let Some(_) = ids.get(&commit.id()) {
389426
commit.tree()?
@@ -619,7 +656,7 @@ fn apply2<'a>(
619656
Op::Squash(None) => Ok(tree),
620657
Op::Squash(Some(_)) => Err(josh_error("not applicable to tree")),
621658
Op::Linear => Ok(tree),
622-
659+
Op::Rev(_) => Err(josh_error("not applicable to tree")),
623660
Op::RegexReplace(regex, replacement) => {
624661
tree::regex_replace(tree.id(), &regex, &replacement, transaction)
625662
}
@@ -712,7 +749,11 @@ pub fn unapply<'a>(
712749
parent_tree: git2::Tree<'a>,
713750
) -> JoshResult<git2::Tree<'a>> {
714751
if let Ok(inverted) = invert(filter) {
715-
let matching = apply(transaction, chain(filter, inverted), parent_tree.clone())?;
752+
let matching = apply(
753+
transaction,
754+
chain(invert(inverted)?, inverted),
755+
parent_tree.clone(),
756+
)?;
716757
let stripped = tree::subtract(transaction, parent_tree.id(), matching.id())?;
717758
let new_tree = apply(transaction, inverted, tree)?;
718759

@@ -733,7 +774,8 @@ pub fn unapply<'a>(
733774
}
734775

735776
if let Op::Chain(a, b) = to_op(filter) {
736-
let p = apply(transaction, a, parent_tree.clone())?;
777+
let i = if let Ok(i) = invert(a) { invert(i)? } else { a };
778+
let p = apply(transaction, i, parent_tree.clone())?;
737779
return unapply(
738780
transaction,
739781
a,

src/filter/opt.rs

Lines changed: 2 additions & 0 deletions
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(id, filter) => Op::Rev(id, step(filter)),
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

Lines changed: 12 additions & 0 deletions
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)