diff --git a/docs/src/reference/filters.md b/docs/src/reference/filters.md index 93f5154fe..c605e8860 100644 --- a/docs/src/reference/filters.md +++ b/docs/src/reference/filters.md @@ -114,6 +114,11 @@ commits that don't match any of the other shas. Produce the history that would be the result of pushing the passed branches with the passed filters into the upstream. +### Start filtering from a specific commit **:from(:filter)** + +Produce a history that keeps the original history leading up to the specified commit `` unchanged, +but applies the given `:filter` to all commits from that commit onwards. + ### Prune trivial merge commits **:prune=trivial-merge** Produce a history that skips all merge commits whose tree is identical to the first parents @@ -121,6 +126,22 @@ tree. Normally Josh will keep all commits in the filtered history whose tree differs from any of it's parents. +### Pin tree contents + +`:pin` filter prevents appearance of selected subtrees for a given revision. + +In practical terms, it means that file and folder updates are "held off", and revisions are "pinned". +If a tree entry already existed in the parent revision, that version will be chosen. +Otherwise, the tree entry will not appear in the filtered commit. + +The source of the parent revision is always the first commit parent. + +Note that this filter is only practical when used with `:hook` or `workspace.josh`, +as it should apply per-revision only. Applying `:pin` for the whole history +will result in the subtree being excluded from all revisions. + +Refer to `pin_filter_workspace.t` and `pin_filter_hook.t` for reference. + Filter order matters -------------------- diff --git a/josh-core/src/cache.rs b/josh-core/src/cache.rs index 77e4aaf19..063dec9b5 100644 --- a/josh-core/src/cache.rs +++ b/josh-core/src/cache.rs @@ -9,9 +9,20 @@ use std::sync::{LazyLock, RwLock}; pub(crate) const CACHE_VERSION: u64 = 24; pub trait CacheBackend: Send + Sync { - fn read(&self, filter: filter::Filter, from: git2::Oid) -> JoshResult>; - - fn write(&self, filter: filter::Filter, from: git2::Oid, to: git2::Oid) -> JoshResult<()>; + fn read( + &self, + filter: filter::Filter, + from: git2::Oid, + sequence_number: u128, + ) -> JoshResult>; + + fn write( + &self, + filter: filter::Filter, + from: git2::Oid, + to: git2::Oid, + sequence_number: u128, + ) -> JoshResult<()>; } pub trait FilterHook { @@ -323,6 +334,11 @@ impl Transaction { } pub fn insert(&self, filter: filter::Filter, from: git2::Oid, to: git2::Oid, store: bool) { + let sequence_number = if filter != filter::sequence_number() { + compute_sequence_number(self, from).unwrap() + } else { + 0 + }; let mut t2 = self.t2.borrow_mut(); t2.commit_map .entry(filter.id()) @@ -334,14 +350,13 @@ impl Transaction { // the history length by a very large factor. if store || from.as_bytes()[0] == 0 { t2.cache - .write_all(filter, from, to) + .write_all(filter, from, to, sequence_number) .expect("Failed to write cache"); } } pub fn get_missing(&self) -> Vec<(filter::Filter, git2::Oid)> { let mut missing = self.t2.borrow().missing.clone(); - missing.sort_by_key(|(f, i)| (filter::nesting(*f), *f, *i)); missing.dedup(); missing.retain(|(f, i)| !self.known(*f, *i)); self.t2.borrow_mut().missing = missing.clone(); @@ -358,7 +373,9 @@ impl Transaction { } else { let mut t2 = self.t2.borrow_mut(); t2.misses += 1; - t2.missing.push((filter, from)); + if !t2.missing.contains(&(filter, from)) { + t2.missing.insert(0, (filter, from)); + } None } } @@ -367,6 +384,11 @@ impl Transaction { if filter == filter::nop() { return Some(from); } + let sequence_number = if filter != filter::sequence_number() { + compute_sequence_number(self, from).unwrap() + } else { + 0 + }; let t2 = self.t2.borrow_mut(); if let Some(m) = t2.commit_map.get(&filter.id()) { if let Some(oid) = m.get(&from).cloned() { @@ -376,7 +398,7 @@ impl Transaction { let oid = t2 .cache - .read_propagate(filter, from) + .read_propagate(filter, from, sequence_number) .expect("Failed to read from cache backend"); let oid = if let Some(oid) = oid { Some(oid) } else { None }; @@ -385,6 +407,9 @@ impl Transaction { if oid == git2::Oid::zero() { return Some(oid); } + if filter == filter::sequence_number() { + return Some(oid); + } if self.repo.odb().unwrap().exists(oid) { // Only report an object as cached if it exists in the object database. @@ -396,3 +421,70 @@ impl Transaction { None } } + +/// Encode a `u128` into a 20-byte git OID (SHA-1 sized). +/// The high 4 bytes of the OID are zero; the low 16 bytes +/// contain the big-endian integer. +pub fn oid_from_u128(n: u128) -> git2::Oid { + let mut bytes = [0u8; 20]; + // place the 16 integer bytes at the end (big-endian) + bytes[20 - 16..].copy_from_slice(&n.to_be_bytes()); + // Safe: length is exactly 20 + git2::Oid::from_bytes(&bytes).expect("20-byte OID construction cannot fail") +} + +/// Decode a `u128` previously encoded by `oid_from_u128`. +pub fn u128_from_oid(oid: git2::Oid) -> u128 { + let b = oid.as_bytes(); + let mut n = [0u8; 16]; + n.copy_from_slice(&b[20 - 16..]); // take the last 16 bytes + u128::from_be_bytes(n) +} + +pub fn compute_sequence_number( + transaction: &cache::Transaction, + input: git2::Oid, +) -> JoshResult { + if let Some(count) = transaction.get(filter::sequence_number(), input) { + return Ok(u128_from_oid(count)); + } + + let commit = transaction.repo().find_commit(input)?; + if let Some(p) = commit.parent_ids().next() { + if let Some(count) = transaction.get(filter::sequence_number(), p) { + let pc = u128_from_oid(count); + transaction.insert( + filter::sequence_number(), + input, + oid_from_u128(pc + 1), + true, + ); + return Ok(pc + 1); + } + } + + let mut walk = transaction.repo().revwalk()?; + walk.set_sorting(git2::Sort::REVERSE | git2::Sort::TOPOLOGICAL)?; + walk.push(input)?; + + for c in walk { + let commit = transaction.repo().find_commit(c?)?; + let pc = if let Some(p) = commit.parent_ids().next() { + compute_sequence_number(transaction, p)? + } else { + 0 + }; + + transaction.insert( + filter::sequence_number(), + commit.id(), + oid_from_u128(pc + 1), + true, + ); + } + if let Some(count) = transaction.get(filter::sequence_number(), input) { + Ok(u128_from_oid(count)) + } else { + Err(josh_error("missing sequence_number")) + } +} diff --git a/josh-core/src/cache_notes.rs b/josh-core/src/cache_notes.rs index 1096000ae..f399e0d65 100644 --- a/josh-core/src/cache_notes.rs +++ b/josh-core/src/cache_notes.rs @@ -1,5 +1,6 @@ use crate::JoshResult; use crate::cache::{CACHE_VERSION, CacheBackend}; +use crate::filter; use crate::filter::Filter; pub struct NotesCacheBackend { @@ -15,24 +16,43 @@ impl NotesCacheBackend { } } -fn is_note_eligible(oid: git2::Oid) -> bool { - oid.as_bytes()[0] == 0 +fn is_note_eligible(repo: &git2::Repository, oid: git2::Oid, sequence_number: u128) -> bool { + let parent_count = if let Ok(c) = repo.find_commit(oid) { + c.parent_ids().count() + } else { + return false; + }; + + sequence_number % 100 == 0 || parent_count != 1 } -fn note_path(key: git2::Oid) -> String { - format!("refs/josh/{}/{}", CACHE_VERSION, key) +fn note_path(key: git2::Oid, sequence_number: u128) -> String { + format!( + "refs/josh/{}/{}/{}", + CACHE_VERSION, + sequence_number / 10000, + key, + ) } impl CacheBackend for NotesCacheBackend { - fn read(&self, filter: Filter, from: git2::Oid) -> JoshResult> { + fn read( + &self, + filter: Filter, + from: git2::Oid, + sequence_number: u128, + ) -> JoshResult> { + if filter == filter::sequence_number() { + return Ok(None); + } let repo = self.repo.lock()?; - let key = crate::filter::as_tree(&repo, filter)?; - - if !is_note_eligible(from) { + if !is_note_eligible(&repo, from, sequence_number) { return Ok(None); } - if let Ok(note) = repo.find_note(Some(¬e_path(key)), from) { + let key = crate::filter::as_tree(&*repo, filter)?; + + if let Ok(note) = repo.find_note(Some(¬e_path(key, sequence_number)), from) { let message = note.message().unwrap_or(""); let result = git2::Oid::from_str(message)?; @@ -42,20 +62,29 @@ impl CacheBackend for NotesCacheBackend { } } - fn write(&self, filter: Filter, from: git2::Oid, to: git2::Oid) -> JoshResult<()> { - let repo = self.repo.lock()?; - let key = crate::filter::as_tree(&repo, filter)?; + fn write( + &self, + filter: Filter, + from: git2::Oid, + to: git2::Oid, + sequence_number: u128, + ) -> JoshResult<()> { + if filter == filter::sequence_number() { + return Ok(()); + } - if !is_note_eligible(from) { + let repo = self.repo.lock()?; + if !is_note_eligible(&*repo, from, sequence_number) { return Ok(()); } + let key = crate::filter::as_tree(&*repo, filter)?; let signature = crate::cache::josh_commit_signature()?; repo.note( &signature, &signature, - Some(¬e_path(key)), + Some(¬e_path(key, sequence_number)), from, &to.to_string(), true, diff --git a/josh-core/src/cache_sled.rs b/josh-core/src/cache_sled.rs index d7bd595a6..9ea94e352 100644 --- a/josh-core/src/cache_sled.rs +++ b/josh-core/src/cache_sled.rs @@ -80,7 +80,12 @@ fn insert_sled_tree(filter: Filter) -> sled::Tree { } impl CacheBackend for SledCacheBackend { - fn read(&self, filter: Filter, from: git2::Oid) -> JoshResult> { + fn read( + &self, + filter: Filter, + from: git2::Oid, + _sequence_number: u128, + ) -> JoshResult> { let mut trees = self.trees.lock()?; let tree = trees .entry(filter.id()) @@ -94,7 +99,13 @@ impl CacheBackend for SledCacheBackend { } } - fn write(&self, filter: Filter, from: git2::Oid, to: git2::Oid) -> JoshResult<()> { + fn write( + &self, + filter: Filter, + from: git2::Oid, + to: git2::Oid, + _sequence_number: u128, + ) -> JoshResult<()> { let mut trees = self.trees.lock()?; let tree = trees .entry(filter.id()) diff --git a/josh-core/src/cache_stack.rs b/josh-core/src/cache_stack.rs index 8b71b4382..613fbe97a 100644 --- a/josh-core/src/cache_stack.rs +++ b/josh-core/src/cache_stack.rs @@ -33,9 +33,10 @@ impl CacheStack { filter: filter::Filter, from: git2::Oid, to: git2::Oid, + sequence_number: u128, ) -> JoshResult<()> { for backend in &self.backends { - backend.write(filter, from, to)?; + backend.write(filter, from, to, sequence_number)?; } Ok(()) @@ -51,16 +52,19 @@ impl CacheStack { &self, filter: filter::Filter, from: git2::Oid, + sequence_number: u128, ) -> JoshResult> { let values = self .backends .iter() .enumerate() - .find_map(|(index, backend)| match backend.read(filter, from) { - Ok(None) => None, - Ok(Some(oid)) => Some(Ok((index, oid))), - Err(e) => Some(Err(e)), - }); + .find_map( + |(index, backend)| match backend.read(filter, from, sequence_number) { + Ok(None) => None, + Ok(Some(oid)) => Some(Ok((index, oid))), + Err(e) => Some(Err(e)), + }, + ); let (index, oid) = match values { // None of the backends had the value @@ -74,7 +78,7 @@ impl CacheStack { self.backends .iter() .take(index) - .try_for_each(|backend| backend.write(filter, from, oid))?; + .try_for_each(|backend| backend.write(filter, from, oid, sequence_number))?; Ok(Some(oid)) } diff --git a/josh-core/src/filter/grammar.pest b/josh-core/src/filter/grammar.pest index 0a6a28bea..1f8c94839 100644 --- a/josh-core/src/filter/grammar.pest +++ b/josh-core/src/filter/grammar.pest @@ -24,6 +24,8 @@ filter_spec = { ( filter_group | filter_message | filter_rev + | filter_from + | filter_concat | filter_join | filter_replace | filter_squash @@ -51,6 +53,24 @@ filter_rev = { ~ ")" } +filter_from = { + CMD_START ~ "from" ~ "(" + ~ NEWLINE* + ~ (rev ~ filter_spec)? + ~ (CMD_SEP+ ~ (rev ~ filter_spec))* + ~ NEWLINE* + ~ ")" +} + +filter_concat = { + CMD_START ~ "from" ~ "(" + ~ NEWLINE* + ~ (rev ~ filter_spec)? + ~ (CMD_SEP+ ~ (rev ~ filter_spec))* + ~ NEWLINE* + ~ ")" +} + filter_join = { CMD_START ~ "join" ~ "(" ~ NEWLINE* @@ -60,7 +80,6 @@ filter_join = { ~ ")" } - filter_replace = { CMD_START ~ "replace" ~ "(" ~ NEWLINE* diff --git a/josh-core/src/filter/mod.rs b/josh-core/src/filter/mod.rs index dc64d2e04..e309ae424 100644 --- a/josh-core/src/filter/mod.rs +++ b/josh-core/src/filter/mod.rs @@ -51,6 +51,10 @@ impl Filter { } } +pub fn sequence_number() -> Filter { + Filter(git2::Oid::zero()) +} + impl std::fmt::Debug for Filter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { to_op(*self).fmt(f) @@ -223,6 +227,9 @@ fn to_filter(op: Op) -> Filter { } fn to_op(filter: Filter) -> Op { + if filter == sequence_number() { + return Op::Nop; + } FILTERS .lock() .unwrap() @@ -293,10 +300,13 @@ enum Op { Pattern(String), Message(String), + HistoryConcat(LazyRef, Filter), + Compose(Vec), Chain(Filter, Filter), Subtract(Filter, Filter), Exclude(Filter), + Pin(Filter), } /// Pretty print the filter on multiple lines with initial indentation level. @@ -342,6 +352,10 @@ fn pretty2(op: &Op, indent: usize, compose: bool) -> String { Op::Compose(filters) => ff(&filters, "exclude", indent), b => format!(":exclude[{}]", pretty2(&b, indent, false)), }, + Op::Pin(filter) => match to_op(*filter) { + Op::Compose(filters) => ff(&filters, "pin", indent), + b => format!(":pin[{}]", pretty2(&b, indent, false)), + }, Op::Chain(a, b) => match (to_op(*a), to_op(*b)) { (Op::Subdir(p1), Op::Prefix(p2)) if p1 == p2 => { format!("::{}/", parse::quote_if(&p1.to_string_lossy())) @@ -385,36 +399,6 @@ fn pretty2(op: &Op, indent: usize, compose: bool) -> String { } } -pub fn nesting(filter: Filter) -> usize { - nesting2(&to_op(filter)) -} - -fn nesting2(op: &Op) -> usize { - match op { - Op::Compose(filters) => 1 + filters.iter().map(|f| nesting(*f)).fold(0, |a, b| a.max(b)), - Op::Exclude(filter) => 1 + nesting(*filter), - Op::Workspace(_) => usize::MAX / 2, // divide by 2 to make sure there is enough headroom to avoid overflows - Op::Hook(_) => usize::MAX / 2, // divide by 2 to make sure there is enough headroom to avoid overflows - Op::Chain(a, b) => 1 + nesting(*a).max(nesting(*b)), - Op::Subtract(a, b) => 1 + nesting(*a).max(nesting(*b)), - Op::Rev(filters) => { - 1 + filters - .values() - .map(|filter| nesting(*filter)) - .max() - .unwrap_or(0) - } - Op::Join(filters) => { - 1 + filters - .values() - .map(|filter| nesting(*filter)) - .max() - .unwrap_or(0) - } - _ => 0, - } -} - pub fn lazy_refs(filter: Filter) -> Vec { lazy_refs2(&to_op(filter)) } @@ -430,7 +414,7 @@ fn lazy_refs2(op: &Op) -> Vec { acc }) } - Op::Exclude(filter) => lazy_refs(*filter), + Op::Exclude(filter) | Op::Pin(filter) => lazy_refs(*filter), Op::Chain(a, b) => { let mut av = lazy_refs(*a); av.append(&mut lazy_refs(*b)); @@ -442,6 +426,13 @@ fn lazy_refs2(op: &Op) -> Vec { av } Op::Rev(filters) => lazy_refs2(&Op::Join(filters.clone())), + Op::HistoryConcat(r, _) => { + let mut lr = Vec::new(); + if let LazyRef::Lazy(s) = r { + lr.push(s.to_owned()); + } + lr + } Op::Join(filters) => { let mut lr = lazy_refs2(&Op::Compose(filters.values().copied().collect())); lr.extend(filters.keys().filter_map(|x| { @@ -481,6 +472,7 @@ fn resolve_refs2(refs: &std::collections::HashMap, op: &Op) - Op::Compose(filters.iter().map(|f| resolve_refs(refs, *f)).collect()) } Op::Exclude(filter) => Op::Exclude(resolve_refs(refs, *filter)), + Op::Pin(filter) => Op::Pin(resolve_refs(refs, *filter)), Op::Chain(a, b) => Op::Chain(resolve_refs(refs, *a), resolve_refs(refs, *b)), Op::Subtract(a, b) => Op::Subtract(resolve_refs(refs, *a), resolve_refs(refs, *b)), Op::Rev(filters) => { @@ -501,6 +493,19 @@ fn resolve_refs2(refs: &std::collections::HashMap, op: &Op) - .collect(); Op::Rev(lr) } + Op::HistoryConcat(r, filter) => { + let f = resolve_refs(refs, *filter); + let resolved_ref = if let LazyRef::Lazy(s) = r { + if let Some(res) = refs.get(s) { + LazyRef::Resolved(*res) + } else { + r.clone() + } + } else { + r.clone() + }; + Op::HistoryConcat(resolved_ref, f) + } Op::Join(filters) => { let lr = filters .iter() @@ -543,6 +548,9 @@ fn resolve_refs2(refs: &std::collections::HashMap, op: &Op) - /// Compact, single line string representation of a filter so that `parse(spec(F)) == F` /// Note that this is will not be the best human readable representation. For that see `pretty(...)` pub fn spec(filter: Filter) -> String { + if filter == sequence_number() { + return "sequence_number".to_string(); + } let filter = opt::simplify(filter); spec2(&to_op(filter)) } @@ -565,6 +573,9 @@ fn spec2(op: &Op) -> String { Op::Exclude(b) => { format!(":exclude[{}]", spec(*b)) } + Op::Pin(filter) => { + format!(":pin[{}]", spec(*filter)) + } Op::Rev(filters) => { let mut v = filters .iter() @@ -636,6 +647,9 @@ fn spec2(op: &Op) -> String { Op::Message(m) => { format!(":{}", parse::quote(m)) } + Op::HistoryConcat(r, filter) => { + format!(":concat({}{})", r.to_string(), spec(*filter)) + } Op::Hook(hook) => { format!(":hook={}", parse::quote(hook)) } @@ -708,6 +722,9 @@ fn as_tree2(repo: &git2::Repository, op: &Op) -> JoshResult { Op::Exclude(b) => { builder.insert("exclude", as_tree(repo, *b)?, git2::FileMode::Tree.into())?; } + Op::Pin(b) => { + builder.insert("pin", as_tree(repo, *b)?, git2::FileMode::Tree.into())?; + } Op::Subdir(path) => { builder.insert( "subdir", @@ -828,6 +845,13 @@ fn as_tree2(repo: &git2::Repository, op: &Op) -> JoshResult { v.sort(); builder.insert("rev", rev_params(repo, &v)?, git2::FileMode::Tree.into())?; } + Op::HistoryConcat(r, f) => { + builder.insert( + "historyconcat", + rev_params(repo, &vec![(r.to_string(), *f)])?, + git2::FileMode::Tree.into(), + )?; + } Op::Join(filters) => { let mut v = filters .iter() @@ -1084,6 +1108,11 @@ fn from_tree2(repo: &git2::Repository, tree_oid: git2::Oid) -> JoshResult { let filter = from_tree2(repo, exclude_tree.id())?; Ok(Op::Exclude(to_filter(filter))) } + "pin" => { + let pin_tree = repo.find_tree(entry.id())?; + let filter = from_tree2(repo, pin_tree.id())?; + Ok(Op::Pin(to_filter(filter))) + } "rev" => { let rev_tree = repo.find_tree(entry.id())?; let mut filters = std::collections::BTreeMap::new(); @@ -1243,13 +1272,7 @@ pub fn apply_to_commit( let missing = transaction.get_missing(); - // Since 'missing' is sorted by nesting, the first is always the minimal - let minimal_nesting = missing.first().map(|(f, _)| nesting(*f)).unwrap_or(0); - for (f, i) in missing { - if nesting(f) != minimal_nesting { - break; - } history::walk2(f, i, transaction)?; } } @@ -1634,6 +1657,19 @@ fn apply_to_commit2( parent_filters, ); } + Op::HistoryConcat(r, f) => { + if let LazyRef::Resolved(c) = r { + let a = apply_to_commit2(&to_op(*f), &repo.find_commit(*c)?, transaction)?; + let a = some_or!(a, { return Ok(None) }); + if commit.id() == a { + transaction.insert(filter, commit.id(), *c, true); + return Ok(Some(*c)); + } + } else { + return Err(josh_error("unresolved lazy ref")); + } + Apply::from_commit(commit)? + } _ => { let filtered_parent_ids = commit .parent_ids() @@ -1685,7 +1721,7 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos Op::Nop => Ok(x), Op::Empty => Ok(x.with_tree(tree::empty(repo))), Op::Fold => Ok(x), - Op::Squash(None) => Ok(x), + Op::Squash(..) => Ok(x), Op::Author(author, email) => Ok(x.with_author((author.clone(), email.clone()))), Op::Committer(author, email) => Ok(x.with_committer((author.clone(), email.clone()))), Op::Message(m) => Ok(x.with_message( @@ -1695,7 +1731,7 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos &std::collections::HashMap::::new(), )?, )), - Op::Squash(Some(_)) => Err(josh_error("not applicable to tree")), + Op::HistoryConcat(..) => Ok(x), Op::Linear => Ok(x), Op::Prune => Ok(x), Op::Unsign => Ok(x), @@ -1810,6 +1846,25 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos return apply(transaction, *b, apply(transaction, *a, x.clone())?); } Op::Hook(_) => Err(josh_error("not applicable to tree")), + + Op::Pin(pin_filter) => { + let filtered_parent = if let Some(parent) = x.parents.as_ref().and_then(|p| p.first()) { + let parent = repo.find_commit(*parent)?; + let filtered = apply(transaction, *pin_filter, Apply::from_commit(&parent)?)?; + filtered.tree.id() + } else { + tree::empty_id() + }; + + // Mask out all the "pinned" files from current tree + let exclude = to_filter(Op::Exclude(*pin_filter)); + let with_mask = apply(transaction, exclude, x.clone())?; + + // Overlay filtered parent tree on current one to override versions + let with_overlay = tree::overlay(transaction, with_mask.tree.id(), filtered_parent)?; + + Ok(x.with_tree(repo.find_tree(with_overlay)?)) + } } } diff --git a/josh-core/src/filter/opt.rs b/josh-core/src/filter/opt.rs index b3092f765..3b7d177c4 100644 --- a/josh-core/src/filter/opt.rs +++ b/josh-core/src/filter/opt.rs @@ -82,6 +82,7 @@ pub fn simplify(filter: Filter) -> Filter { Op::Subtract(simplify(to_filter(a)), simplify(to_filter(b))) } Op::Exclude(b) => Op::Exclude(simplify(b)), + Op::Pin(b) => Op::Pin(simplify(b)), _ => to_op(filter), }); @@ -137,6 +138,7 @@ pub fn flatten(filter: Filter) -> Filter { Op::Subtract(flatten(to_filter(a)), flatten(to_filter(b))) } Op::Exclude(b) => Op::Exclude(flatten(b)), + Op::Pin(b) => Op::Pin(flatten(b)), _ => to_op(filter), }); @@ -440,11 +442,13 @@ fn step(filter: Filter) -> Filter { (a, b) => Op::Chain(step(to_filter(a)), step(to_filter(b))), }, Op::Exclude(b) if b == to_filter(Op::Nop) => Op::Empty, - Op::Exclude(b) if b == to_filter(Op::Empty) => Op::Nop, + Op::Exclude(b) | Op::Pin(b) if b == to_filter(Op::Empty) => Op::Nop, Op::Exclude(b) => Op::Exclude(step(b)), + Op::Pin(b) => Op::Pin(step(b)), Op::Subtract(a, b) if a == b => Op::Empty, Op::Subtract(af, bf) => match (to_op(af), to_op(bf)) { (Op::Empty, _) => Op::Empty, + (Op::Message(..), Op::Message(..)) => Op::Empty, (_, Op::Nop) => Op::Empty, (a, Op::Empty) => a, (Op::Chain(a, b), Op::Chain(c, d)) if a == c => { @@ -492,6 +496,7 @@ fn step(filter: Filter) -> Filter { pub fn invert(filter: Filter) -> JoshResult { let result = match to_op(filter) { Op::Nop => Some(Op::Nop), + Op::Message(..) => Some(Op::Nop), Op::Linear => Some(Op::Nop), Op::Prune => Some(Op::Prune), Op::Unsign => Some(Op::Unsign), @@ -502,6 +507,7 @@ pub fn invert(filter: Filter) -> JoshResult { Op::Pattern(pattern) => Some(Op::Pattern(pattern)), Op::Rev(_) => Some(Op::Nop), Op::RegexReplace(_) => Some(Op::Nop), + Op::Pin(_) => Some(Op::Nop), _ => None, }; diff --git a/josh-core/src/filter/parse.rs b/josh-core/src/filter/parse.rs index 10f59dc1d..4c9b7b9ab 100644 --- a/josh-core/src/filter/parse.rs +++ b/josh-core/src/filter/parse.rs @@ -115,6 +115,7 @@ fn parse_item(pair: pest::iterators::Pair) -> JoshResult { [cmd, args] => { let g = parse_group(args)?; match *cmd { + "pin" => Ok(Op::Pin(to_filter(Op::Compose(g)))), "exclude" => Ok(Op::Exclude(to_filter(Op::Compose(g)))), "subtract" if g.len() == 2 => Ok(Op::Subtract(g[0], g[1])), _ => Err(josh_error(&format!("parse_item: no match {:?}", cmd))), @@ -145,6 +146,31 @@ fn parse_item(pair: pest::iterators::Pair) -> JoshResult { Ok(Op::Rev(hm)) } + Rule::filter_from => { + let v: Vec<_> = pair.into_inner().map(|x| x.as_str()).collect(); + + if v.len() == 2 { + let oid = LazyRef::parse(v[0])?; + let filter = parse(v[1])?; + Ok(Op::Chain( + filter, + filter::to_filter(Op::HistoryConcat(oid, filter)), + )) + } else { + Err(josh_error("wrong argument count for :from")) + } + } + Rule::filter_concat => { + let v: Vec<_> = pair.into_inner().map(|x| x.as_str()).collect(); + + if v.len() == 2 { + let oid = LazyRef::parse(v[0])?; + let filter = parse(v[1])?; + Ok(Op::HistoryConcat(oid, filter)) + } else { + Err(josh_error("wrong argument count for :concat")) + } + } Rule::filter_replace => { let replacements = pair .into_inner() diff --git a/josh-core/src/history.rs b/josh-core/src/history.rs index cf0a3b743..ce372258b 100644 --- a/josh-core/src/history.rs +++ b/josh-core/src/history.rs @@ -16,59 +16,58 @@ pub fn walk2( return Ok(()); } - let (known, n_new) = find_known(filter, input, transaction)?; - let walk = { let mut walk = transaction.repo().revwalk()?; if filter::is_linear(filter) { walk.simplify_first_parent()?; } walk.set_sorting(git2::Sort::REVERSE | git2::Sort::TOPOLOGICAL)?; + walk.push(input)?; - for k in known.iter() { - walk.hide(*k)?; - } walk }; + let mut hide_callback = |id| { + let k = transaction.known(filter, id); + k + }; + let walk = walk.with_hide_callback(&mut hide_callback)?; log::info!( "Walking {} new commits for:\n{}\n", - n_new, + 0, filter::pretty(filter, 4), ); - let mut n_commits = 0; - let mut n_misses = transaction.misses(); + let mut n_in = 0; + let mut n_out = 0; let walks = transaction.new_walk(); for original_commit_id in walk { - if !filter::apply_to_commit3( + if filter::apply_to_commit3( filter, &transaction.repo().find_commit(original_commit_id?)?, transaction, )? { - break; + n_out += 1; } - n_commits += 1; - if n_commits % 1000 == 0 { + n_in += 1; + if n_in % 1000 == 0 { log::debug!( - "{} {} commits filtered, {} misses", + "{} {} commits filtered, {} written", " ->".repeat(walks), - n_commits, - transaction.misses() - n_misses, + n_in, + n_out, ); - n_misses = transaction.misses(); } } log::info!( - "{} {} commits filtered, {} misses", + "{} {} commits filtered, {} written", " ->".repeat(walks), - n_commits, - transaction.misses() - n_misses, + n_in, + n_out, ); - transaction.end_walk(); Ok(()) @@ -166,29 +165,6 @@ pub fn find_original( Ok(git2::Oid::zero()) } -fn find_known( - filter: filter::Filter, - input: git2::Oid, - transaction: &cache::Transaction, -) -> JoshResult<(Vec, usize)> { - log::debug!("find_known"); - let mut known = vec![]; - let mut walk = transaction.repo().revwalk()?; - walk.push(input)?; - - let n_new = walk - .with_hide_callback(&mut |id| { - let k = transaction.known(filter, id); - if k { - known.push(id) - } - k - })? - .count(); - log::debug!("/find_known {}", n_new); - Ok((known, n_new)) -} - // takes everything from base except its tree and replaces it with the tree // given pub fn rewrite_commit( diff --git a/tests/experimental/indexer.t b/tests/experimental/indexer.t index d91f25e36..8efb6b437 100644 --- a/tests/experimental/indexer.t +++ b/tests/experimental/indexer.t @@ -20,6 +20,7 @@ $ josh-filter -s :INDEX --update refs/heads/index [3] :INDEX + [3] sequence_number [6] _trigram_index $ josh-filter :/ --search "Another" diff --git a/tests/filter/ambiguous_merge.t b/tests/filter/ambiguous_merge.t index 55a223473..b89f59e0d 100644 --- a/tests/filter/ambiguous_merge.t +++ b/tests/filter/ambiguous_merge.t @@ -39,6 +39,7 @@ $ josh-filter -s ::sub1/ branch1 --update refs/heads/hidden_branch1 [2] :prefix=sub1 [3] :/sub1 + [5] sequence_number $ git checkout hidden_branch1 Switched to branch 'hidden_branch1' $ git log --graph --oneline --decorate @@ -54,6 +55,7 @@ $ josh-filter -s ::sub1/ master --update refs/heads/hidden_master [3] :prefix=sub1 [4] :/sub1 + [7] sequence_number $ git checkout hidden_master Switched to branch 'hidden_master' $ git log --graph --oneline --decorate @@ -86,6 +88,7 @@ $ josh-filter -s ::sub1/ --reverse master --update refs/heads/hidden_master [3] :prefix=sub1 [4] :/sub1 + [7] sequence_number $ git checkout master Switched to branch 'master' diff --git a/tests/filter/cmdline.t b/tests/filter/cmdline.t index 1f8a88e61..172a5f51d 100644 --- a/tests/filter/cmdline.t +++ b/tests/filter/cmdline.t @@ -36,6 +36,7 @@ $ josh-filter -s c=:/sub1 --update refs/josh/filter/libs/master libs/master [2] :/sub1 [2] :prefix=c + [4] sequence_number $ git log --graph --pretty=%s josh/filter/libs/master * add file2 * add file1 @@ -45,6 +46,7 @@ [2] :/sub1 [2] :/sub2 [2] :prefix=c + [7] sequence_number $ git log --graph --pretty=%s josh/filter/libs/foo * add file3 @@ -63,6 +65,13 @@ |-- heads | `-- master |-- josh + | |-- 24 + | | `-- 0 + | | |-- 5a4a73be0065ab74681f8302fd032224a32ffaed + | | |-- c37f9b5c5d2022cce0acbae87682f3eb5cdf29fb + | | |-- d14715b1358e12e9fb4132036e06049fd1ddf88f + | | |-- d6fc41eeb18ee6e3acf23e9a49bd4f10136d1db0 + | | `-- fcdb5e527e610ecf5ad7e25a5b388fffc2af7240 | `-- filter | `-- libs | |-- foo @@ -74,7 +83,7 @@ | `-- master `-- tags - 8 directories, 6 files + 10 directories, 11 files $ git read-tree HEAD josh/filter/libs/master josh/filter/libs/foo $ git commit -m "sync" diff --git a/tests/filter/commit_message_raw.t b/tests/filter/commit_message_raw.t index d352f924e..208acbc26 100644 --- a/tests/filter/commit_message_raw.t +++ b/tests/filter/commit_message_raw.t @@ -13,6 +13,7 @@ $ josh-filter -s c=:prefix=pre master --update refs/josh/filter/master [1] :prefix=c [1] :prefix=pre + [2] sequence_number $ git cat-file commit master tree 2f407f8ecb16a66b85e2c84d3889720b7a0e3762 author Josh 1112911993 +0000 diff --git a/tests/filter/compose_shadow_dir_same_name.t b/tests/filter/compose_shadow_dir_same_name.t index 9602b9455..b4c1bd5dd 100644 --- a/tests/filter/compose_shadow_dir_same_name.t +++ b/tests/filter/compose_shadow_dir_same_name.t @@ -92,6 +92,7 @@ :/xx :/sub/xx ] + [3] sequence_number $ git diff ${EMPTY_TREE}..FILTERED_HEAD diff --git a/file1 b/file1 new file mode 100644 diff --git a/tests/filter/concat.t b/tests/filter/concat.t new file mode 100644 index 000000000..05dab45d5 --- /dev/null +++ b/tests/filter/concat.t @@ -0,0 +1,42 @@ + $ export TESTTMP=${PWD} + + $ cd ${TESTTMP} + $ git init -q libs 1> /dev/null + $ cd libs + + $ mkdir sub1 + $ echo contents1 > sub1/file1 + $ git add sub1 + $ git commit -m "add file1" 1> /dev/null + + $ echo contents2 > sub1/file2 + $ git add sub1 + $ git commit -m "add file2" 1> /dev/null + $ git update-ref refs/heads/from_here HEAD + + + $ mkdir sub2 + $ echo contents1 > sub2/file3 + $ git add sub2 + $ git commit -m "add file3" 1> /dev/null + + $ josh-filter ":\"x\"" + + $ git log --graph --pretty=%s:%H HEAD + * add file3:667a912db7482f3c8023082c9b4c7b267792633a + * add file2:81b10fb4984d20142cd275b89c91c346e536876a + * add file1:bb282e9cdc1b972fffd08fd21eead43bc0c83cb8 + + $ git log --graph --pretty=%s:%H FILTERED_HEAD + * x:9d117d96dfdba145df43ebe37d9e526acac4b17c + * x:b232aa8eefaadfb5e38b3ad7355118aa59fb651e + * x:6b4d1f87c2be08f7d0f9d40b6679aab612e259b1 + + $ josh-filter -p ":from(81b10fb4984d20142cd275b89c91c346e536876a:\"x\")" + :"x":concat(81b10fb4984d20142cd275b89c91c346e536876a:"x") + $ josh-filter ":from(81b10fb4984d20142cd275b89c91c346e536876a:\"x\")" + + $ git log --graph --pretty=%s FILTERED_HEAD + * x + * add file2 + * add file1 diff --git a/tests/filter/deleted_dir.t b/tests/filter/deleted_dir.t index a5d4c658b..0b08e0150 100644 --- a/tests/filter/deleted_dir.t +++ b/tests/filter/deleted_dir.t @@ -19,6 +19,7 @@ in that subtree repo should have an empty tree $ josh-filter -s c=:/sub1 master --update refs/josh/filter/master [2] :/sub1 [2] :prefix=c + [4] sequence_number $ git log refs/josh/filter/master --graph --pretty=%s * add file2 @@ -36,6 +37,7 @@ in that subtree repo should have an empty tree $ josh-filter -s c=:/sub1 master --update refs/josh/filter/master [3] :/sub1 [3] :prefix=c + [6] sequence_number $ git log refs/josh/filter/master --graph --pretty=%s * rm sub1 diff --git a/tests/filter/empty_head.t b/tests/filter/empty_head.t index 03a5975c2..6d11db7d3 100644 --- a/tests/filter/empty_head.t +++ b/tests/filter/empty_head.t @@ -20,6 +20,7 @@ $ josh-filter -s :/sub1 master --update refs/josh/filter/master [2] :/sub1 + [3] sequence_number $ git log --graph --pretty=%s josh/filter/master * add file2 * add file1 @@ -27,6 +28,7 @@ $ josh-filter -s :/sub2 master --update refs/josh/filter/master [2] :/sub1 [2] :/sub2 + [3] sequence_number $ git log --graph --pretty=%s josh/filter/master * add file3 @@ -38,5 +40,6 @@ Warning: reference refs/josh/filter/master wasn't updated [2] :/sub1 [2] :/sub2 + [4] sequence_number $ git log --graph --pretty=%s josh/filter/master * add file3 diff --git a/tests/filter/empty_orphan.t b/tests/filter/empty_orphan.t index fb7647d26..95eb2f026 100644 --- a/tests/filter/empty_orphan.t +++ b/tests/filter/empty_orphan.t @@ -22,6 +22,7 @@ Empty root commits from unrelated parts of the tree should not be included $ josh-filter -s c=:/sub1 master --update refs/josh/filter/master [3] :/sub1 [3] :prefix=c + [6] sequence_number $ git log refs/josh/filter/master --graph --pretty=%s * add file3 @@ -83,6 +84,7 @@ Empty root commits from unrelated parts of the tree should not be included $ josh-filter -s c=:/sub1 master [3] :prefix=c [5] :/sub1 + [10] sequence_number $ git log FILTERED_HEAD --graph --pretty=%s * add file3 @@ -98,6 +100,7 @@ Empty root commits from unrelated parts of the tree should not be included [5] :/sub1 [5] :exclude[::sub1/] [6] :prefix=c + [10] sequence_number $ git log FILTERED_HEAD --graph --pretty=%s * add some_other_file @@ -113,6 +116,7 @@ Empty root commits from unrelated parts of the tree should not be included [5] :/sub1 [5] :exclude[::sub1/] [6] :prefix=c + [12] sequence_number $ git ls-tree --name-only -r FILTERED_HEAD x/c/some_file diff --git a/tests/filter/empty_reimport.t b/tests/filter/empty_reimport.t index 3021af81e..86ac5a3fd 100644 --- a/tests/filter/empty_reimport.t +++ b/tests/filter/empty_reimport.t @@ -40,6 +40,7 @@ $ josh-filter -s c=:/pre master --update refs/josh/filter/master [2] :prefix=c [4] :/pre + [7] sequence_number $ git log josh/filter/master --graph --pretty=%s * change on other 2 @@ -82,6 +83,7 @@ $ josh-filter -s c=:/pre master --update refs/josh/filter/master [5] :prefix=c [7] :/pre + [14] sequence_number $ git log josh/filter/master --graph --pretty=%s * Merge branch 'other_branch' diff --git a/tests/filter/exclude_compose.t b/tests/filter/exclude_compose.t index cbfa187fc..0dc2aa8e3 100644 --- a/tests/filter/exclude_compose.t +++ b/tests/filter/exclude_compose.t @@ -21,6 +21,7 @@ $ josh-filter -s :exclude[::sub2/] master --update refs/heads/hidden [2] :exclude[::sub2/] + [3] sequence_number $ git checkout -q hidden 1> /dev/null $ tree . @@ -44,6 +45,7 @@ ::sub2/ ] [2] :exclude[::sub2/] + [3] sequence_number $ git checkout -q refs/josh/filtered $ tree @@ -60,6 +62,7 @@ ] [2] :exclude[::sub2/] [3] :exclude[:/sub3:prefix=sub1] + [3] sequence_number $ git checkout -q refs/josh/filtered $ tree diff --git a/tests/filter/file.t b/tests/filter/file.t index 11b435f09..7b445eed5 100644 --- a/tests/filter/file.t +++ b/tests/filter/file.t @@ -33,6 +33,7 @@ c = :/sub1 a/b = :/sub2 ] + [4] sequence_number $ git log --graph --pretty=%s FILTERED_HEAD * add file3 * add file2 @@ -43,6 +44,7 @@ c = :/sub1 a/b = :/sub2 ] + [5] sequence_number $ git log --graph --pretty=%s FILTERED_HEAD * initial @@ -50,7 +52,11 @@ .git/refs/ |-- heads | `-- master + |-- josh + | `-- 24 + | `-- 0 + | `-- 8d28f139b3f76b91bc4e6146a5943eb1635bfb11 `-- tags - 3 directories, 1 file + 6 directories, 2 files diff --git a/tests/filter/gpgsig.t b/tests/filter/gpgsig.t index cfdd817e8..8fe4b2b79 100644 --- a/tests/filter/gpgsig.t +++ b/tests/filter/gpgsig.t @@ -39,11 +39,13 @@ If 0b4cf6c9efbbda1eada39fa9c1d21d2525b027bb shows up then the signature was lost Remove the signature, the shas are different. $ josh-filter :unsign refs/heads/master --update refs/heads/filtered -s [1] :unsign + [1] sequence_number $ git rev-parse master filtered cb22ebb8e47b109f7add68b1043e561e0db09802 0b4cf6c9efbbda1eada39fa9c1d21d2525b027bb $ josh-filter --reverse :unsign refs/heads/double-filtered --update refs/heads/filtered -s [1] :unsign + [1] sequence_number $ git rev-parse master double-filtered cb22ebb8e47b109f7add68b1043e561e0db09802 cb22ebb8e47b109f7add68b1043e561e0db09802 diff --git a/tests/filter/hide_view.t b/tests/filter/hide_view.t index e20ba1b59..ec51249f8 100644 --- a/tests/filter/hide_view.t +++ b/tests/filter/hide_view.t @@ -36,6 +36,7 @@ $ josh-filter -s c=:exclude[::sub1/] master --update refs/josh/filter/master [1] :prefix=c [2] :exclude[::sub1/] + [4] sequence_number $ git checkout josh/filter/master 2> /dev/null $ git log --graph --pretty=%s * add file3 @@ -51,6 +52,7 @@ [2] :exclude[::sub1/] [2] :exclude[::sub1/file2] [3] :prefix=c + [5] sequence_number $ git checkout josh/filter/master 2> /dev/null $ git log --graph --pretty=%s * add file3 @@ -70,6 +72,7 @@ [2] :exclude[::sub1/file2] [2] :exclude[::sub2/file3] [4] :prefix=c + [5] sequence_number $ git checkout josh/filter/master 2> /dev/null $ git log --graph --pretty=%s * add file2 diff --git a/tests/filter/hook_notes.t b/tests/filter/hook_notes.t index 9253d884b..50794fb15 100644 --- a/tests/filter/hook_notes.t +++ b/tests/filter/hook_notes.t @@ -25,6 +25,7 @@ $ josh-filter -s :hook=commits HEAD --update refs/josh/filtered [2] ::b [3] :hook="commits" + [3] sequence_number $ git log --graph --pretty=%s refs/josh/filtered * add f3 diff --git a/tests/filter/infofile.t b/tests/filter/infofile.t index d2a41e65a..26ef765a6 100644 --- a/tests/filter/infofile.t +++ b/tests/filter/infofile.t @@ -25,6 +25,7 @@ $ josh-filter -s c=:/sub1 master --update refs/josh/filter/master [2] :/sub1 [2] :prefix=c + [6] sequence_number $ git log --graph --pretty=%s josh/filter/master * add file2 * add file1 @@ -33,6 +34,7 @@ Warning: reference refs/josh/filter/master wasn't updated [2] :/sub1 [2] :prefix=c + [6] sequence_number $ git log --graph --pretty=%s josh/filter/master * add file2 * add file1 @@ -41,6 +43,7 @@ [2] :/sub1 [2] :/sub2 [3] :prefix=c + [7] sequence_number $ git log --graph --pretty=%s josh/filter/master * add file3 @@ -53,5 +56,6 @@ [2] :/sub1 [2] :/sub2 [3] :prefix=c + [8] sequence_number $ git log --graph --pretty=%s josh/filter/master * add file3 diff --git a/tests/filter/join.t b/tests/filter/join.t index 1a21dfca7..a08cfdd2f 100644 --- a/tests/filter/join.t +++ b/tests/filter/join.t @@ -8,6 +8,7 @@ Initial commit Apply prefix filter $ josh-filter -s :prefix=subtree refs/heads/master --update refs/heads/filtered [1] :prefix=subtree + [1] sequence_number $ git log --graph --pretty=%s refs/heads/filtered * add file1 diff --git a/tests/filter/linear.t b/tests/filter/linear.t index 27f8538f9..6f287362e 100644 --- a/tests/filter/linear.t +++ b/tests/filter/linear.t @@ -32,7 +32,8 @@ * add file1 $ josh-filter -s :linear refs/heads/master --update refs/heads/filtered - [3] :linear + [4] :linear + [4] sequence_number $ git log --graph --pretty=%s refs/heads/filtered * Merge branch 'branch2' @@ -57,7 +58,8 @@ * add file1 $ josh-filter -s :linear refs/heads/master --update refs/heads/filtered --reverse - [3] :linear + [4] :linear + [4] sequence_number $ git log --graph --pretty=%s refs/heads/master * mod file2 diff --git a/tests/filter/moved_dir.t b/tests/filter/moved_dir.t index c594ee6a2..bbc24b640 100644 --- a/tests/filter/moved_dir.t +++ b/tests/filter/moved_dir.t @@ -19,6 +19,7 @@ in that subtree repo should have an empty tree $ josh-filter -s c=:/sub1 master --update refs/josh/filter/master [2] :/sub1 [2] :prefix=c + [4] sequence_number $ git log refs/josh/filter/master --graph --pretty=%s * add file2 @@ -38,6 +39,7 @@ in that subtree repo should have an empty tree $ josh-filter -s c=:/sub1 master --update refs/josh/filter/master [3] :/sub1 [3] :prefix=c + [6] sequence_number $ git log refs/josh/filter/master --graph --pretty=%s * mv sub1 @@ -52,6 +54,7 @@ in that subtree repo should have an empty tree $ josh-filter -s c=:/sub1 master --update refs/josh/filter/master2 [3] :/sub1 [3] :prefix=c + [7] sequence_number $ git log refs/josh/filter/master2 --graph --pretty=%s * mv sub1 * add file2 diff --git a/tests/filter/pin_compose.t b/tests/filter/pin_compose.t new file mode 100644 index 000000000..4b4e9c88f --- /dev/null +++ b/tests/filter/pin_compose.t @@ -0,0 +1,117 @@ + $ export GIT_TREE_FMT='%(objectmode) %(objecttype) %(objectname) %(path)' + + $ export TESTTMP=${PWD} + $ cd ${TESTTMP} + + $ git init -q repo + $ cd repo + $ mkdir -p josh/overlay + $ mkdir -p code + +Populate repo contents for the first commit + + $ cat << EOF > code/app.js + > async fn main() { + > await fetch("http://127.0.0.1"); + > } + > EOF + + $ cat << EOF > code/lib.js + > fn log() { + > console.log("logged!"); + > } + > EOF + +Also create a workspace with the tree overlay filter + +We first select files in josh/overlay, whatever is in there +will take priority over the next tree in the composition filter + + $ mkdir -p workspaces/overlay + $ cat << EOF > workspaces/overlay/workspace.josh + > :[ + > :/code + > :/josh/overlay + > ] + > EOF + +Here's the repo layout at this point: + + $ tree . + . + |-- code + | |-- app.js + | `-- lib.js + |-- josh + | `-- overlay + `-- workspaces + `-- overlay + `-- workspace.josh + + 6 directories, 3 files + +Commit this: + + $ git add . + $ git commit -q -m "first commit" + +Now, filter the ws and check the result + + $ josh-filter ':workspace=workspaces/overlay' + $ git ls-tree --format="${GIT_TREE_FMT}" -r FILTERED_HEAD + 100644 blob 0747fcb9cd688a7876932dcc30006e6ffa9106d6 app.js + 100644 blob 5910ad90fda519a6cc9299d4688679d56dc8d6dd lib.js + 100644 blob 39dc0f50ad353a5ee880b4a87ecc06dee7b48c92 workspace.josh + +Save the OID of app.js before making changes: + + $ export ORIGINAL_APP_OID=$(git ls-tree --format="%(objectname)" FILTERED_HEAD app.js) + $ echo "${ORIGINAL_APP_OID}" + 0747fcb9cd688a7876932dcc30006e6ffa9106d6 + +Make next commit: both files will change + + $ cat << EOF > code/app.js + > async fn main() { + > await fetch("http://internal-secret-portal.company.com"); + > } + > EOF + + $ cat << EOF > code/lib.js + > fn log() { + > console.log("INFO: logged!"); + > } + > EOF + + $ git add code/app.js code/lib.js + +Insert the old app.js OID into the overlay. +Note that we aren't copying the file -- we are directly referencing the OID. +This ensures it's the same entry in git ODB. + + $ git update-index --add --cacheinfo 100644,"${ORIGINAL_APP_OID}","josh/overlay/app.js" + $ git commit -q -m "second commit" + +Verify commit tree looks right: + + $ git ls-tree -r --format="${GIT_TREE_FMT}" HEAD + 100644 blob 1540d15e1bdc499e31ea05703a0daaf520774a85 code/app.js + 100644 blob 627cdb2ef7a3eb1a2b4537ce17fea1d93bfecdd2 code/lib.js + 100644 blob 0747fcb9cd688a7876932dcc30006e6ffa9106d6 josh/overlay/app.js + 100644 blob 39dc0f50ad353a5ee880b4a87ecc06dee7b48c92 workspaces/overlay/workspace.josh + +Filter the workspace and check the result: + + $ josh-filter ':workspace=workspaces/overlay' + +We can see now that the app.js file was held at the previous version: + + $ git ls-tree --format="${GIT_TREE_FMT}" -r FILTERED_HEAD + 100644 blob 0747fcb9cd688a7876932dcc30006e6ffa9106d6 app.js + 100644 blob 627cdb2ef7a3eb1a2b4537ce17fea1d93bfecdd2 lib.js + 100644 blob 39dc0f50ad353a5ee880b4a87ecc06dee7b48c92 workspace.josh + + $ git show FILTERED_HEAD:app.js + async fn main() { + await fetch("http://127.0.0.1"); + } diff --git a/tests/filter/pin_filter_hook.t b/tests/filter/pin_filter_hook.t new file mode 100644 index 000000000..358f94986 --- /dev/null +++ b/tests/filter/pin_filter_hook.t @@ -0,0 +1,134 @@ + $ export GIT_TREE_FMT='%(objectmode) %(objecttype) %(objectname) %(path)' + + $ export TESTTMP=${PWD} + $ cd ${TESTTMP} + +Similar scenario to pin_filter_workspace.t, except here it's using +filter hooks as opposed to workspace.josh + + $ git init -q repo + $ cd repo + $ mkdir -p code + +Populate repo contents for the first commit + + $ cat << EOF > code/app.js + > async fn main() { + > await fetch("http://127.0.0.1"); + > } + > EOF + + $ cat << EOF > code/lib.js + > fn log() { + > console.log("logged!"); + > } + > EOF + + $ git add . + $ git commit -q -m "first commit" + +Add note with basic filter - no pin yet + + $ git notes add -m ':/code' -f + +Update files, but pin one file using git notes + + $ cat << EOF > code/app.js + > async fn main() { + > await fetch("https://secret-internal-resource.contoso.com"); + > } + > EOF + + $ cat << EOF > code/lib2.js + > fn foo() {} + > EOF + + $ git add . + $ git commit -q -m "secret update" + +Add note with pin filter for this commit + + $ git notes add -m ':/code:pin[::app.js]' -f + +Filter using the hook + + $ josh-filter ':hook=commits' + +Check the filtered history + + $ git log --oneline FILTERED_HEAD + e22b4d0 secret update + 71f53f9 first commit + +Verify that the secret update commit doesn't show app.js changes + + $ git show FILTERED_HEAD + commit e22b4d031f61b2d443a11700627fa73011bfd95f + Author: Josh + Date: Thu Apr 7 22:13:13 2005 +0000 + + secret update + + diff --git a/lib2.js b/lib2.js + new file mode 100644 + index 0000000..8f3b7ef + --- /dev/null + +++ b/lib2.js + @@ -0,0 +1 @@ + +fn foo() {} + +Add another file and use hook to hold it + + $ cat << EOF > code/lib3.js + > fn bar() {} + > EOF + +Also update app.js to remove the secret - remove pin from it + + $ cat << EOF > code/app.js + > async fn main() { + > const host = process.env.REMOTE_HOST; + > await fetch(host); + > } + > EOF + + $ git add . + $ git commit -q -m "read env variable" + +Add note to pin the new file and allow app.js changes + + $ git notes add -m ':/code:pin[::lib3.js]' -f + + $ josh-filter ':hook=commits' + +Check the resulting history + + $ git log --oneline FILTERED_HEAD + 6b712b2 read env variable + e22b4d0 secret update + 71f53f9 first commit + +Verify that app.js changes are now visible but lib3.js is pinned + + $ git show FILTERED_HEAD -- app.js + commit 6b712b21b99511e8b40334a96ef266d2a38f2e94 + Author: Josh + Date: Thu Apr 7 22:13:13 2005 +0000 + + read env variable + + diff --git a/app.js b/app.js + index 0747fcb..990514f 100644 + --- a/app.js + +++ b/app.js + @@ -1,3 +1,4 @@ + async fn main() { + - await fetch("http://127.0.0.1"); + + const host = process.env.REMOTE_HOST; + + await fetch(host); + } + + $ git ls-tree --format="${GIT_TREE_FMT}" -r FILTERED_HEAD + 100644 blob 990514fe4034b4d8dac7ffa05d4a74331b57cb21 app.js + 100644 blob 5910ad90fda519a6cc9299d4688679d56dc8d6dd lib.js + 100644 blob 8f3b7ef112a0f4951016967f520b9399c02f902d lib2.js diff --git a/tests/filter/pin_filter_workspace.t b/tests/filter/pin_filter_workspace.t new file mode 100644 index 000000000..4fef038e7 --- /dev/null +++ b/tests/filter/pin_filter_workspace.t @@ -0,0 +1,163 @@ + $ export GIT_TREE_FMT='%(objectmode) %(objecttype) %(objectname) %(path)' + + $ export TESTTMP=${PWD} + $ cd ${TESTTMP} + + $ git init -q repo + $ cd repo + $ mkdir -p code + +Populate repo contents for the first commit + + $ cat << EOF > code/app.js + > async fn main() { + > await fetch("http://127.0.0.1"); + > } + > EOF + + $ cat << EOF > code/lib.js + > fn log() { + > console.log("logged!"); + > } + > EOF + +Create a workspace: the :pin filter must be applicable per-commit, so it should +be tied to commit sha1 either via workspace or via hook. Otherwise we are going +to pin a file in every commit, resulting in no versions of the file appearing at all. +Don't pin anything yet. + + $ mkdir -p workspaces/code + $ cat << EOF > workspaces/code/workspace.josh + > :/code + > EOF + + $ git add . + $ git commit -q -m "first commit" + + $ josh-filter ':workspace=workspaces/code' + $ git ls-tree --format="${GIT_TREE_FMT}" -r FILTERED_HEAD + 100644 blob 0747fcb9cd688a7876932dcc30006e6ffa9106d6 app.js + 100644 blob 5910ad90fda519a6cc9299d4688679d56dc8d6dd lib.js + 100644 blob 035bf7abf8a572ccf122f71984ed0e9680e8a01d workspace.josh + +Update a file, but put it on pin in workspace + + $ cat << EOF > code/app.js + > async fn main() { + > await fetch("https://secret-internal-resource.contoso.com"); + > } + > EOF + + $ cat << EOF > workspaces/code/workspace.josh + > :/code:pin[::app.js] + > EOF + + $ git add . + $ git commit -q -m "secret update" + +Filter and check history + + $ josh-filter ':workspace=workspaces/code' + $ git log --oneline FILTERED_HEAD + 4f83a36 secret update + 6620984 first commit + +We only see workspace.josh update + + $ git show FILTERED_HEAD + commit 4f83a362554fde79389596222637db9084e028bc + Author: Josh + Date: Thu Apr 7 22:13:13 2005 +0000 + + secret update + + diff --git a/workspace.josh b/workspace.josh + index 035bf7a..801a6f7 100644 + --- a/workspace.josh + +++ b/workspace.josh + @@ -1 +1 @@ + -:/code + +:/code:pin[::app.js] + +We can also exclude workspace.josh itself + + $ josh-filter ':workspace=workspaces/code:exclude[::workspace.josh]' + +This makes the commit disappear completely + + $ git log --oneline FILTERED_HEAD + 71f53f9 first commit + +Now, let's add another file, but prevent it from appearing + + $ cat << EOF > code/lib2.js + > fn bar() {} + > EOF + +Also, update app.js and remove pin from it + + $ cat << EOF > code/app.js + > async fn main() { + > const host = process.env.REMOTE_HOST; + > await fetch(host); + > } + > EOF + + $ cat << EOF > workspaces/code/workspace.josh + > :/code:pin[::lib2.js] + > EOF + + $ git add . + $ git commit -q -m "read env variable" + + $ josh-filter ':workspace=workspaces/code' + +Check the resulting history + + $ git log --oneline FILTERED_HEAD + 824fe83 read env variable + 4f83a36 secret update + 6620984 first commit + +Check that files changed in commits are as expected + + $ git show --stat FILTERED_HEAD~1 + commit 4f83a362554fde79389596222637db9084e028bc + Author: Josh + Date: Thu Apr 7 22:13:13 2005 +0000 + + secret update + + workspace.josh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + + $ git show --stat FILTERED_HEAD + commit 824fe83d4a74a71fd7bec25756166863e063b932 + Author: Josh + Date: Thu Apr 7 22:13:13 2005 +0000 + + read env variable + + app.js | 3 ++- + workspace.josh | 2 +- + 2 files changed, 3 insertions(+), 2 deletions(-) + +We can also verify that the "offending" version was skipped in filtered history + + $ git show FILTERED_HEAD -- app.js + commit 824fe83d4a74a71fd7bec25756166863e063b932 + Author: Josh + Date: Thu Apr 7 22:13:13 2005 +0000 + + read env variable + + diff --git a/app.js b/app.js + index 0747fcb..990514f 100644 + --- a/app.js + +++ b/app.js + @@ -1,3 +1,4 @@ + async fn main() { + - await fetch("http://127.0.0.1"); + + const host = process.env.REMOTE_HOST; + + await fetch(host); + } diff --git a/tests/filter/prefix.t b/tests/filter/prefix.t index dae646ece..1c5a98216 100644 --- a/tests/filter/prefix.t +++ b/tests/filter/prefix.t @@ -8,6 +8,7 @@ Initial commit Apply prefix filter $ josh-filter -s :prefix=subtree refs/heads/master --update refs/heads/filtered [1] :prefix=subtree + [1] sequence_number $ git log --graph --pretty=%s refs/heads/filtered * add file1 diff --git a/tests/filter/prune_trivial_merge.t b/tests/filter/prune_trivial_merge.t index d52ca62ca..9572b366f 100644 --- a/tests/filter/prune_trivial_merge.t +++ b/tests/filter/prune_trivial_merge.t @@ -35,6 +35,7 @@ $ josh-filter -s ::file1 [4] ::file1 + [5] sequence_number $ git log --graph --pretty=%s FILTERED_HEAD * Merge branch 'branch1' |\ @@ -45,6 +46,7 @@ $ josh-filter -s ::file1:prune=trivial-merge [3] :prune=trivial-merge [4] ::file1 + [6] sequence_number $ git log --graph --pretty=%s FILTERED_HEAD * empty commit diff --git a/tests/filter/rev.t b/tests/filter/rev.t index d44906acc..e48451dad 100644 --- a/tests/filter/rev.t +++ b/tests/filter/rev.t @@ -39,6 +39,7 @@ $ josh-filter -s :prefix=x/y --update refs/heads/filtered [5] :prefix=x [5] :prefix=y + [10] sequence_number $ git log --graph --decorate --pretty=%H:%T refs/heads/filtered * 37f8b29c9e892ea0eb7abac2759ddc6fb0337203:dcbbddf47649f8e73f59fae92896c0d2cd02b6ec |\ @@ -51,6 +52,7 @@ $ josh-filter -s ":rev(ffffffffffffffffffffffffffffffffffffffff:prefix=x/y)" --update refs/heads/filtered [5] :prefix=x [5] :prefix=y + [10] sequence_number ERROR: `:rev(...)` with nonexistent OID: ffffffffffffffffffffffffffffffffffffffff [1] @@ -58,6 +60,7 @@ [5] :prefix=x [5] :prefix=y [5] :rev(975d4c4975912729482cc864d321c5196a969271:prefix=x/y) + [10] sequence_number $ git log --graph --decorate --pretty=%H:%T refs/heads/filtered * 54651c29aa86e8512a7b9d39e3b8ea26da644247:5f47d9fdffdc726bb8ebcfea67531d2574243c5d |\ @@ -73,6 +76,7 @@ [5] :prefix=y [5] :rev(975d4c4975912729482cc864d321c5196a969271:prefix=x/y) [5] :rev(e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) + [10] sequence_number $ git log --graph --decorate --pretty=%H:%T refs/heads/filtered * 5fe60a2d55b652822b3d3f25410714e9053ba72b:5f47d9fdffdc726bb8ebcfea67531d2574243c5d |\ @@ -95,6 +99,7 @@ [5] :rev(975d4c4975912729482cc864d321c5196a969271:prefix=x/y) [5] :rev(975d4c4975912729482cc864d321c5196a969271:prefix=x/y,e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [5] :rev(e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) + [10] sequence_number $ git log --graph --decorate --pretty=%H:%T refs/heads/filtered * 63fea1234f375bd09019b676da8291f28d2ddb43:5f47d9fdffdc726bb8ebcfea67531d2574243c5d |\ @@ -121,6 +126,7 @@ [5] :rev(975d4c4975912729482cc864d321c5196a969271:prefix=x/z,e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [5] :rev(e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [6] :prefix=x + [11] sequence_number $ cat > filter.josh < :rev( > e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y @@ -140,6 +146,7 @@ [5] :rev(975d4c4975912729482cc864d321c5196a969271:prefix=x/z,e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [5] :rev(e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [6] :prefix=x + [11] sequence_number $ git log --graph --decorate --pretty=%H:%T refs/heads/filtered * 1c4fe25dc386c77adaae12d6b1cd3abfa296fc3c:5f47d9fdffdc726bb8ebcfea67531d2574243c5d |\ @@ -155,13 +162,14 @@ [2] :rev(0000000000000000000000000000000000000000:prefix=x/y,975d4c4975912729482cc864d321c5196a969271:prefix=x/z) [2] :rev(0000000000000000000000000000000000000000:prefix=x/y,e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [2] :rev(0000000000000000000000000000000000000000:prefix=x/z,e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) - [3] :linear + [5] :linear [5] :prefix=y [5] :rev(975d4c4975912729482cc864d321c5196a969271:prefix=x/y) [5] :rev(975d4c4975912729482cc864d321c5196a969271:prefix=x/y,e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [5] :rev(975d4c4975912729482cc864d321c5196a969271:prefix=x/z,e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [5] :rev(e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [6] :prefix=x + [11] sequence_number $ git log --graph --decorate --pretty=%H:%T refs/heads/filtered * f8e8bc9daf54340c9fce647be467d2577b623bbe:5f47d9fdffdc726bb8ebcfea67531d2574243c5d * e707f76bb6a1390f28b2162da5b5eb6933009070:5d8a699f74b48c9c595f4615dd3755244e11d176 @@ -195,14 +203,15 @@ [2] :rev(0000000000000000000000000000000000000000:prefix=x/y,e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [2] :rev(0000000000000000000000000000000000000000:prefix=x/z,e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [2] :rev(0000000000000000000000000000000000000000:prefix=y,0b4cf6c9efbbda1eada39fa9c1d21d2525b027bb:prefix=z) - [3] :linear [3] :rev(0000000000000000000000000000000000000000:prefix=x,0b4cf6c9efbbda1eada39fa9c1d21d2525b027bb:prefix=z,e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=y) + [5] :linear [5] :prefix=y [5] :rev(975d4c4975912729482cc864d321c5196a969271:prefix=x/y) [5] :rev(975d4c4975912729482cc864d321c5196a969271:prefix=x/y,e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [5] :rev(975d4c4975912729482cc864d321c5196a969271:prefix=x/z,e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [5] :rev(e707f76bb6a1390f28b2162da5b5eb6933009070:prefix=x/y) [6] :prefix=x + [12] sequence_number $ git log --graph --decorate --pretty=%H:%T refs/heads/filtered * 2944f04c33ea037f7696282bf20b2e570524552e:047b1b6f39e8d95b62ef7f136189005d0e3c80b3 diff --git a/tests/filter/reverse_hide.t b/tests/filter/reverse_hide.t index dc54f0a1a..37ee2d6c0 100644 --- a/tests/filter/reverse_hide.t +++ b/tests/filter/reverse_hide.t @@ -16,6 +16,7 @@ $ josh-filter -s :exclude[::sub2/] master --update refs/heads/hidden [1] :exclude[::sub2/] + [2] sequence_number $ git checkout hidden 1> /dev/null Switched to branch 'hidden' $ tree @@ -33,6 +34,7 @@ $ josh-filter -s :exclude[::sub2/] --reverse master --update refs/heads/hidden [1] :exclude[::sub2/] + [2] sequence_number $ git checkout master Switched to branch 'master' @@ -67,6 +69,7 @@ $ josh-filter -s :exclude[::sub2/] --reverse master --update refs/heads/hidden [2] :exclude[::sub2/] + [3] sequence_number $ git log --graph --pretty=%s refs/heads/master * empty commit * add sub1/file3 diff --git a/tests/filter/reverse_hide_edit.t b/tests/filter/reverse_hide_edit.t index e3c63db88..404de8d7f 100644 --- a/tests/filter/reverse_hide_edit.t +++ b/tests/filter/reverse_hide_edit.t @@ -16,6 +16,7 @@ $ josh-filter -s :exclude[::sub2/] master --update refs/heads/hidden [1] :exclude[::sub2/] + [2] sequence_number $ git checkout hidden 1> /dev/null Switched to branch 'hidden' $ tree @@ -33,6 +34,7 @@ $ josh-filter -s :exclude[::sub2/] --reverse master --update refs/heads/hidden [1] :exclude[::sub2/] + [2] sequence_number $ git checkout master Switched to branch 'master' diff --git a/tests/filter/reverse_hide_edit_missing_change.t b/tests/filter/reverse_hide_edit_missing_change.t index 8c145d09d..0ec12fe70 100644 --- a/tests/filter/reverse_hide_edit_missing_change.t +++ b/tests/filter/reverse_hide_edit_missing_change.t @@ -29,6 +29,7 @@ $ josh-filter -s :exclude[::sub2/] master --update refs/heads/hidden [1] :exclude[::sub2/] + [2] sequence_number $ git checkout hidden 1> /dev/null Switched to branch 'hidden' $ tree @@ -48,6 +49,7 @@ $ josh-filter -s :exclude[::sub2/] --reverse master --update refs/heads/hidden [1] :exclude[::sub2/] + [2] sequence_number $ git checkout master Switched to branch 'master' diff --git a/tests/filter/reverse_merge.t b/tests/filter/reverse_merge.t index 01a65ca0a..46e369964 100644 --- a/tests/filter/reverse_merge.t +++ b/tests/filter/reverse_merge.t @@ -26,6 +26,7 @@ $ josh-filter -s :exclude[::sub2/] branch1 --update refs/heads/hidden_branch1 [2] :exclude[::sub2/] + [2] sequence_number $ git checkout hidden_branch1 Switched to branch 'hidden_branch1' $ tree @@ -40,6 +41,7 @@ $ josh-filter -s :exclude[::sub2/] master --update refs/heads/hidden_master [3] :exclude[::sub2/] + [3] sequence_number $ git checkout hidden_master Switched to branch 'hidden_master' $ tree @@ -73,6 +75,7 @@ $ josh-filter -s :exclude[::sub2/] --reverse master --update refs/heads/hidden_master [3] :exclude[::sub2/] + [3] sequence_number $ git checkout master Switched to branch 'master' diff --git a/tests/filter/reverse_split.t b/tests/filter/reverse_split.t index 6e9d5c1b0..d5ac7bcef 100644 --- a/tests/filter/reverse_split.t +++ b/tests/filter/reverse_split.t @@ -18,6 +18,7 @@ a = ::*.a :prefix=rest ] + [2] sequence_number $ git checkout filtered 1> /dev/null Switched to branch 'filtered' $ tree @@ -42,6 +43,7 @@ a = ::*.a :prefix=rest ] + [2] sequence_number $ git checkout master Switched to branch 'master' diff --git a/tests/filter/roundtrip_custom_header.t b/tests/filter/roundtrip_custom_header.t index 919e57886..f436cad48 100644 --- a/tests/filter/roundtrip_custom_header.t +++ b/tests/filter/roundtrip_custom_header.t @@ -130,3 +130,5 @@ Write a custom header into the commit (h/t https://github.com/Byron/gitoxide/blo f2fd7b2 (HEAD -> master, re-filtered) second 73007fa initial 7d7c929 initial + 9340c45 Notes added by 'git_note_create' from libgit2 + 56a1fe0 Notes added by 'git_note_create' from libgit2 diff --git a/tests/filter/squash.t b/tests/filter/squash.t index 2760b0f8d..dc30e4dd5 100644 --- a/tests/filter/squash.t +++ b/tests/filter/squash.t @@ -30,6 +30,7 @@ $ josh-filter -s --squash-pattern "refs/tags/*" --update refs/heads/filtered Warning: reference refs/heads/filtered wasn't updated + [5] sequence_number $ git log --graph --decorate --pretty=oneline refs/heads/filtered fatal: ambiguous argument 'refs/heads/filtered': unknown revision or path not in the working tree. @@ -45,6 +46,7 @@ This one tag is an annotated tag, to make sure those are handled as well [1] :squash( 1d69b7d2651f744be3416f2ad526aeccefb99310:"refs/tags/tag_a" ) + [7] sequence_number $ git log --graph --decorate --pretty=oneline refs/heads/filtered * 977cc3ee14c0d6163ba63bd96f4aeedd43916ba7 (tag: filtered/tag_a, filtered) refs/tags/tag_a @@ -73,6 +75,7 @@ This one tag is an annotated tag, to make sure those are handled as well 977cc3ee14c0d6163ba63bd96f4aeedd43916ba7:"refs/tags/filtered/tag_a" ) [4] :author="New Author";"new@e.mail" + [11] sequence_number $ git log --graph --decorate --pretty=oneline refs/heads/filtered * be41caf35896090033cfd103e06aae721a3ce541 (tag: filtered/tag_a, filtered) refs/tags/tag_a @@ -115,6 +118,7 @@ This one tag is an annotated tag, to make sure those are handled as well a68763bdf2f45a44304067954855749e366a5533:"refs/tags/filtered/filtered/tag_a" be41caf35896090033cfd103e06aae721a3ce541:"refs/tags/filtered/tag_a" ) + [16] sequence_number $ git log --graph --pretty=%an:%ae-%cn:%ce refs/heads/filtered * Josh:josh@example.com-New Author:new@e.mail |\ @@ -181,6 +185,7 @@ This one tag is an annotated tag, to make sure those are handled as well be41caf35896090033cfd103e06aae721a3ce541:"refs/tags/filtered/tag_a" ) [6] :author="New Author";"new@e.mail" + [19] sequence_number $ git log --graph --decorate --pretty=oneline refs/heads/filtered * 2826b9a173c7a7d5c83d9ae2614de89c77205d83 (filtered) refs/tags/tag_a diff --git a/tests/filter/squash_empty_initial.t b/tests/filter/squash_empty_initial.t index b597b7f04..af32b7595 100644 --- a/tests/filter/squash_empty_initial.t +++ b/tests/filter/squash_empty_initial.t @@ -37,6 +37,7 @@ $ josh-filter -s --squash-pattern "refs/tags/*" --update refs/heads/filtered Warning: reference refs/heads/filtered wasn't updated + [5] sequence_number $ git log --graph --decorate --pretty=oneline refs/heads/filtered fatal: ambiguous argument 'refs/heads/filtered': unknown revision or path not in the working tree. Use '--' to separate paths from revisions, like this: @@ -50,6 +51,7 @@ [1] :squash( 882f2656a5075936eb37bfefde740e0b453e4479:"refs/tags/tag_a" ) + [7] sequence_number $ git log --graph --decorate --pretty=oneline refs/heads/filtered * 977cc3ee14c0d6163ba63bd96f4aeedd43916ba7 (tag: filtered/tag_a, filtered) refs/tags/tag_a diff --git a/tests/filter/submodule.t b/tests/filter/submodule.t index e9a2188af..c72816b2d 100644 --- a/tests/filter/submodule.t +++ b/tests/filter/submodule.t @@ -25,9 +25,11 @@ $ josh-filter -s :/libs master --update refs/josh/filter/master [1] :/libs + [2] sequence_number $ git ls-tree --name-only -r refs/josh/filter/master $ josh-filter -s c=:/libs master --update refs/josh/filter/master Warning: reference refs/josh/filter/master wasn't updated [1] :/libs [1] :prefix=c + [2] sequence_number $ git ls-tree --name-only -r refs/josh/filter/master diff --git a/tests/filter/subtree_prefix.t b/tests/filter/subtree_prefix.t index dc3455aea..f496e784d 100644 --- a/tests/filter/subtree_prefix.t +++ b/tests/filter/subtree_prefix.t @@ -44,6 +44,7 @@ Rewrite the subtree part of the history $ josh-filter -s ":rev($SUBTREE_TIP:prefix=subtree)" refs/heads/master --update refs/heads/filtered [1] :prefix=subtree [4] :rev(c036f944faafb865e0585e4fa5e005afa0aeea3f:prefix=subtree) + [4] sequence_number $ git log --graph --pretty=%s refs/heads/filtered * subtree edit from main repo @@ -70,6 +71,7 @@ Extract the subtree history [1] :prefix=subtree [4] :/subtree [4] :rev(c036f944faafb865e0585e4fa5e005afa0aeea3f:prefix=subtree) + [7] sequence_number $ git checkout subtree Switched to branch 'subtree' $ cat file2 @@ -83,6 +85,7 @@ Work in the subtree, and sync that back. [1] :prefix=subtree [4] :/subtree [4] :rev(c036f944faafb865e0585e4fa5e005afa0aeea3f:prefix=subtree) + [7] sequence_number $ git log --graph --pretty=%s refs/heads/master * add even more content * subtree edit from main repo @@ -105,6 +108,7 @@ And then re-extract, which should re-construct the same subtree. [1] :prefix=subtree [5] :/subtree [5] :rev(c036f944faafb865e0585e4fa5e005afa0aeea3f:prefix=subtree) + [9] sequence_number $ test $(git rev-parse subtree) = $(git rev-parse subtree2) Simulate a feature branch on the main repo that crosses subtree changes @@ -179,6 +183,7 @@ And finally, sync first from main to sub and then back. [1] :prefix=subtree [9] :/subtree [9] :rev(c036f944faafb865e0585e4fa5e005afa0aeea3f:prefix=subtree) + [17] sequence_number $ git log --graph --pretty=%H:%s refs/heads/master * 6ac0ba56575859cfaacd5818084333e532ffc442:Merge branch 'subtree-sync' into subtree @@ -225,6 +230,7 @@ taken back into the main history. [1] :prefix=subtree [13] :/subtree [13] :rev(c036f944faafb865e0585e4fa5e005afa0aeea3f:prefix=subtree) + [25] sequence_number $ git ls-tree --name-only -r refs/heads/master feature1 feature2 diff --git a/tests/filter/workspace_combine_filter.t b/tests/filter/workspace_combine_filter.t index c27fa8ffb..cabd731e5 100644 --- a/tests/filter/workspace_combine_filter.t +++ b/tests/filter/workspace_combine_filter.t @@ -56,6 +56,7 @@ ] [2] :prefix=x [2] :workspace=ws + [4] sequence_number $ git log --graph --pretty=%s FILTERED_HEAD * add ws @@ -92,6 +93,7 @@ blub = :/sub1 ] [3] :prefix=xyz + [7] sequence_number $ git log --graph --pretty=%s FILTERED_HEAD * add ws diff --git a/tests/filter/workspace_discover.t b/tests/filter/workspace_discover.t index cb76163f8..5401d4889 100644 --- a/tests/filter/workspace_discover.t +++ b/tests/filter/workspace_discover.t @@ -65,6 +65,7 @@ ] [2] :workspace=ws [2] :workspace=ws2 + [9] sequence_number $ cat > workspace.josh < :/sub1::file1 @@ -101,3 +102,4 @@ ] [2] :workspace=ws [2] :workspace=ws2 + [10] sequence_number diff --git a/tests/filter/workspace_exclude.t b/tests/filter/workspace_exclude.t index 8c24a4051..9d20f30bd 100644 --- a/tests/filter/workspace_exclude.t +++ b/tests/filter/workspace_exclude.t @@ -29,6 +29,7 @@ ::sub2/subsub/ ] [2] :workspace=ws + [3] sequence_number $ git log --graph --pretty=%s refs/heads/filtered * add ws @@ -59,6 +60,7 @@ ::sub2/subsub/ ] [2] :workspace=ws + [3] sequence_number $ git checkout master Switched to branch 'master' diff --git a/tests/filter/workspace_implicit_filter.t b/tests/filter/workspace_implicit_filter.t index 784678438..e887c4168 100644 --- a/tests/filter/workspace_implicit_filter.t +++ b/tests/filter/workspace_implicit_filter.t @@ -28,6 +28,7 @@ ::sub2/subsub/ ] [2] :workspace=ws + [3] sequence_number $ git log --graph --pretty=%s refs/josh/master * add ws diff --git a/tests/filter/workspace_modify_chain.t b/tests/filter/workspace_modify_chain.t index 89d9c7b70..6801de9f0 100644 --- a/tests/filter/workspace_modify_chain.t +++ b/tests/filter/workspace_modify_chain.t @@ -29,6 +29,7 @@ [1] :prefix=subsub [2] :workspace=ws [3] :/sub2 + [7] sequence_number $ git log --graph --pretty=%s refs/heads/filtered * add file2 @@ -53,6 +54,7 @@ [1] :prefix=subsub [2] :workspace=ws [3] :/sub2 + [7] sequence_number $ git checkout master Switched to branch 'master' diff --git a/tests/filter/workspace_multiple_globs.t b/tests/filter/workspace_multiple_globs.t index 8f1889275..1440142b9 100644 --- a/tests/filter/workspace_multiple_globs.t +++ b/tests/filter/workspace_multiple_globs.t @@ -33,6 +33,7 @@ b = ::**/file1 ] [2] :workspace=ws + [3] sequence_number $ git log --graph --pretty=%s refs/josh/master * add ws diff --git a/tests/filter/workspace_redirect.t b/tests/filter/workspace_redirect.t index 64458ad6b..32becdfff 100644 --- a/tests/filter/workspace_redirect.t +++ b/tests/filter/workspace_redirect.t @@ -53,16 +53,18 @@ ::sub2/subsub/ ] [3] :workspace=ws + [7] sequence_number $ josh-filter -s :workspace=ws_new master --update refs/heads/filtered_new [1] :prefix=b - [1] :workspace=ws_new [2] :/sub3 [2] :[ a = :/sub1 ::sub2/subsub/ ] + [2] :workspace=ws_new [3] :workspace=ws [5] :exclude[::ws_new] + [7] sequence_number $ git log --graph --pretty=%s refs/heads/filtered * edit ws @@ -188,3 +190,4 @@ [4] :exclude[::ws] [4] :workspace=ws [6] :exclude[::ws_new] + [10] sequence_number diff --git a/tests/filter/workspace_single_file.t b/tests/filter/workspace_single_file.t index 896ac7356..048807017 100644 --- a/tests/filter/workspace_single_file.t +++ b/tests/filter/workspace_single_file.t @@ -46,6 +46,7 @@ ::sub2/subsub/ ] [2] :workspace=ws + [4] sequence_number $ git log --graph --pretty=%s refs/josh/master * add ws @@ -86,6 +87,7 @@ ] [2] :workspace=ws [2] :workspace=ws2 + [4] sequence_number $ git log --graph --pretty=%s refs/josh/master * add ws2 diff --git a/tests/filter/workspace_trailing_slash.t b/tests/filter/workspace_trailing_slash.t index 19146136e..cd3eaf973 100644 --- a/tests/filter/workspace_trailing_slash.t +++ b/tests/filter/workspace_trailing_slash.t @@ -28,6 +28,7 @@ a/b = :/sub2 ] [2] :workspace=ws + [3] sequence_number $ git log --graph --pretty=%s refs/josh/master * add ws @@ -48,6 +49,7 @@ a/b = :/sub2 ] [3] :workspace=ws + [4] sequence_number $ git log --graph --pretty=%s refs/josh/master * add trailing slash diff --git a/tests/filter/workspace_unique.t b/tests/filter/workspace_unique.t index cf20e025c..80ea10baf 100644 --- a/tests/filter/workspace_unique.t +++ b/tests/filter/workspace_unique.t @@ -38,6 +38,7 @@ so the workspace.josh will still appear in the root of the workspace b = ::ws/workspace.josh ] [2] :workspace=ws + [3] sequence_number $ git log --graph --pretty=%s refs/josh/master * add ws diff --git a/tests/proxy/workspace_errors.t b/tests/proxy/workspace_errors.t index c1b4231ce..7918fa6e7 100644 --- a/tests/proxy/workspace_errors.t +++ b/tests/proxy/workspace_errors.t @@ -106,7 +106,7 @@ Error in filter remote: 1 | a/b = :b/sub2 remote: | ^--- remote: | - remote: = expected EOI, filter_group, filter_subdir, filter_nop, filter_presub, filter, filter_noarg, filter_message, filter_rev, filter_join, filter_replace, or filter_squash + remote: = expected EOI, filter_group, filter_subdir, filter_nop, filter_presub, filter, filter_noarg, filter_message, filter_rev, filter_from, filter_concat, filter_join, filter_replace, or filter_squash remote: remote: a/b = :b/sub2 remote: c = :/sub1