Skip to content

Commit 453c737

Browse files
Implement stored filter nesting
1 parent 3188d83 commit 453c737

File tree

2 files changed

+122
-58
lines changed

2 files changed

+122
-58
lines changed

josh-core/src/filter/mod.rs

Lines changed: 89 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ static ANCESTORS: LazyLock<
2323
std::sync::Mutex<std::collections::HashMap<git2::Oid, std::collections::HashSet<git2::Oid>>>,
2424
> = LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
2525

26+
static LEGALIZED: LazyLock<
27+
std::sync::Mutex<std::collections::HashMap<(Filter, git2::Oid), Filter>>,
28+
> = LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
29+
2630
/// Match-all regex pattern used as the default for Op::Message when no regex is specified.
2731
/// The pattern `(?s)^.*$` matches any string (including newlines) from start to end.
2832
static MESSAGE_MATCH_ALL_REGEX: LazyLock<regex::Regex> =
@@ -442,7 +446,19 @@ fn lazy_refs2(op: &Op) -> Vec<String> {
442446
av.append(&mut lazy_refs(*b));
443447
av
444448
}
445-
Op::Rev(filters) => lazy_refs2(&Op::Join(filters.clone())),
449+
Op::Rev(filters) => {
450+
let mut lr = lazy_refs2(&Op::Compose(filters.values().copied().collect()));
451+
lr.extend(filters.keys().filter_map(|x| {
452+
if let LazyRef::Lazy(s) = x {
453+
Some(s.to_owned())
454+
} else {
455+
None
456+
}
457+
}));
458+
lr.sort();
459+
lr.dedup();
460+
lr
461+
}
446462
Op::HistoryConcat(r, f) => {
447463
let mut lr = Vec::new();
448464
if let LazyRef::Lazy(s) = r {
@@ -757,53 +773,51 @@ fn resolve_workspace_redirect<'a>(
757773
}
758774
}
759775

760-
fn get_workspace<'a>(repo: &'a git2::Repository, tree: &'a git2::Tree<'a>, path: &Path) -> Filter {
776+
fn get_workspace<'a>(
777+
transaction: &cache::Transaction,
778+
tree: &'a git2::Tree<'a>,
779+
path: &Path,
780+
) -> Filter {
761781
let wsj_file = file("workspace.josh");
762782
let base = to_filter(Op::Subdir(path.to_owned()));
763783
let wsj_file = chain(base, wsj_file);
764-
compose(
784+
let ws = compose(
765785
wsj_file,
766-
compose(get_filter(repo, tree, &path.join("workspace.josh")), base),
767-
)
786+
compose(
787+
get_filter(transaction, tree, &path.join("workspace.josh")),
788+
base,
789+
),
790+
);
791+
ws
768792
}
769793

770-
// Handle stored.josh files that contain ":stored=..." as their only filter as
771-
// a "redirect" to that other stored. We chain an exclude of the redirecting stored
772-
// in front to prevent infinite recursion.
773-
fn resolve_stored_redirect<'a>(
774-
repo: &'a git2::Repository,
794+
fn get_stored<'a>(
795+
transaction: &cache::Transaction,
775796
tree: &'a git2::Tree<'a>,
776797
path: &Path,
777-
) -> Option<(Filter, std::path::PathBuf)> {
778-
let stored_path = path.with_extension("josh");
779-
let f = parse::parse(&tree::get_blob(repo, tree, &stored_path))
780-
.unwrap_or_else(|_| to_filter(Op::Empty));
781-
782-
if let Op::Stored(p) = to_op(f) {
783-
Some((chain(to_filter(Op::Exclude(file(stored_path))), f), p))
784-
} else {
785-
None
786-
}
787-
}
788-
789-
fn get_stored<'a>(repo: &'a git2::Repository, tree: &'a git2::Tree<'a>, path: &Path) -> Filter {
798+
) -> Filter {
790799
let stored_path = path.with_extension("josh");
791800
let sj_file = file(stored_path.clone());
792-
compose(sj_file, get_filter(repo, tree, &stored_path))
801+
compose(sj_file, get_filter(transaction, tree, &stored_path))
793802
}
794803

