diff --git a/josh-core/src/filter/mod.rs b/josh-core/src/filter/mod.rs index a8ea2f10d..d6fc34b69 100644 --- a/josh-core/src/filter/mod.rs +++ b/josh-core/src/filter/mod.rs @@ -321,6 +321,10 @@ enum Op { Prefix(std::path::PathBuf), Subdir(std::path::PathBuf), Workspace(std::path::PathBuf), + #[cfg(feature = "incubating")] + Lookup(std::path::PathBuf), + #[cfg(feature = "incubating")] + Lookup2(git2::Oid), Stored(std::path::PathBuf), Pattern(String), @@ -599,6 +603,14 @@ fn spec2(op: &Op) -> String { Op::Workspace(path) => { format!(":workspace={}", parse::quote_if(&path.to_string_lossy())) } + #[cfg(feature = "incubating")] + Op::Lookup(path) => { + format!(":lookup={}", parse::quote_if(&path.to_string_lossy())) + } + #[cfg(feature = "incubating")] + Op::Lookup2(oid) => { + format!(":lookup2={}", oid.to_string()) + } Op::Stored(path) => { format!(":+{}", parse::quote_if(&path.to_string_lossy())) } @@ -926,6 +938,71 @@ fn apply_to_commit2( apply(transaction, nf, Apply::from_commit(commit)?)? } + #[cfg(feature = "incubating")] + Op::Lookup(lookup_path) => { + let lookup_commit = if let Some(lookup_commit) = + apply_to_commit2(&Op::Subdir(lookup_path.clone()), &commit, transaction)? + { + lookup_commit + } else { + return Ok(None); + }; + + let op = Op::Lookup2(lookup_commit); + + if let Some(start) = transaction.get(to_filter(op), commit.id()) { + transaction.insert(filter, commit.id(), start, true); + return Ok(Some(start)); + } else { + return Ok(None); + } + } + + #[cfg(feature = "incubating")] + Op::Lookup2(lookup_commit_id) => { + let lookup_commit = repo.find_commit(*lookup_commit_id)?; + for parent in lookup_commit.parents() { + let lookup_tree = lookup_commit.tree_id(); + let cw = get_filter( + transaction, + &repo.find_tree(lookup_tree)?, + &std::path::PathBuf::new().join(commit.id().to_string()), + ); + if cw != filter::empty() { + if let Some(start) = + apply_to_commit2(&Op::Lookup2(parent.id()), &commit, transaction)? + { + transaction.insert(filter, commit.id(), start, true); + return Ok(Some(start)); + } else { + return Ok(None); + } + } + break; + } + let lookup_tree = lookup_commit.tree_id(); + let cw = get_filter( + transaction, + &repo.find_tree(lookup_tree)?, + &std::path::PathBuf::new().join(commit.id().to_string()), + ); + + if cw == filter::empty() { + // FIXME empty filter or no entry in table? + for parent in commit.parents() { + if let Some(start) = apply_to_commit2(&op, &parent, transaction)? { + transaction.insert(filter, commit.id(), start, true); + return Ok(Some(start)); + } else { + return Ok(None); + } + } + return Ok(None); + } + + Apply::from_commit(commit)? + .with_tree(apply(transaction, cw, Apply::from_commit(commit)?)?.into_tree()) + } Op::Squash(Some(ids)) => { if let Some(sq) = ids.get(&LazyRef::Resolved(commit.id())) { let oid = if let Some(oid) = @@ -1717,6 +1794,8 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos } } Op::Pin(_) => Ok(x), + #[cfg(feature = "incubating")] + Op::Lookup(_) | Op::Lookup2(_) => Err(josh_error("not applicable to tree")), } } diff --git a/josh-core/src/filter/parse.rs b/josh-core/src/filter/parse.rs index 5908649c2..07c8959b4 100644 --- a/josh-core/src/filter/parse.rs +++ b/josh-core/src/filter/parse.rs @@ -10,6 +10,8 @@ fn make_op(args: &[&str]) -> JoshResult { ["author", author, email] => Ok(Op::Author(author.to_string(), email.to_string())), ["committer", author, email] => Ok(Op::Committer(author.to_string(), email.to_string())), ["workspace", arg] => Ok(Op::Workspace(Path::new(arg).to_owned())), + #[cfg(feature = "incubating")] + ["lookup", arg] => Ok(Op::Lookup(Path::new(arg).to_owned())), ["prefix"] => Err(josh_error(indoc!( r#" Filter ":prefix" requires an argument. diff --git a/josh-core/src/filter/persist.rs b/josh-core/src/filter/persist.rs index aa0745dab..9157bd6a3 100644 --- a/josh-core/src/filter/persist.rs +++ b/josh-core/src/filter/persist.rs @@ -325,6 +325,16 @@ impl InMemoryBuilder { let params_tree = self.build_str_params(&[hook.as_ref()]); push_tree_entries(&mut entries, [("hook", params_tree)]); } + #[cfg(feature = "incubating")] + Op::Lookup(path) => { + let params_tree = self.build_str_params(&[path.to_string_lossy().as_ref()]); + push_tree_entries(&mut entries, [("lookup", params_tree)]); + } + #[cfg(feature = "incubating")] + Op::Lookup2(oid) => { + let params_tree = self.build_str_params(&[oid.to_string().as_ref()]); + push_tree_entries(&mut entries, [("lookup2", params_tree)]); + } } let tree = gix_object::Tree { entries }; @@ -606,6 +616,32 @@ fn from_tree2(repo: &git2::Repository, tree_oid: git2::Oid) -> JoshResult { let path = std::str::from_utf8(path_blob.content())?; Ok(Op::Stored(std::path::PathBuf::from(path))) } + #[cfg(feature = "incubating")] + "lookup" => { + let inner = repo.find_tree(entry.id())?; + let path_blob = repo.find_blob( + inner + .get_name("0") + .ok_or_else(|| josh_error("lookup: missing path"))? + .id(), + )?; + let path = std::str::from_utf8(path_blob.content())?; + Ok(Op::Lookup(std::path::PathBuf::from(path))) + } + #[cfg(feature = "incubating")] + "lookup2" => { + let inner = repo.find_tree(entry.id())?; + let oid_blob = repo.find_blob( + inner + .get_name("0") + .ok_or_else(|| josh_error("lookup2: missing oid"))? + .id(), + )?; + let oid_str = std::str::from_utf8(oid_blob.content())?; + let oid = git2::Oid::from_str(oid_str) + .map_err(|e| josh_error(&format!("lookup2: invalid oid: {}", e)))?; + Ok(Op::Lookup2(oid)) + } "compose" => { let compose_tree = repo.find_tree(entry.id())?; let mut filters = Vec::new(); diff --git a/tests/experimental/lookup.t b/tests/experimental/lookup.t new file mode 100644 index 000000000..efb64f4b6 --- /dev/null +++ b/tests/experimental/lookup.t @@ -0,0 +1,117 @@ + $ export TERM=dumb + $ export RUST_LOG_STYLE=never + + $ git init -q real_repo 1> /dev/null + $ cd real_repo + + $ mkdir sub1 + $ echo contents1 > sub1/file1 + $ git add sub1 + $ git commit -m "add file1" 1> /dev/null + + $ mkdir sub1 + mkdir: cannot create directory 'sub1': File exists + [1] + $ echo contents2 > sub1/file2 + $ git add sub1 + $ git commit -m "add file2" 1> /dev/null + + $ git log --graph --pretty=%H + * 81b10fb4984d20142cd275b89c91c346e536876a + * bb282e9cdc1b972fffd08fd21eead43bc0c83cb8 + + $ mkdir table + $ echo ":prefix=x" > table/81b10fb4984d20142cd275b89c91c346e536876a + $ echo ":prefix=y" > table/bb282e9cdc1b972fffd08fd21eead43bc0c83cb8 + $ git add table + $ git commit -m "add lookup table" 1> /dev/null + + + $ echo contents3 > sub1/file3 + $ git add sub1 + $ git commit -m "add file3" 1> /dev/null + + $ git log --graph --pretty=%H + * 26e4c43675b985689e280bc42264a9226af76943 + * 14c74c5eca73952b36d736034b388832748c49d6 + * 81b10fb4984d20142cd275b89c91c346e536876a + * bb282e9cdc1b972fffd08fd21eead43bc0c83cb8 + + $ josh-filter -s ":lookup=table" --update refs/heads/filtered + [1] :lookup=table + [2] :/table + [4] :lookup2=4880528e9d57aa5efc925e120a8077bfa37d778d + + $ git log refs/heads/filtered --graph --pretty=%s + * add file2 + * add file1 + $ git diff ${EMPTY_TREE}..refs/heads/filtered + diff --git a/x/sub1/file1 b/x/sub1/file1 + new file mode 100644 + index 0000000..a024003 + --- /dev/null + +++ b/x/sub1/file1 + @@ -0,0 +1 @@ + +contents1 + diff --git a/x/sub1/file2 b/x/sub1/file2 + new file mode 100644 + index 0000000..6b46faa + --- /dev/null + +++ b/x/sub1/file2 + @@ -0,0 +1 @@ + +contents2 + $ git diff ${EMPTY_TREE}..refs/heads/filtered~1 + diff --git a/y/sub1/file1 b/y/sub1/file1 + new file mode 100644 + index 0000000..a024003 + --- /dev/null + +++ b/y/sub1/file1 + @@ -0,0 +1 @@ + +contents1 + + $ echo ":prefix=z" > table/14c74c5eca73952b36d736034b388832748c49d6 + $ echo ":prefix=z" > table/26e4c43675b985689e280bc42264a9226af76943 + $ git add table + $ git commit -m "mod lookup table" 1> /dev/null + $ tree table + table + |-- 14c74c5eca73952b36d736034b388832748c49d6 + |-- 26e4c43675b985689e280bc42264a9226af76943 + |-- 81b10fb4984d20142cd275b89c91c346e536876a + `-- bb282e9cdc1b972fffd08fd21eead43bc0c83cb8 + + 1 directory, 4 files + + $ josh-filter -s ":lookup=table" --update refs/heads/filtered + Warning: reference refs/heads/filtered wasn't updated + [2] :lookup=table + [3] :/table + [4] :lookup2=4880528e9d57aa5efc925e120a8077bfa37d778d + [5] :lookup2=ed934c124e28c83270d9cfbb011f3ceb46c0f69e + $ git log refs/heads/filtered --graph --pretty=%s + * add file2 + * add file1 + + $ git diff ${EMPTY_TREE}..refs/heads/filtered + diff --git a/x/sub1/file1 b/x/sub1/file1 + new file mode 100644 + index 0000000..a024003 + --- /dev/null + +++ b/x/sub1/file1 + @@ -0,0 +1 @@ + +contents1 + diff --git a/x/sub1/file2 b/x/sub1/file2 + new file mode 100644 + index 0000000..6b46faa + --- /dev/null + +++ b/x/sub1/file2 + @@ -0,0 +1 @@ + +contents2 + $ git diff ${EMPTY_TREE}..refs/heads/filtered~1 + diff --git a/y/sub1/file1 b/y/sub1/file1 + new file mode 100644 + index 0000000..a024003 + --- /dev/null + +++ b/y/sub1/file1 + @@ -0,0 +1 @@ + +contents1