Skip to content

Commit 41c5137

Browse files
Implement stored filter nesting
1 parent 5edf587 commit 41c5137

File tree

2 files changed

+136
-59
lines changed

2 files changed

+136
-59
lines changed

josh-core/src/filter/mod.rs

Lines changed: 103 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@ pub use parse::get_comments;
1616
pub use parse::parse;
1717

1818
static FILTERS: LazyLock<std::sync::Mutex<std::collections::HashMap<Filter, Op>>> =
19-
LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
19+
LazyLock::new(|| Default::default());
2020
static WORKSPACES: LazyLock<std::sync::Mutex<std::collections::HashMap<git2::Oid, Filter>>> =
21-
LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
21+
LazyLock::new(|| Default::default());
2222
static ANCESTORS: LazyLock<
2323
std::sync::Mutex<std::collections::HashMap<git2::Oid, std::collections::HashSet<git2::Oid>>>,
24-
> = LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
24+
> = LazyLock::new(|| Default::default());
25+
26+
static LEGALIZED: LazyLock<
27+
std::sync::Mutex<std::collections::HashMap<(Filter, git2::Oid), Filter>>,
28+
> = LazyLock::new(|| Default::default());
2529

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.
@@ -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,50 @@ 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);
764784
compose(
765785
wsj_file,
766-
compose(get_filter(repo, tree, &path.join("workspace.josh")), base),
786+
compose(
787+
get_filter(transaction, tree, &path.join("workspace.josh")),
788+
base,
789+
),
767790
)
768791
}
769792

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,
793+
fn get_stored<'a>(
794+
transaction: &cache::Transaction,
775795
tree: &'a git2::Tree<'a>,
776796
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 {
797+
) -> Filter {
790798
let stored_path = path.with_extension("josh");
791799
let sj_file = file(stored_path.clone());
792-
compose(sj_file, get_filter(repo, tree, &stored_path))
800+
compose(sj_file, get_filter(transaction, tree, &stored_path))
793801
}
794802

