Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/src/reference/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ workspace root as well as additional files specified in the ``workspace.josh`` f
### Text replacement **`:replace="regex","replacement"`**
Applies the supplied regular expression to every file in the input tree.

### Signature removal **`:unsign`
The default behaviour of Josh is to copy, if it exsists, the signature of the original commit in
the filtered commit. This makes the signature invalid, but allows a perfect round-trip: josh will be
able to recreate the original commit from the filtered one.

This behaviour might not be desirable, and this filter drops the signatures from the history.

## Pattern filters

The following filters accept a glob like pattern ``X`` that can contain ``*`` to
Expand Down
1 change: 1 addition & 0 deletions josh-proxy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ fn split_changes(
&vec![&parent],
&new_tree,
None,
false,
)?;
changes[i].1 = new_commit;
new_bases.push(new_commit);
Expand Down
26 changes: 26 additions & 0 deletions src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ enum Op {
Squash(Option<std::collections::HashMap<git2::Oid, (String, String, String)>>),
Rev(std::collections::HashMap<git2::Oid, Filter>),
Linear,
Unsign,

RegexReplace(regex::Regex, String),

Expand Down Expand Up @@ -277,6 +278,7 @@ fn spec2(op: &Op) -> String {
format!(":SQUASH={}", s)
}
Op::Linear => ":linear".to_string(),
Op::Unsign => ":unsign".to_string(),
Op::Subdir(path) => format!(":/{}", parse::quote(&path.to_string_lossy())),
Op::File(path) => format!("::{}", parse::quote(&path.to_string_lossy())),
Op::Prefix(path) => format!(":prefix={}", parse::quote(&path.to_string_lossy())),
Expand Down Expand Up @@ -387,6 +389,7 @@ fn apply_to_commit2(
&[],
&commit.tree()?,
None,
true,
))
.transpose()
}
Expand Down Expand Up @@ -467,6 +470,28 @@ fn apply_to_commit2(
))
.transpose();
}
Op::Unsign => {
let parents: Vec<_> = commit.parent_ids().collect();

let filtered_parents: Vec<_> = parents
.iter()
.map(|p| transaction.get(filter, *p))
.collect();
if filtered_parents.iter().any(|p| p.is_none()) {
return Ok(None);
}
let filtered_parents = filtered_parents.iter().map(|p| p.unwrap()).collect();

return Some(history::remove_commit_signature(
commit,
filtered_parents,
commit.tree()?,
transaction,
filter,
None,
))
.transpose();
}
Op::Compose(filters) => {
let filtered = filters
.iter()
Expand Down Expand Up @@ -661,6 +686,7 @@ fn apply2<'a>(
Op::Squash(None) => Ok(tree),
Op::Squash(Some(_)) => Err(josh_error("not applicable to tree")),
Op::Linear => Ok(tree),
Op::Unsign => Ok(tree),
Op::Rev(_) => Err(josh_error("not applicable to tree")),
Op::RegexReplace(regex, replacement) => {
tree::regex_replace(tree.id(), &regex, &replacement, transaction)
Expand Down
1 change: 1 addition & 0 deletions src/filter/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ pub fn invert(filter: Filter) -> JoshResult<Filter> {
let result = match to_op(filter) {
Op::Nop => Some(Op::Nop),
Op::Linear => Some(Op::Nop),
Op::Unsign => Some(Op::Unsign),
Op::Empty => Some(Op::Empty),
Op::Subdir(path) => Some(Op::Prefix(path)),
Op::File(path) => Some(Op::File(path)),
Expand Down
1 change: 1 addition & 0 deletions src/filter/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ fn make_op(args: &[&str]) -> JoshResult<Op> {
["SQUASH"] => Ok(Op::Squash(None)),
["SQUASH", _ids @ ..] => Err(josh_error("SQUASH with ids can't be parsed")),
["linear"] => Ok(Op::Linear),
["unsign"] => Ok(Op::Unsign),
["PATHS"] => Ok(Op::Paths),
#[cfg(feature = "search")]
["INDEX"] => Ok(Op::Index),
Expand Down
51 changes: 30 additions & 21 deletions src/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,8 @@ pub fn rewrite_commit(
parents: &[&git2::Commit],
tree: &git2::Tree,
message: Option<(String, String, String)>,
unsign: bool,
) -> JoshResult<git2::Oid> {
if message == None && base.tree()?.id() == tree.id() && all_equal(base.parents(), parents) {
// Looks like an optimization, but in fact serves to not change the commit in case
// it was signed.
return Ok(base.id());
}

let b = if let Some((message, author, email)) = message {
let a = base.author();
let new_a = git2::Signature::new(&author, &email, &a.when())?;
Expand All @@ -205,7 +200,7 @@ pub fn rewrite_commit(
)?
};

if let Ok((sig, _)) = repo.extract_signature(&base.id(), None) {
if let (false, Ok((sig, _))) = (unsign, repo.extract_signature(&base.id(), None)) {
// Re-create the object with the original signature (which of course does not match any
// more, but this is needed to guarantee perfect round-trips).
let b = b
Expand All @@ -220,20 +215,6 @@ pub fn rewrite_commit(
return Ok(repo.odb()?.write(git2::ObjectType::Commit, &b)?);
}

fn all_equal(a: git2::Parents, b: &[&git2::Commit]) -> bool {
let a: Vec<_> = a.collect();
if a.len() != b.len() {
return false;
}

for (x, y) in b.iter().zip(a.iter()) {
if x.id() != y.id() {
return false;
}
}
true
}

fn find_oldest_similar_commit(
transaction: &cache::Transaction,
filter: filter::Filter,
Expand Down Expand Up @@ -527,6 +508,7 @@ pub fn unapply_filter(
&original_parents_refs,
&new_tree,
None,
false,
)?;

if let Some(ref mut change_ids) = change_ids {
Expand Down Expand Up @@ -560,6 +542,30 @@ fn select_parent_commits<'a>(
}
}

pub fn remove_commit_signature<'a>(
original_commit: &'a git2::Commit,
filtered_parent_ids: Vec<git2::Oid>,
filtered_tree: git2::Tree<'a>,
transaction: &cache::Transaction,
filter: filter::Filter,
message: Option<(String, String, String)>,
) -> JoshResult<git2::Oid> {
let (r, is_new) = create_filtered_commit2(
transaction.repo(),
original_commit,
filtered_parent_ids,
filtered_tree,
message,
true,
)?;

let store = is_new || original_commit.parent_ids().len() != 1;

transaction.insert(filter, original_commit.id(), r, store);

Ok(r)
}

pub fn drop_commit<'a>(
original_commit: &'a git2::Commit,
filtered_parent_ids: Vec<git2::Oid>,
Expand Down Expand Up @@ -591,6 +597,7 @@ pub fn create_filtered_commit<'a>(
filtered_parent_ids,
filtered_tree,
message,
false,
)?;

let store = is_new || original_commit.parent_ids().len() != 1;
Expand All @@ -606,6 +613,7 @@ fn create_filtered_commit2<'a>(
filtered_parent_ids: Vec<git2::Oid>,
filtered_tree: git2::Tree<'a>,
message: Option<(String, String, String)>,
unsign: bool,
) -> JoshResult<(git2::Oid, bool)> {
let filtered_parent_commits: Result<Vec<_>, _> = filtered_parent_ids
.iter()
Expand Down Expand Up @@ -651,6 +659,7 @@ fn create_filtered_commit2<'a>(
&selected_filtered_parent_commits,
&filtered_tree,
message,
unsign,
)?,
true,
))
Expand Down
22 changes: 22 additions & 0 deletions tests/filter/gpgsig.t
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,25 @@ If 0b4cf6c9efbbda1eada39fa9c1d21d2525b027bb shows up then the signature was lost
$ git rev-parse master double-filtered
cb22ebb8e47b109f7add68b1043e561e0db09802
cb22ebb8e47b109f7add68b1043e561e0db09802

Remove the signature, the shas are different.
$ josh-filter :unsign refs/heads/master --update refs/heads/filtered -s
[1] :unsign
$ git rev-parse master filtered
cb22ebb8e47b109f7add68b1043e561e0db09802
0b4cf6c9efbbda1eada39fa9c1d21d2525b027bb
$ josh-filter --reverse :unsign refs/heads/double-filtered --update refs/heads/filtered -s
[1] :unsign
$ git rev-parse master double-filtered
cb22ebb8e47b109f7add68b1043e561e0db09802
cb22ebb8e47b109f7add68b1043e561e0db09802

Round trip does not work but reversed works since the commit exists
$ josh-filter :prefix=extra:unsign refs/heads/master --update refs/heads/filtered
$ josh-filter :/extra refs/heads/filtered --update refs/heads/double-filtered
$ git branch reversed
$ josh-filter --reverse :prefix=extra:unsign refs/heads/reversed --update refs/heads/filtered
$ git rev-parse master double-filtered reversed
cb22ebb8e47b109f7add68b1043e561e0db09802
0b4cf6c9efbbda1eada39fa9c1d21d2525b027bb
cb22ebb8e47b109f7add68b1043e561e0db09802