Skip to content

Commit 43d663d

Browse files
Implement :lookup filter
Change: lookup
1 parent 73287ab commit 43d663d

File tree

13 files changed

+870
-60
lines changed

13 files changed

+870
-60
lines changed

docs/src/reference/filters.md

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

129+
### Freeze tree updates
130+
131+
`:freeze` filter prevents appearance of selected subtrees for a given revision.
132+
133+
In practical terms, it means that file and folder updates are "held off" or "frozen".
134+
If a tree entry already existed in the parent revision, that version will be chosen.
135+
Otherwise, the tree entry will not appear in the filtered commit.
136+
137+
The source of the parent revision is always the first commit parent.
138+
139+
Note that this filter is only practical when used with `:hook` or `workspace.josh`,
140+
as it should apply per-revision only. Applying `:freeze` for the whole history
141+
will result in the subtree being excluded from all revisions.
142+
143+
Refer to `freeze_filter_workspace.t` and `freeze_filter_hook.t` for reference.
144+
129145
Filter order matters
130146
--------------------
131147

josh-core/src/cache.rs

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use std::sync::{LazyLock, RwLock};
99
pub(crate) const CACHE_VERSION: u64 = 24;
1010

1111
pub trait CacheBackend: Send + Sync {
12-
fn read(&self, filter: filter::Filter, from: git2::Oid) -> JoshResult<Option<git2::Oid>>;
12+
fn read(&self, filter: filter::Filter, from: git2::Oid, np: u128) -> JoshResult<Option<git2::Oid>>;
1313

14-
fn write(&self, filter: filter::Filter, from: git2::Oid, to: git2::Oid) -> JoshResult<()>;
14+
fn write(&self, filter: filter::Filter, from: git2::Oid, to: git2::Oid, np: u128) -> JoshResult<()>;
1515
}
1616

