Skip to content

Commit 4efbc69

Browse files
Implement :lookup filter
Change: lookup
1 parent 84f9cc1 commit 4efbc69

File tree

5 files changed

+290
-24
lines changed

5 files changed

+290
-24
lines changed

josh-core/src/cache.rs

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,66 @@
11
use super::*;
22
use std::collections::HashMap;
3+
use std::sync::{LazyLock, RwLock};
34

45
const CACHE_VERSION: u64 = 24;
56

7+
fn josh_commit_signature<'a>() -> JoshResult<git2::Signature<'a>> {
8+
Ok(if let Ok(time) = std::env::var("JOSH_COMMIT_TIME") {
9+
git2::Signature::new(
10+
"JOSH",
11+
"josh@josh-project.dev",
12+
&git2::Time::new(time.parse()?, 0),
13+
)?
14+
} else {
15+
git2::Signature::now("JOSH", "josh@josh-project.dev")?
16+
})
17+
}
18+
19+
fn store_note(repo: &git2::Repository, kind: &str, key: git2::Oid, from: git2::Oid, to: git2::Oid) {
20+
let signature = josh_commit_signature().unwrap();
21+
repo.note(
22+
&signature,
23+
&signature,
24+
Some(&format!("refs/josh/{}/{}/{}", kind, CACHE_VERSION, key)),
25+
from,
26+
&format!("{}", to),
27+
true,
28+
)
29+
.unwrap();
30+
}
31+
32+
fn lookup_note(
33+
repo: &git2::Repository,
34+
kind: &str,
35+
key: git2::Oid,
36+
from: git2::Oid,
37+
) -> Option<git2::Oid> {
38+
if from.as_bytes()[0] != 0 {
39+
return None;
40+
}
41+
if let Ok(note) = repo.find_note(
42+
Some(&format!("refs/josh/{}/{}/{}", kind, CACHE_VERSION, key)),
43+
from,
44+
) {
45+
Some(git2::Oid::from_str(note.message().unwrap()).unwrap())
46+
} else {
47+
None
48+
}
49+
}
50+
651
lazy_static! {
752
static ref DB: std::sync::Mutex<Option<sled::Db>> = std::sync::Mutex::new(None);
8-
static ref REF_CACHE: std::sync::Mutex<HashMap<git2::Oid, HashMap<git2::Oid, git2::Oid>>> =
9-
std::sync::Mutex::new(HashMap::new());
10-
static ref POPULATE_MAP: std::sync::Mutex<HashMap<(git2::Oid, git2::Oid), git2::Oid>> =
11-
std::sync::Mutex::new(HashMap::new());
12-
static ref GLOB_MAP: std::sync::Mutex<HashMap<(git2::Oid, git2::Oid), git2::Oid>> =
13-
std::sync::Mutex::new(HashMap::new());
1453
}
1554

