Skip to content

Commit 55b6193

Browse files
Add simplification rules for message filter
Change: simplify-message-opt
1 parent 6d0ac56 commit 55b6193

File tree

7 files changed

+475
-2
lines changed

7 files changed

+475
-2
lines changed

docs/src/reference/filters.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,22 @@ tree.
121121
Normally Josh will keep all commits in the filtered history whose tree differs from any of it's
122122
parents.
123123

124+
### Pin tree contents
125+
126+
`:pin` filter prevents appearance of selected subtrees for a given revision.
127+
128+
In practical terms, it means that file and folder updates are "held off", and revisions are "pinned".
129+
If a tree entry already existed in the parent revision, that version will be chosen.
130+
Otherwise, the tree entry will not appear in the filtered commit.
131+
132+
The source of the parent revision is always the first commit parent.
133+
134+
Note that this filter is only practical when used with `:hook` or `workspace.josh`,
135+
as it should apply per-revision only. Applying `:pin` for the whole history
136+
will result in the subtree being excluded from all revisions.
137+
138+
Refer to `pin_filter_workspace.t` and `pin_filter_hook.t` for reference.
139+
124140
Filter order matters
125141
--------------------
126142

josh-core/src/filter/mod.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ enum Op {
304304
Chain(Filter, Filter),
305305
Subtract(Filter, Filter),
306306
Exclude(Filter),
307+
Pin(Filter),
307308
}
308309

309310
/// Pretty print the filter on multiple lines with initial indentation level.
@@ -349,6 +350,10 @@ fn pretty2(op: &Op, indent: usize, compose: bool) -> String {
349350
Op::Compose(filters) => ff(&filters, "exclude", indent),
350351
b => format!(":exclude[{}]", pretty2(&b, indent, false)),
351352
},
353+
Op::Pin(filter) => match to_op(*filter) {
354+
Op::Compose(filters) => ff(&filters, "pin", indent),
355+
b => format!(":pin[{}]", pretty2(&b, indent, false)),
356+
},
352357
Op::Chain(a, b) => match (to_op(*a), to_op(*b)) {
353358
(Op::Subdir(p1), Op::Prefix(p2)) if p1 == p2 => {
354359
format!("::{}/", parse::quote_if(&p1.to_string_lossy()))
@@ -407,7 +412,7 @@ fn lazy_refs2(op: &Op) -> Vec<String> {
407412
acc
408413
})
409414
}
410-
Op::Exclude(filter) => lazy_refs(*filter),
415+
Op::Exclude(filter) | Op::Pin(filter) => lazy_refs(*filter),
411416
Op::Chain(a, b) => {
412417
let mut av = lazy_refs(*a);
413418
av.append(&mut lazy_refs(*b));
@@ -458,6 +463,7 @@ fn resolve_refs2(refs: &std::collections::HashMap<String, git2::Oid>, op: &Op) -
458463
Op::Compose(filters.iter().map(|f| resolve_refs(refs, *f)).collect())
459464
}
460465
Op::Exclude(filter) => Op::Exclude(resolve_refs(refs, *filter)),
466+
Op::Pin(filter) => Op::Pin(resolve_refs(refs, *filter)),
461467
Op::Chain(a, b) => Op::Chain(resolve_refs(refs, *a), resolve_refs(refs, *b)),
462468
Op::Subtract(a, b) => Op::Subtract(resolve_refs(refs, *a), resolve_refs(refs, *b)),
463469
Op::Rev(filters) => {
@@ -545,6 +551,9 @@ fn spec2(op: &Op) -> String {
545551
Op::Exclude(b) => {
546552
format!(":exclude[{}]", spec(*b))
547553
}
554+
Op::Pin(filter) => {
555+
format!(":pin[{}]", spec(*filter))
556+
}
548557
Op::Rev(filters) => {
549558
let mut v = filters
550559
.iter()
@@ -688,6 +697,9 @@ fn as_tree2(repo: &git2::Repository, op: &Op) -> JoshResult<git2::Oid> {
688697
Op::Exclude(b) => {
689698
builder.insert("exclude", as_tree(repo, *b)?, git2::FileMode::Tree.into())?;
690699
}
700+
Op::Pin(b) => {
701+
builder.insert("pin", as_tree(repo, *b)?, git2::FileMode::Tree.into())?;
702+
}
691703
Op::Subdir(path) => {
692704
builder.insert(
693705
"subdir",
@@ -1064,6 +1076,11 @@ fn from_tree2(repo: &git2::Repository, tree_oid: git2::Oid) -> JoshResult<Op> {
10641076
let filter = from_tree2(repo, exclude_tree.id())?;
10651077
Ok(Op::Exclude(to_filter(filter)))
10661078
}
1079+
"pin" => {
1080+
let pin_tree = repo.find_tree(entry.id())?;
1081+
let filter = from_tree2(repo, pin_tree.id())?;
1082+
Ok(Op::Pin(to_filter(filter)))
1083+
}
10671084
"rev" => {
10681085
let rev_tree = repo.find_tree(entry.id())?;
10691086
let mut filters = std::collections::BTreeMap::new();
@@ -1828,6 +1845,25 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos
18281845
return apply(transaction, *b, apply(transaction, *a, x.clone())?);
18291846
}
18301847
Op::Hook(_) => Err(josh_error("not applicable to tree")),
1848+
1849+
Op::Pin(pin_filter) => {
1850+
let filtered_parent = if let Some(parent) = x.parents.as_ref().and_then(|p| p.first()) {
1851+
let parent = repo.find_commit(*parent)?;
1852+
let filtered = apply(transaction, *pin_filter, Apply::from_commit(&parent)?)?;
1853+
filtered.tree.id()
1854+
} else {
1855+
tree::empty_id()
1856+
};
1857+
1858+
// Mask out all the "pinned" files from current tree
1859+
let exclude = to_filter(Op::Exclude(*pin_filter));
1860+
let with_mask = apply(transaction, exclude, x.clone())?;
1861+
1862+
// Overlay filtered parent tree on current one to override versions
1863+
let with_overlay = tree::overlay(transaction, with_mask.tree.id(), filtered_parent)?;
1864+
1865+
Ok(x.with_tree(repo.find_tree(with_overlay)?))
1866+
}
18311867
}
18321868
}
18331869

josh-core/src/filter/opt.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ pub fn simplify(filter: Filter) -> Filter {
8282
Op::Subtract(simplify(to_filter(a)), simplify(to_filter(b)))
8383
}
8484
Op::Exclude(b) => Op::Exclude(simplify(b)),
85+
Op::Pin(b) => Op::Pin(simplify(b)),
8586
_ => to_op(filter),
8687
});
8788

@@ -137,6 +138,7 @@ pub fn flatten(filter: Filter) -> Filter {
137138
Op::Subtract(flatten(to_filter(a)), flatten(to_filter(b)))
138139
}
139140
Op::Exclude(b) => Op::Exclude(flatten(b)),
141+
Op::Pin(b) => Op::Pin(flatten(b)),
140142
_ => to_op(filter),
141143
});
142144

