Skip to content

Commit fb1b387

Browse files
implement :lookup filter
Change: lookup
1 parent 734a836 commit fb1b387

File tree

6 files changed

+328
-24
lines changed

6 files changed

+328
-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()
@@ -1300,8 +1310,8 @@ fn resolve_workspace_redirect<'a>(
13001310
}
13011311
}
13021312

1303-
fn get_workspace<'a>(repo: &'a git2::Repository, tree: &'a git2::Tree<'a>, path: &Path) -> Filter {
1304-
let ws_path = normalize_path(&path.join("workspace.josh"));
1313+
fn get_filter<'a>(repo: &'a git2::Repository, tree: &'a git2::Tree<'a>, path: &Path) -> Filter {
1314+
let ws_path = normalize_path(path);
13051315
let ws_id = ok_or!(tree.get_path(&ws_path), {
13061316
return to_filter(Op::Empty);
13071317
})
@@ -1453,6 +1463,69 @@ fn apply_to_commit2(
14531463
Apply::from_commit(commit)?.with_parents(filtered_parent_ids),
14541464
)?
14551465
}
1466+
Op::Lookup(lookup_path) => {
1467+
let lookup_commit = if let Some(lookup_commit) =
1468+
apply_to_commit2(&Op::Subdir(lookup_path.clone()), &commit, transaction)?
1469+
{
1470+
lookup_commit
1471+
} else {
1472+
return Ok(None);
1473+
};
1474+
1475+
let op = Op::Lookup2(lookup_commit);
1476+
1477+
if let Some(start) = transaction.get(to_filter(op), commit.id()) {
1478+
transaction.insert(filter, commit.id(), start, true);
1479+
return Ok(Some(start));
1480+
} else {
1481+
return Ok(None);
1482+
}
1483+
}
1484+
1485+
Op::Lookup2(lookup_commit_id) => {
1486+
let lookup_commit = repo.find_commit(*lookup_commit_id)?;
1487+
for parent in lookup_commit.parents() {
1488+
let lookup_tree = lookup_commit.tree_id();
1489+
let cw = get_filter(
1490+
repo,
1491+
&repo.find_tree(lookup_tree)?,
1492+
&std::path::PathBuf::new().join(commit.id().to_string()),
1493+
);
1494+
if cw != filter::empty() {
1495+
if let Some(start) =
1496+
apply_to_commit2(&Op::Lookup2(parent.id()), &commit, transaction)?
1497+
{
1498+
transaction.insert(filter, commit.id(), start, true);
1499+
return Ok(Some(start));
1500+
} else {
1501+
return Ok(None);
1502+
}
1503+
}
1504+
break;
1505+
}
1506+
let lookup_tree = lookup_commit.tree_id();
1507+
let cw = get_filter(
1508+
repo,
1509+
&repo.find_tree(lookup_tree)?,
1510+
&std::path::PathBuf::new().join(commit.id().to_string()),
1511+
);
1512+
1513+
if cw == filter::empty() {
1514+
// FIXME empty filter or no entry in table?
1515+
for parent in commit.parents() {
1516+
if let Some(start) = apply_to_commit2(&op, &parent, transaction)? {
1517+
transaction.insert(filter, commit.id(), start, true);
1518+
return Ok(Some(start));
1519+
} else {
1520+
return Ok(None);
1521+
}
1522+
}
1523+
return Ok(None);
1524+
}
1525+
1526+
Apply::from_commit(commit)?
1527+
.with_tree(apply(transaction, cw, Apply::from_commit(commit)?)?.into_tree())
1528+
}
14561529
Op::Squash(Some(ids)) => {
14571530
if let Some(sq) = ids.get(&LazyRef::Resolved(commit.id())) {
14581531
let oid = if let Some(oid) =
@@ -1584,7 +1657,7 @@ fn apply_to_commit2(
15841657

15851658
let normal_parents = some_or!(normal_parents, { return Ok(None) });
15861659

1587-
let cw = get_workspace(repo, &commit.tree()?, ws_path);
1660+
let cw = get_filter(repo, &commit.tree()?, &ws_path.join("workspace.josh"));
15881661

15891662
let extra_parents = commit
15901663
.parents()
@@ -1599,10 +1672,10 @@ fn apply_to_commit2(
15991672
ws_path.clone()
16001673
};
16011674

1602-
let pcw = get_workspace(
1675+
let pcw = get_filter(
16031676
repo,
16041677
&parent.tree().unwrap_or_else(|_| tree::empty(repo)),
1605-
&p,
1678+
&p.join("workspace.josh"),
16061679
);
16071680
let f = opt::optimize(to_filter(Op::Subtract(cw, pcw)));
16081681

@@ -1718,6 +1791,8 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos
17181791
Op::Prune => Ok(x),
17191792
Op::Unsign => Ok(x),
17201793
Op::Rev(_) => Err(josh_error("not applicable to tree")),
1794+
Op::Lookup(_) => Err(josh_error("not applicable to tree")),
1795+
Op::Lookup2(_) => Err(josh_error("not applicable to tree")),
17211796
Op::Join(_) => Err(josh_error("not applicable to tree")),
17221797
Op::RegexReplace(replacements) => {
17231798
let mut t = x.tree().clone();
@@ -1806,7 +1881,10 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos
18061881
transaction,
18071882
compose(
18081883
wsj_file,
1809-
compose(get_workspace(repo, &x.tree(), path), base),
1884+
compose(
1885+
get_filter(repo, &x.tree(), &path.join("workspace.josh")),
1886+
base,
1887+
),
18101888
),
18111889
x,
18121890
)
@@ -1898,8 +1976,12 @@ fn unapply_workspace<'a>(
18981976
match op {
18991977
Op::Workspace(path) => {
19001978
let tree = pre_process_tree(transaction.repo(), tree)?;
1901-
let workspace = get_workspace(transaction.repo(), &tree, Path::new(""));
1902-
let original_workspace = get_workspace(transaction.repo(), &parent_tree, path);
1979+
let workspace = get_filter(transaction.repo(), &tree, Path::new("workspace.josh"));
1980+
let original_workspace = get_filter(
1981+
transaction.repo(),
1982+
&parent_tree,
1983+
&path.join("workspace.josh"),
1984+
);
19031985

19041986
let root = to_filter(Op::Subdir(path.to_owned()));
19051987
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)