55+
static REF_CACHE: LazyLock<RwLock<HashMap<git2::Oid, HashMap<git2::Oid, git2::Oid>>>> =
56+
LazyLock::new(Default::default);
57+
58+
static POPULATE_MAP: LazyLock<RwLock<HashMap<(git2::Oid, git2::Oid), git2::Oid>>> =
59+
LazyLock::new(Default::default);
60+
61+
static GLOB_MAP: LazyLock<RwLock<HashMap<(git2::Oid, git2::Oid), git2::Oid>>> =
62+
LazyLock::new(Default::default);
63+
1664
pub fn load(path: &std::path::Path) -> JoshResult<()> {
1765
*DB.lock()? = Some(
1866
sled::Config::default()
@@ -272,32 +320,32 @@ impl Transaction {
272320
}
273321

274322
pub fn insert_populate(&self, tree: (git2::Oid, git2::Oid), result: git2::Oid) {
275-
POPULATE_MAP.lock().unwrap().entry(tree).or_insert(result);
323+
POPULATE_MAP.write().unwrap().entry(tree).or_insert(result);
276324
}
277325

278326
pub fn get_populate(&self, tree: (git2::Oid, git2::Oid)) -> Option<git2::Oid> {
279-
return POPULATE_MAP.lock().unwrap().get(&tree).cloned();
327+
POPULATE_MAP.read().unwrap().get(&tree).cloned()
280328
}
281329

282330
pub fn insert_glob(&self, tree: (git2::Oid, git2::Oid), result: git2::Oid) {
283-
GLOB_MAP.lock().unwrap().entry(tree).or_insert(result);
331+
GLOB_MAP.write().unwrap().entry(tree).or_insert(result);
284332
}
285333

286334
pub fn get_glob(&self, tree: (git2::Oid, git2::Oid)) -> Option<git2::Oid> {
287-
return GLOB_MAP.lock().unwrap().get(&tree).cloned();
335+
GLOB_MAP.read().unwrap().get(&tree).cloned()
288336
}
289337

290338
pub fn insert_ref(&self, filter: filter::Filter, from: git2::Oid, to: git2::Oid) {
291339
REF_CACHE
292-
.lock()
340+
.write()
293341
.unwrap()
294342
.entry(filter.id())
295343
.or_default()
296344
.insert(from, to);
297345
}
298346

299347
pub fn get_ref(&self, filter: filter::Filter, from: git2::Oid) -> Option<git2::Oid> {
300-
if let Some(m) = REF_CACHE.lock().unwrap().get(&filter.id()) {
348+
if let Some(m) = REF_CACHE.read().unwrap().get(&filter.id()) {
301349
if let Some(oid) = m.get(&from) {
302350
if self.repo.odb().unwrap().exists(*oid) {
303351
return Some(*oid);
@@ -336,6 +384,9 @@ impl Transaction {
336384
});
337385

338386
t.insert(from.as_bytes(), to.as_bytes()).unwrap();
387+
if from.as_bytes()[0] == 0 {
388+
store_note(&self.repo, "cache", filter.id(), from, to);
389+
}
339390
}
340391
}
341392

@@ -396,8 +447,15 @@ impl Transaction {
396447
.open_tree(filter::spec(filter))
397448
.unwrap()
398449
});
399-
if let Some(oid) = t.get(from.as_bytes()).unwrap() {
400-
let oid = git2::Oid::from_bytes(&oid).unwrap();
450+
let oid = if let Some(oid) = t.get(from.as_bytes()).unwrap() {
451+
Some(git2::Oid::from_bytes(&oid).unwrap())
452+
} else if let Some(oid) = lookup_note(&self.repo, "cache", filter.id(), from) {
453+
Some(oid)
454+
} else {
455+
None
456+
};
457+
458+
if let Some(oid) = oid {
401459
if oid == git2::Oid::zero() {
402460
return Some(oid);
403461
}

josh-core/src/filter/mod.rs

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@ enum Op {
283283
Prefix(std::path::PathBuf),
284284
Subdir(std::path::PathBuf),
285285
Workspace(std::path::PathBuf),
286+
Lookup(std::path::PathBuf),
287+
Lookup2(git2::Oid),
286288

287289
Pattern(String),
288290
Message(String),
@@ -388,6 +390,8 @@ fn nesting2(op: &Op) -> usize {
388390
Op::Compose(filters) => 1 + filters.iter().map(|f| nesting(*f)).fold(0, |a, b| a.max(b)),
389391
Op::Exclude(filter) => 1 + nesting(*filter),
390392
Op::Workspace(_) => usize::MAX / 2, // divide by 2 to make sure there is enough headroom to avoid overflows
393+
Op::Lookup(_) => usize::MAX / 2, // divide by 2 to make sure there is enough headroom to avoid overflows
394+
Op::Lookup2(_) => usize::MAX / 2, // divide by 2 to make sure there is enough headroom to avoid overflows
391395
Op::Chain(a, b) => 1 + nesting(*a).max(nesting(*b)),
392396
Op::Subtract(a, b) => 1 + nesting(*a).max(nesting(*b)),
393397
Op::Rev(filters) => {
@@ -577,6 +581,12 @@ fn spec2(op: &Op) -> String {
577581
Op::Workspace(path) => {
578582
format!(":workspace={}", parse::quote_if(&path.to_string_lossy()))
579583
}
584+
Op::Lookup(path) => {
585+
format!(":lookup={}", parse::quote_if(&path.to_string_lossy()))
586+
}
587+
Op::Lookup2(oid) => {
588+
format!(":lookup2={}", oid.to_string())
589+
}
580590
Op::RegexReplace(replacements) => {
581591
let v = replacements
582592
.iter()
@@ -711,8 +721,8 @@ fn resolve_workspace_redirect<'a>(
711721
}
712722
}
713723

714-
fn get_workspace<'a>(repo: &'a git2::Repository, tree: &'a git2::Tree<'a>, path: &Path) -> Filter {
715-
let ws_path = normalize_path(&path.join("workspace.josh"));
724+
fn get_filter<'a>(repo: &'a git2::Repository, tree: &'a git2::Tree<'a>, path: &Path) -> Filter {
725+
let ws_path = normalize_path(path);
716726
let ws_id = ok_or!(tree.get_path(&ws_path), {
717727
return to_filter(Op::Empty);
718728
})
@@ -864,6 +874,69 @@ fn apply_to_commit2(
864874
Apply::from_commit(commit)?.with_parents(filtered_parent_ids),
865875
)?
866876
}
877+
Op::Lookup(lookup_path) => {
878+
let lookup_commit = if let Some(lookup_commit) =
879+
apply_to_commit2(&Op::Subdir(lookup_path.clone()), &commit, transaction)?
880+
{
881+
lookup_commit
882+
} else {
883+
return Ok(None);
884+
};
885+
886+
let op = Op::Lookup2(lookup_commit);
887+
888+
if let Some(start) = transaction.get(to_filter(op), commit.id()) {
889+
transaction.insert(filter, commit.id(), start, true);
890+
return Ok(Some(start));
891+
} else {
892+
return Ok(None);
893+
}
894+
}
895+
896+
Op::Lookup2(lookup_commit_id) => {
897+
let lookup_commit = repo.find_commit(*lookup_commit_id)?;
898+
for parent in lookup_commit.parents() {
899+
let lookup_tree = lookup_commit.tree_id();
900+
let cw = get_filter(
901+
repo,
902+
&repo.find_tree(lookup_tree)?,
903+
&std::path::PathBuf::new().join(commit.id().to_string()),
904+
);
905+
if cw != filter::empty() {
906+
if let Some(start) =
907+
apply_to_commit2(&Op::Lookup2(parent.id()), &commit, transaction)?
908+
{
909+
transaction.insert(filter, commit.id(), start, true);
910+
return Ok(Some(start));
911+
} else {
912+
return Ok(None);
913+
}
914+
}
915+
break;
916+
}
917+
let lookup_tree = lookup_commit.tree_id();
918+
let cw = get_filter(
919+
repo,
920+
&repo.find_tree(lookup_tree)?,
921+
&std::path::PathBuf::new().join(commit.id().to_string()),
922+
);
923+
924+
if cw == filter::empty() {
925+
// FIXME empty filter or no entry in table?
926+
for parent in commit.parents() {
927+
if let Some(start) = apply_to_commit2(&op, &parent, transaction)? {
928+
transaction.insert(filter, commit.id(), start, true);
929+
return Ok(Some(start));
930+
} else {
931+
return Ok(None);
932+
}
933+
}
934+
return Ok(None);
935+
}
936+
937+
Apply::from_commit(commit)?
938+
.with_tree(apply(transaction, cw, Apply::from_commit(commit)?)?.into_tree())
939+
}
867940
Op::Squash(Some(ids)) => {
868941
if let Some(sq) = ids.get(&LazyRef::Resolved(commit.id())) {
869942
let oid = if let Some(oid) =
@@ -995,7 +1068,7 @@ fn apply_to_commit2(
9951068

9961069
let normal_parents = some_or!(normal_parents, { return Ok(None) });
9971070

998-
let cw = get_workspace(repo, &commit.tree()?, ws_path);
1071+
let cw = get_filter(repo, &commit.tree()?, &ws_path.join("workspace.josh"));
9991072

10001073
let extra_parents = commit
10011074
.parents()
@@ -1010,10 +1083,10 @@ fn apply_to_commit2(
10101083
ws_path.clone()
10111084
};
10121085

1013-
let pcw = get_workspace(
1086+
let pcw = get_filter(
10141087
repo,
10151088
&parent.tree().unwrap_or_else(|_| tree::empty(repo)),
1016-
&p,
1089+
&p.join("workspace.josh"),
10171090
);
10181091
let f = opt::optimize(to_filter(Op::Subtract(cw, pcw)));
10191092

@@ -1129,6 +1202,8 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos
11291202
Op::Prune => Ok(x),
11301203
Op::Unsign => Ok(x),
11311204
Op::Rev(_) => Err(josh_error("not applicable to tree")),
1205+
Op::Lookup(_) => Err(josh_error("not applicable to tree")),
1206+
Op::Lookup2(_) => Err(josh_error("not applicable to tree")),
11321207
Op::Join(_) => Err(josh_error("not applicable to tree")),
11331208
Op::RegexReplace(replacements) => {
11341209
let mut t = x.tree().clone();
@@ -1217,7 +1292,10 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos
12171292
transaction,
12181293
compose(
12191294
wsj_file,
1220-
compose(get_workspace(repo, &x.tree(), path), base),
1295+
compose(
1296+
get_filter(repo, &x.tree(), &path.join("workspace.josh")),
1297+
base,
1298+
),
12211299
),
12221300
x,
12231301
)
@@ -1309,8 +1387,12 @@ fn unapply_workspace<'a>(
13091387
match op {
13101388
Op::Workspace(path) => {
13111389
let tree = pre_process_tree(transaction.repo(), tree)?;
1312-
let workspace = get_workspace(transaction.repo(), &tree, Path::new(""));
1313-
let original_workspace = get_workspace(transaction.repo(), &parent_tree, path);
1390+
let workspace = get_filter(transaction.repo(), &tree, Path::new("workspace.josh"));
1391+
let original_workspace = get_filter(
1392+
transaction.repo(),
1393+
&parent_tree,
1394+
&path.join("workspace.josh"),
1395+
);
13141396

13151397
let root = to_filter(Op::Subdir(path.to_owned()));
13161398
let wsj_file = to_filter(Op::File(Path::new("workspace.josh").to_owned()));

josh-core/src/filter/parse.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ fn make_op(args: &[&str]) -> JoshResult<Op> {
1010
["author", author, email] => Ok(Op::Author(author.to_string(), email.to_string())),
1111
["committer", author, email] => Ok(Op::Committer(author.to_string(), email.to_string())),
1212
["workspace", arg] => Ok(Op::Workspace(Path::new(arg).to_owned())),
13+
["lookup", arg] => Ok(Op::Lookup(Path::new(arg).to_owned())),
1314
["prefix"] => Err(josh_error(indoc!(
1415
r#"
1516
Filter ":prefix" requires an argument.

0 commit comments

Comments
 (0)