1717
pub trait FilterHook {
@@ -323,6 +323,7 @@ impl Transaction {
323323
}
324324

325325
pub fn insert(&self, filter: filter::Filter, from: git2::Oid, to: git2::Oid, store: bool) {
326+
let np = if filter != filter::n_parents_f() {n_parents(self, from).unwrap() } else { 0 };
326327
let mut t2 = self.t2.borrow_mut();
327328
t2.commit_map
328329
.entry(filter.id())
@@ -334,14 +335,14 @@ impl Transaction {
334335
// the history length by a very large factor.
335336
if store || from.as_bytes()[0] == 0 {
336337
t2.cache
337-
.write_all(filter, from, to)
338+
.write_all(filter, from, to, np)
338339
.expect("Failed to write cache");
339340
}
340341
}
341342

342343
pub fn get_missing(&self) -> Vec<(filter::Filter, git2::Oid)> {
343344
let mut missing = self.t2.borrow().missing.clone();
344-
missing.sort_by_key(|(f, i)| (filter::nesting(*f), *f, *i));
345+
/* missing.sort_by_key(|(f, i)| (filter::nesting(*f), *f, *i)); */
345346
missing.dedup();
346347
missing.retain(|(f, i)| !self.known(*f, *i));
347348
self.t2.borrow_mut().missing = missing.clone();
@@ -358,7 +359,10 @@ impl Transaction {
358359
} else {
359360
let mut t2 = self.t2.borrow_mut();
360361
t2.misses += 1;
361-
t2.missing.push((filter, from));
362+
if !t2.missing.contains(&(filter, from)) {
363+
t2.missing.insert(0,(filter, from));
364+
/* eprintln!("N MISSING {}", t2.missing.len()); */
365+
}
362366
None
363367
}
364368
}
@@ -367,6 +371,7 @@ impl Transaction {
367371
if filter == filter::nop() {
368372
return Some(from);
369373
}
374+
let np = if filter != filter::n_parents_f() {n_parents(self, from).unwrap() } else { 0 };
370375
let t2 = self.t2.borrow_mut();
371376
if let Some(m) = t2.commit_map.get(&filter.id()) {
372377
if let Some(oid) = m.get(&from).cloned() {
@@ -376,7 +381,7 @@ impl Transaction {
376381

377382
let oid = t2
378383
.cache
379-
.read_propagate(filter, from)
384+
.read_propagate(filter, from, np)
380385
.expect("Failed to read from cache backend");
381386

382387
let oid = if let Some(oid) = oid { Some(oid) } else { None };
@@ -385,6 +390,9 @@ impl Transaction {
385390
if oid == git2::Oid::zero() {
386391
return Some(oid);
387392
}
393+
if filter == filter::n_parents_f() {
394+
return Some(oid);
395+
}
388396

389397
if self.repo.odb().unwrap().exists(oid) {
390398
// Only report an object as cached if it exists in the object database.
@@ -396,3 +404,61 @@ impl Transaction {
396404
None
397405
}
398406
}
407+
408+
409+
/// Encode a `u128` into a 20-byte git OID (SHA-1 sized).
410+
/// The high 4 bytes of the OID are zero; the low 16 bytes
411+
/// contain the big-endian integer.
412+
pub fn oid_from_u128(n: u128) -> git2::Oid {
413+
let mut bytes = [0u8; 20];
414+
// place the 16 integer bytes at the end (big-endian)
415+
bytes[20 - 16..].copy_from_slice(&n.to_be_bytes());
416+
// Safe: length is exactly 20
417+
git2::Oid::from_bytes(&bytes).expect("20-byte OID construction cannot fail")
418+
}
419+
420+
/// Decode a `u128` previously encoded by `oid_from_u128`.
421+
pub fn u128_from_oid(oid: git2::Oid) -> u128 {
422+
let b = oid.as_bytes();
423+
let mut n = [0u8; 16];
424+
n.copy_from_slice(&b[20 - 16..]); // take the last 16 bytes
425+
u128::from_be_bytes(n)
426+
}
427+
428+
pub fn n_parents(transaction: &cache::Transaction, input: git2::Oid) -> JoshResult<u128>
429+
{
430+
/* return Ok(0); */
431+
if let Some(count) = transaction.get(filter::n_parents_f(), input) {
432+
return Ok(u128_from_oid(count));
433+
}
434+
eprintln!("n_parents {:?}", input);
435+
436+
let commit = transaction.repo().find_commit(input)?;
437+
if let Some(p) = commit.parent_ids().next() {
438+
if let Some(count) = transaction.get(filter::n_parents_f(), p) {
439+
let pc = u128_from_oid(count);
440+
transaction.insert(filter::n_parents_f(), input, oid_from_u128(pc+1), true);
441+
return n_parents(transaction, input);
442+
}
443+
}
444+
445+
let mut walk = transaction.repo().revwalk()?;
446+
/* walk.simplify_first_parent()?; */
447+
walk.set_sorting(git2::Sort::REVERSE | git2::Sort::TOPOLOGICAL)?;
448+
walk.push(input)?;
449+
eprintln!("n_parents walk");
450+
451+
for c in walk {
452+
let commit = transaction.repo().find_commit(c?)?;
453+
let pc = if let Some(p) = commit.parent_ids().next() {
454+
n_parents(transaction, p)?
455+
}
456+
else {
457+
0
458+
};
459+
460+
transaction.insert(filter::n_parents_f(), commit.id(), oid_from_u128(pc+1), true);
461+
}
462+
n_parents(transaction, input)
463+
}
464+

josh-core/src/cache_notes.rs

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::JoshResult;
22
use crate::cache::{CACHE_VERSION, CacheBackend};
33
use crate::filter::Filter;
4+
use crate::filter::n_parents_f;
45

56
pub struct NotesCacheBackend {
67
repo: std::sync::Mutex<git2::Repository>,
@@ -15,24 +16,37 @@ impl NotesCacheBackend {
1516
}
1617
}
1718

18-
fn is_note_eligible(oid: git2::Oid) -> bool {
19-
oid.as_bytes()[0] == 0
19+
fn is_note_eligible(repo: &git2::Repository, oid: git2::Oid, np: u128) -> bool {
20+
/* oid.as_bytes()[0] % 8 == 0 */
21+
let parent_count = if let Ok(c) = repo.find_commit(oid) {
22+
c.parent_ids().count()
23+
} else {
24+
return false;
25+
};
26+
27+
np % 100 == 0
28+
|| parent_count != 1
29+
/* || (np+1) % 100 == 0 */
2030
}
2131

22-
fn note_path(key: git2::Oid) -> String {
23-
format!("refs/josh/{}/{}", CACHE_VERSION, key)
32+
fn note_path(key: git2::Oid, np: u128) -> String {
33+
format!("refs/josh/{}/{}/{}", CACHE_VERSION, key, np / 10000)
2434
}
2535

2636
impl CacheBackend for NotesCacheBackend {
27-
fn read(&self, filter: Filter, from: git2::Oid) -> JoshResult<Option<git2::Oid>> {
28-
let repo = self.repo.lock()?;
29-
let key = crate::filter::as_tree(&repo, filter)?;
37+
fn read(&self, filter: Filter, from: git2::Oid, np: u128) -> JoshResult<Option<git2::Oid>> {
38+
if filter == n_parents_f() {
3039

31-
if !is_note_eligible(from) {
3240
return Ok(None);
3341
}
42+
let repo = self.repo.lock()?;
43+
if !is_note_eligible(&repo, from, np) {
44+
return Ok(None);
45+
}
46+
47+
let key = crate::filter::as_tree(&*repo, filter)?;
3448

35-
if let Ok(note) = repo.find_note(Some(&note_path(key)), from) {
49+
if let Ok(note) = repo.find_note(Some(&note_path(key, np)), from) {
3650
let message = note.message().unwrap_or("");
3751
let result = git2::Oid::from_str(message)?;
3852

@@ -42,20 +56,27 @@ impl CacheBackend for NotesCacheBackend {
4256
}
4357
}
4458

45-
fn write(&self, filter: Filter, from: git2::Oid, to: git2::Oid) -> JoshResult<()> {
46-
let repo = self.repo.lock()?;
47-
let key = crate::filter::as_tree(&repo, filter)?;
59+
fn write(&self, filter: Filter, from: git2::Oid, to: git2::Oid, np: u128) -> JoshResult<()> {
60+
if filter == n_parents_f() {
4861

49-
if !is_note_eligible(from) {
5062
return Ok(());
5163
}
5264

65+
let repo = self.repo.lock()?;
66+
if !is_note_eligible(&*repo, from, np) {
67+
return Ok(());
68+
}
69+
70+
71+
let key = crate::filter::as_tree(&*repo, filter)?;
5372
let signature = crate::cache::josh_commit_signature()?;
5473

74+
75+
5576
repo.note(
5677
&signature,
5778
&signature,
58-
Some(&note_path(key)),
79+
Some(&note_path(key, np)),
5980
from,
6081
&to.to_string(),
6182
true,

josh-core/src/cache_sled.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ fn insert_sled_tree(filter: Filter) -> sled::Tree {
8080
}
8181

8282
impl CacheBackend for SledCacheBackend {
83-
fn read(&self, filter: Filter, from: git2::Oid) -> JoshResult<Option<git2::Oid>> {
83+
fn read(&self, filter: Filter, from: git2::Oid, np: u128) -> JoshResult<Option<git2::Oid>> {
8484
let mut trees = self.trees.lock()?;
8585
let tree = trees
8686
.entry(filter.id())
@@ -94,7 +94,7 @@ impl CacheBackend for SledCacheBackend {
9494
}
9595
}
9696

97-
fn write(&self, filter: Filter, from: git2::Oid, to: git2::Oid) -> JoshResult<()> {
97+
fn write(&self, filter: Filter, from: git2::Oid, to: git2::Oid, np: u128) -> JoshResult<()> {
9898
let mut trees = self.trees.lock()?;
9999
let tree = trees
100100
.entry(filter.id())

josh-core/src/cache_stack.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ impl CacheStack {
3333
filter: filter::Filter,
3434
from: git2::Oid,
3535
to: git2::Oid,
36+
np: u128,
3637
) -> JoshResult<()> {
3738
for backend in &self.backends {
38-
backend.write(filter, from, to)?;
39+
backend.write(filter, from, to, np)?;
3940
}
4041

4142
Ok(())
@@ -51,12 +52,13 @@ impl CacheStack {
5152
&self,
5253
filter: filter::Filter,
5354
from: git2::Oid,
55+
np: u128
5456
) -> JoshResult<Option<git2::Oid>> {
5557
let values = self
5658
.backends
5759
.iter()
5860
.enumerate()
59-
.find_map(|(index, backend)| match backend.read(filter, from) {
61+
.find_map(|(index, backend)| match backend.read(filter, from, np) {
6062
Ok(None) => None,
6163
Ok(Some(oid)) => Some(Ok((index, oid))),
6264
Err(e) => Some(Err(e)),
@@ -74,7 +76,7 @@ impl CacheStack {
7476
self.backends
7577
.iter()
7678
.take(index)
77-
.try_for_each(|backend| backend.write(filter, from, oid))?;
79+
.try_for_each(|backend| backend.write(filter, from, oid, np))?;
7880

7981
Ok(Some(oid))
8082
}

0 commit comments

Comments
 (0)