795-
fn get_filter<'a>(repo: &'a git2::Repository, tree: &'a git2::Tree<'a>, path: &Path) -> Filter {
803+
fn get_filter<'a>(
804+
transaction: &cache::Transaction,
805+
tree: &'a git2::Tree<'a>,
806+
path: &Path,
807+
) -> Filter {
796808
let ws_path = normalize_path(path);
797809
let ws_id = ok_or!(tree.get_path(&ws_path), {
798810
return to_filter(Op::Empty);
799811
})
800812
.id();
801-
let ws_blob = tree::get_blob(repo, tree, &ws_path);
813+
let ws_blob = tree::get_blob(transaction.repo(), tree, &ws_path);
802814

803815
if let Some(f) = WORKSPACES.lock().unwrap().get(&ws_id) {
804816
*f
805817
} else {
806818
let f = parse::parse(&ws_blob).unwrap_or_else(|_| to_filter(Op::Empty));
819+
let f = legalize_stored(transaction, f, tree);
807820

808821
let f = if invert(f).is_ok() {
809822
f
@@ -1060,14 +1073,14 @@ fn apply_to_commit2(
10601073
}
10611074
}
10621075

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

10651078
let parent_filters = commit
10661079
.parents()
10671080
.map(|parent| {
10681081
rs_tracing::trace_scoped!("parent", "id": parent.id().to_string());
10691082
let pcw = get_workspace(
1070-
repo,
1083+
transaction,
10711084
&parent.tree().unwrap_or_else(|_| tree::empty(repo)),
10721085
&ws_path,
10731086
);
@@ -1078,23 +1091,14 @@ fn apply_to_commit2(
10781091
return per_rev_filter(transaction, commit, filter, commit_filter, parent_filters);
10791092
}
10801093
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);
1094+
let commit_filter = get_stored(transaction, &commit.tree()?, &s_path);
10911095

10921096
let parent_filters = commit
10931097
.parents()
10941098
.map(|parent| {
10951099
rs_tracing::trace_scoped!("parent", "id": parent.id().to_string());
10961100
let pcs = get_stored(
1097-
repo,
1101+
transaction,
10981102
&parent.tree().unwrap_or_else(|_| tree::empty(repo)),
10991103
&s_path,
11001104
);
@@ -1302,8 +1306,8 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos
13021306
.with_tree(tree::invert_paths(transaction, "", x.tree().clone())?))
13031307
}
13041308

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),
1309+
Op::Workspace(path) => apply(transaction, get_workspace(transaction, &x.tree(), &path), x),
1310+
Op::Stored(path) => apply(transaction, get_stored(transaction, &x.tree(), &path), x),
13071311

13081312
Op::Compose(filters) => {
13091313
let filtered: Vec<_> = filters
@@ -1394,12 +1398,9 @@ fn unapply_workspace<'a>(
13941398
match op {
13951399
Op::Workspace(path) => {
13961400
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-
);
1401+
let workspace = get_filter(transaction, &tree, Path::new("workspace.josh"));
1402+
let original_workspace =
1403+
get_filter(transaction, &parent_tree, &path.join("workspace.josh"));
14031404

14041405
let root = to_filter(Op::Subdir(path.to_owned()));
14051406
let wsj_file = to_filter(Op::File(
@@ -1428,8 +1429,8 @@ fn unapply_workspace<'a>(
14281429
}
14291430
Op::Stored(path) => {
14301431
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);
1432+
let stored = get_filter(transaction, &tree, &stored_path);
1433+
let original_stored = get_filter(transaction, &parent_tree, &stored_path);
14331434

14341435
let sj_file = file(stored_path.clone());
14351436
let filter = compose(sj_file, stored);
@@ -1628,6 +1629,58 @@ where
16281629
}
16291630
}
16301631

1632+
fn legalize_stored(t: &cache::Transaction, f: Filter, tree: &git2::Tree) -> Filter {
1633+
legalize_stored2(t, f, tree, &mut *LEGALIZED.lock().unwrap()).unwrap_or_else(|_| empty())
1634+
}
1635+
1636+
fn legalize_stored2(
1637+
t: &cache::Transaction,
1638+
f: Filter,
1639+
tree: &git2::Tree,
1640+
hm: &mut std::collections::HashMap<(Filter, git2::Oid), Filter>,
1641+
) -> JoshResult<Filter> {
1642+
if let Some(f) = hm.get(&(f, tree.id())) {
1643+
return Ok(*f);
1644+
}
1645+
1646+
// Put an entry into the hashtable to prevent infinite recursion.
1647+
// If we get called with the same arguments again before we return,
1648+
// Above check breaks the recursion.
1649+
hm.insert((f, tree.id()), empty());
1650+
1651+
let r = match to_op(f) {
1652+
Op::Compose(f) => {
1653+
let f = f
1654+
.into_iter()
1655+
.map(|f| legalize_stored2(t, f, tree, hm))
1656+
.collect::<JoshResult<Vec<_>>>()?;
1657+
to_filter(Op::Compose(f))
1658+
}
1659+
Op::Chain(a, b) => {
1660+
let first = legalize_stored2(t, a, tree, hm)?;
1661+
let second = legalize_stored2(
1662+
t,
1663+
b,
1664+
&apply(t, first, Apply::from_tree(tree.clone()))?.tree,
1665+
hm,
1666+
)?;
1667+
to_filter(Op::Chain(first, second))
1668+
}
1669+
Op::Subtract(a, b) => to_filter(Op::Subtract(
1670+
legalize_stored2(t, a, tree, hm)?,
1671+
legalize_stored2(t, b, tree, hm)?,
1672+
)),
1673+
Op::Exclude(f) => to_filter(Op::Exclude(legalize_stored2(t, f, tree, hm)?)),
1674+
Op::Pin(f) => to_filter(Op::Pin(legalize_stored2(t, f, tree, hm)?)),
1675+
Op::Stored(path) => get_stored(t, tree, &path),
1676+
_ => f,
1677+
};
1678+
1679+
hm.insert((f, tree.id()), r);
1680+
1681+
Ok(r)
1682+
}
1683+
16311684
fn per_rev_filter(
16321685
transaction: &cache::Transaction,
16331686
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)