@@ -440,11 +442,13 @@ fn step(filter: Filter) -> Filter {
440442
(a, b) => Op::Chain(step(to_filter(a)), step(to_filter(b))),
441443
},
442444
Op::Exclude(b) if b == to_filter(Op::Nop) => Op::Empty,
443-
Op::Exclude(b) if b == to_filter(Op::Empty) => Op::Nop,
445+
Op::Exclude(b) | Op::Pin(b) if b == to_filter(Op::Empty) => Op::Nop,
444446
Op::Exclude(b) => Op::Exclude(step(b)),
447+
Op::Pin(b) => Op::Pin(step(b)),
445448
Op::Subtract(a, b) if a == b => Op::Empty,
446449
Op::Subtract(af, bf) => match (to_op(af), to_op(bf)) {
447450
(Op::Empty, _) => Op::Empty,
451+
(Op::Message(..), Op::Message(..)) => Op::Empty,
448452
(_, Op::Nop) => Op::Empty,
449453
(a, Op::Empty) => a,
450454
(Op::Chain(a, b), Op::Chain(c, d)) if a == c => {
@@ -492,6 +496,7 @@ fn step(filter: Filter) -> Filter {
492496
pub fn invert(filter: Filter) -> JoshResult<Filter> {
493497
let result = match to_op(filter) {
494498
Op::Nop => Some(Op::Nop),
499+
Op::Message(..) => Some(Op::Nop),
495500
Op::Linear => Some(Op::Nop),
496501
Op::Prune => Some(Op::Prune),
497502
Op::Unsign => Some(Op::Unsign),
@@ -502,6 +507,7 @@ pub fn invert(filter: Filter) -> JoshResult<Filter> {
502507
Op::Pattern(pattern) => Some(Op::Pattern(pattern)),
503508
Op::Rev(_) => Some(Op::Nop),
504509
Op::RegexReplace(_) => Some(Op::Nop),
510+
Op::Pin(_) => Some(Op::Nop),
505511
_ => None,
506512
};
507513

josh-core/src/filter/parse.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ fn parse_item(pair: pest::iterators::Pair<Rule>) -> JoshResult<Op> {
115115
[cmd, args] => {
116116
let g = parse_group(args)?;
117117
match *cmd {
118+
"pin" => Ok(Op::Pin(to_filter(Op::Compose(g)))),
118119
"exclude" => Ok(Op::Exclude(to_filter(Op::Compose(g)))),
119120
"subtract" if g.len() == 2 => Ok(Op::Subtract(g[0], g[1])),
120121
_ => Err(josh_error(&format!("parse_item: no match {:?}", cmd))),

tests/filter/pin_compose.t

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
$ export GIT_TREE_FMT='%(objectmode) %(objecttype) %(objectname) %(path)'
2+
3+
$ export TESTTMP=${PWD}
4+
$ cd ${TESTTMP}
5+
6+
$ git init -q repo
7+
$ cd repo
8+
$ mkdir -p josh/overlay
9+
$ mkdir -p code
10+
11+
Populate repo contents for the first commit
12+
13+
$ cat << EOF > code/app.js
14+
> async fn main() {
15+
> await fetch("http://127.0.0.1");
16+
> }
17+
> EOF
18+
19+
$ cat << EOF > code/lib.js
20+
> fn log() {
21+
> console.log("logged!");
22+
> }
23+
> EOF
24+
25+
Also create a workspace with the tree overlay filter
26+
27+
We first select files in josh/overlay, whatever is in there
28+
will take priority over the next tree in the composition filter
29+
30+
$ mkdir -p workspaces/overlay
31+
$ cat << EOF > workspaces/overlay/workspace.josh
32+
> :[
33+
> :/code
34+
> :/josh/overlay
35+
> ]
36+
> EOF
37+
38+
Here's the repo layout at this point:
39+
40+
$ tree .
41+
.
42+
|-- code
43+
| |-- app.js
44+
| `-- lib.js
45+
|-- josh
46+
| `-- overlay
47+
`-- workspaces
48+
`-- overlay
49+
`-- workspace.josh
50+
51+
6 directories, 3 files
52+
53+
Commit this:
54+
55+
$ git add .
56+
$ git commit -q -m "first commit"
57+
58+
Now, filter the ws and check the result
59+
60+
$ josh-filter ':workspace=workspaces/overlay'
61+
$ git ls-tree --format="${GIT_TREE_FMT}" -r FILTERED_HEAD
62+
100644 blob 0747fcb9cd688a7876932dcc30006e6ffa9106d6 app.js
63+
100644 blob 5910ad90fda519a6cc9299d4688679d56dc8d6dd lib.js
64+
100644 blob 39dc0f50ad353a5ee880b4a87ecc06dee7b48c92 workspace.josh
65+
66+
Save the OID of app.js before making changes:
67+
68+
$ export ORIGINAL_APP_OID=$(git ls-tree --format="%(objectname)" FILTERED_HEAD app.js)
69+
$ echo "${ORIGINAL_APP_OID}"
70+
0747fcb9cd688a7876932dcc30006e6ffa9106d6
71+
72+
Make next commit: both files will change
73+
74+
$ cat << EOF > code/app.js
75+
> async fn main() {
76+
> await fetch("http://internal-secret-portal.company.com");
77+
> }
78+
> EOF
79+
80+
$ cat << EOF > code/lib.js
81+
> fn log() {
82+
> console.log("INFO: logged!");
83+
> }
84+
> EOF
85+
86+
$ git add code/app.js code/lib.js
87+
88+
Insert the old app.js OID into the overlay.
89+
Note that we aren't copying the file -- we are directly referencing the OID.
90+
This ensures it's the same entry in git ODB.
91+
92+
$ git update-index --add --cacheinfo 100644,"${ORIGINAL_APP_OID}","josh/overlay/app.js"
93+
$ git commit -q -m "second commit"
94+
95+
Verify commit tree looks right:
96+
97+
$ git ls-tree -r --format="${GIT_TREE_FMT}" HEAD
98+
100644 blob 1540d15e1bdc499e31ea05703a0daaf520774a85 code/app.js
99+
100644 blob 627cdb2ef7a3eb1a2b4537ce17fea1d93bfecdd2 code/lib.js
100+
100644 blob 0747fcb9cd688a7876932dcc30006e6ffa9106d6 josh/overlay/app.js
101+
100644 blob 39dc0f50ad353a5ee880b4a87ecc06dee7b48c92 workspaces/overlay/workspace.josh
102+
103+
Filter the workspace and check the result:
104+
105+
$ josh-filter ':workspace=workspaces/overlay'
106+
107+
We can see now that the app.js file was held at the previous version:
108+
109+
$ git ls-tree --format="${GIT_TREE_FMT}" -r FILTERED_HEAD
110+
100644 blob 0747fcb9cd688a7876932dcc30006e6ffa9106d6 app.js
111+
100644 blob 627cdb2ef7a3eb1a2b4537ce17fea1d93bfecdd2 lib.js
112+
100644 blob 39dc0f50ad353a5ee880b4a87ecc06dee7b48c92 workspace.josh
113+
114+
$ git show FILTERED_HEAD:app.js
115+
async fn main() {
116+
await fetch("http://127.0.0.1");
117+
}

0 commit comments

Comments
 (0)