795-
fn get_filter<'a>(repo: &'a git2::Repository, tree: &'a git2::Tree<'a>, path: &Path) -> Filter {
804+
fn get_filter<'a>(
805+
transaction: &cache::Transaction,
806+
tree: &'a git2::Tree<'a>,
807+
path: &Path,
808+
) -> Filter {
796809
let ws_path = normalize_path(path);
797810
let ws_id = ok_or!(tree.get_path(&ws_path), {
798811
return to_filter(Op::Empty);
799812
})
800813
.id();
801-
let ws_blob = tree::get_blob(repo, tree, &ws_path);
814+
let ws_blob = tree::get_blob(transaction.repo(), tree, &ws_path);
802815

803816
if let Some(f) = WORKSPACES.lock().unwrap().get(&ws_id) {
804817
*f
805818
} else {
806819
let f = parse::parse(&ws_blob).unwrap_or_else(|_| to_filter(Op::Empty));
820+
let f = legalize_stored(transaction, f, tree).unwrap_or_else(|_| empty());
807821

808822
let f = if invert(f).is_ok() {
809823
f
@@ -1060,14 +1074,14 @@ fn apply_to_commit2(
10601074
}
10611075
}
10621076

1063-
let commit_filter = get_workspace(repo, &commit.tree()?, &ws_path);
1077+
let commit_filter = get_workspace(transaction, &commit.tree()?, &ws_path);
10641078

10651079
let parent_filters = commit
10661080
.parents()
10671081
.map(|parent| {
10681082
rs_tracing::trace_scoped!("parent", "id": parent.id().to_string());
10691083
let pcw = get_workspace(
1070-
repo,
1084+
transaction,
10711085
&parent.tree().unwrap_or_else(|_| tree::empty(repo)),
10721086
&ws_path,
10731087
);
@@ -1078,23 +1092,14 @@ fn apply_to_commit2(
10781092
return per_rev_filter(transaction, commit, filter, commit_filter, parent_filters);
10791093
}
10801094
Op::Stored(s_path) => {
1081-
if let Some((redirect, _)) = resolve_stored_redirect(repo, &commit.tree()?, s_path) {
1082-
if let Some(r) = apply_to_commit2(&to_op(redirect), commit, transaction)? {
1083-
transaction.insert(filter, commit.id(), r, true);
1084-
return Ok(Some(r));
1085-
} else {
1086-
return Ok(None);
1087-
}
1088-
}
1089-
1090-
let commit_filter = get_stored(repo, &commit.tree()?, &s_path);
1095+
let commit_filter = get_stored(transaction, &commit.tree()?, &s_path);
10911096

10921097
let parent_filters = commit
10931098
.parents()
10941099
.map(|parent| {
10951100
rs_tracing::trace_scoped!("parent", "id": parent.id().to_string());
10961101
let pcs = get_stored(
1097-
repo,
1102+
transaction,
10981103
&parent.tree().unwrap_or_else(|_| tree::empty(repo)),
10991104
&s_path,
11001105
);
@@ -1302,8 +1307,8 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos
13021307
.with_tree(tree::invert_paths(transaction, "", x.tree().clone())?))
13031308
}
13041309

1305-
Op::Workspace(path) => apply(transaction, get_workspace(repo, &x.tree(), &path), x),
1306-
Op::Stored(path) => apply(transaction, get_stored(repo, &x.tree(), &path), x),
1310+
Op::Workspace(path) => apply(transaction, get_workspace(transaction, &x.tree(), &path), x),
1311+
Op::Stored(path) => apply(transaction, get_stored(transaction, &x.tree(), &path), x),
13071312

13081313
Op::Compose(filters) => {
13091314
let filtered: Vec<_> = filters
@@ -1394,12 +1399,9 @@ fn unapply_workspace<'a>(
13941399
match op {
13951400
Op::Workspace(path) => {
13961401
let tree = pre_process_tree(transaction.repo(), tree)?;
1397-
let workspace = get_filter(transaction.repo(), &tree, Path::new("workspace.josh"));
1398-
let original_workspace = get_filter(
1399-
transaction.repo(),
1400-
&parent_tree,
1401-
&path.join("workspace.josh"),
1402-
);
1402+
let workspace = get_filter(transaction, &tree, Path::new("workspace.josh"));
1403+
let original_workspace =
1404+
get_filter(transaction, &parent_tree, &path.join("workspace.josh"));
14031405

14041406
let root = to_filter(Op::Subdir(path.to_owned()));
14051407
let wsj_file = to_filter(Op::File(
@@ -1428,8 +1430,8 @@ fn unapply_workspace<'a>(
14281430
}
14291431
Op::Stored(path) => {
14301432
let stored_path = path.with_extension("josh");
1431-
let stored = get_filter(transaction.repo(), &tree, &stored_path);
1432-
let original_stored = get_filter(transaction.repo(), &parent_tree, &stored_path);
1433+
let stored = get_filter(transaction, &tree, &stored_path);
1434+
let original_stored = get_filter(transaction, &parent_tree, &stored_path);
14331435

14341436
let sj_file = file(stored_path.clone());
14351437
let filter = compose(sj_file, stored);
@@ -1628,6 +1630,44 @@ where
16281630
}
16291631
}
16301632

1633+
fn legalize_stored(t: &cache::Transaction, f: Filter, tree: &git2::Tree) -> JoshResult<Filter> {
1634+
if let Some(f) = LEGALIZED.lock().unwrap().get(&(f, tree.id())) {
1635+
return Ok(*f);
1636+
}
1637+
1638+
// Put an entry into the hashtable to prevent infinite recursion.
1639+
// If we get called with the same arguments again before we return,
1640+
// Above check breaks the recursion.
1641+
LEGALIZED.lock().unwrap().insert((f, tree.id()), empty());
1642+
1643+
let r = match to_op(f) {
1644+
Op::Compose(f) => {
1645+
let f = f
1646+
.into_iter()
1647+
.map(|f| legalize_stored(t, f, tree))
1648+
.collect::<JoshResult<Vec<_>>>()?;
1649+
to_filter(Op::Compose(f))
1650+
}
1651+
Op::Chain(a, b) => {
1652+
let first = legalize_stored(t, a, tree)?;
1653+
let second =
1654+
legalize_stored(t, b, &apply(t, first, Apply::from_tree(tree.clone()))?.tree)?;
1655+
to_filter(Op::Chain(first, second))
1656+
}
1657+
Op::Subtract(a, b) => to_filter(Op::Subtract(
1658+
legalize_stored(t, a, tree)?,
1659+
legalize_stored(t, b, tree)?,
1660+
)),
1661+
Op::Exclude(f) => to_filter(Op::Exclude(legalize_stored(t, f, tree)?)),
1662+
Op::Stored(path) => get_stored(t, tree, &path),
1663+
_ => f,
1664+
};
1665+
1666+
LEGALIZED.lock().unwrap().insert((f, tree.id()), r);
1667+
1668+
Ok(r)
1669+
}
1670+
16311671
fn per_rev_filter(
16321672
transaction: &cache::Transaction,
16331673
commit: &git2::Commit,

tests/filter/stored_redirect.t

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,13 @@
6363
::sub2/subsub/
6464
]
6565
[3] :+st/config
66-
[6] :exclude[::st_new/config.josh]
67-
[8] sequence_number
66+
[5] :[
67+
::st/config.josh
68+
a = :/sub1
69+
::sub2/subsub/
70+
b = :/sub3
71+
]
72+
[7] sequence_number
6873

6974
$ git log --graph --pretty=%s refs/heads/filtered
7075
* edit st
@@ -74,9 +79,9 @@
7479
* add file2
7580
* add file1
7681
$ git log --graph --pretty=%s refs/heads/filtered_new
77-
* edit st
78-
|\
79-
| * add file4
82+
* add st_new
83+
* edit st
84+
* add file4
8085
* add st
8186
* add file2
8287
* add file1
@@ -150,6 +155,13 @@
150155
+::sub2/subsub/
151156
+a = :/sub1
152157
+b = :/sub3
158+
diff --git a/st_new/config.josh b/st_new/config.josh
159+
new file mode 100644
160+
index 0000000..15782f4
161+
--- /dev/null
162+
+++ b/st_new/config.josh
163+
@@ -0,0 +1 @@
164+
+:+st/config
153165
diff --git a/sub2/subsub/file2 b/sub2/subsub/file2
154166
new file mode 100644
155167
index 0000000..a024003
@@ -167,14 +179,26 @@
167179

168180
$ josh-filter -s :+st/config master --update refs/heads/filtered
169181
[1] :prefix=b
182+
[2] :+st_new/config
170183
[2] :/sub3
171184
[2] :[
172185
a = :/sub1
173186
::sub2/subsub/
174187
]
175-
[3] :+st_new/config
188+
[2] :subtract[
189+
::st_new/config.josh
190+
:[
191+
a = :/sub1
192+
::sub2/subsub/
193+
b = :/sub3
194+
]
195+
]
176196
[4] :+st/config
177-
[5] :exclude[::st/config.josh]
178-
[9] :exclude[::st_new/config.josh]
179-
[13] sequence_number
197+
[5] :[
198+
::st/config.josh
199+
a = :/sub1
200+
::sub2/subsub/
201+
b = :/sub3
202+
]
203+
[8] sequence_number
180204

0 commit comments

Comments
 (0)