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
8 changes: 8 additions & 0 deletions docs/src/reference/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ All subdirectories matching the pattern in arbitarily deep subdirectories of the
### Match nested files **`::**/X`**
All files matching the pattern in arbitarily deep subdirectories of the input

## History filters

These filter do not modify git trees, but instead only operate on the commit graph.

### Linearize history **:linear**
Produce a filtered history that does not contain any merge commits. This is done by
simply dropping all parents except the first on every commit.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a few words on a typical use case?

Filter order matters
--------------------

Expand Down
24 changes: 23 additions & 1 deletion src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ enum Op {
Nop,
Empty,
Fold,
Squash,
Paths,
Squash,
Linear,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alphabetical order?


#[cfg(feature = "search")]
Index,
Expand Down Expand Up @@ -185,6 +186,7 @@ fn spec2(op: &Op) -> String {
Op::Index => ":INDEX".to_string(),
Op::Fold => ":FOLD".to_string(),
Op::Squash => ":SQUASH".to_string(),
Op::Linear => ":linear".to_string(),
Op::Subdir(path) => format!(":/{}", path.to_string_lossy()),
Op::File(path) => format!("::{}", path.to_string_lossy()),
Op::Prefix(path) => format!(":prefix={}", path.to_string_lossy()),
Expand Down Expand Up @@ -274,6 +276,24 @@ fn apply_to_commit2(
Op::Squash => {
return Some(history::rewrite_commit(repo, commit, &[], &commit.tree()?)).transpose()
}
Op::Linear => {
let p: Vec<_> = commit.parents().collect();
if p.len() == 0 {
return Ok(Some(commit.id()));
}
let parent = some_or!(apply_to_commit2(op, &p[0], transaction)?, {
return Ok(None);
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont understand this case

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning Ok(None) from this function means that we cannot produce filtered commit right now because one of the parents was not filtered yet. In order to not recuruse here to filter the parent we return and will get called again at a later point when the parent has been filtered.


let parent_commit = repo.find_commit(parent)?;
return Some(history::rewrite_commit(
repo,
commit,
&[&parent_commit],
&commit.tree()?,
))
.transpose();
}
_ => {
if let Some(oid) = transaction.get(filter, commit.id()) {
return Ok(Some(oid));
Expand Down Expand Up @@ -457,6 +477,7 @@ fn apply2<'a>(
Op::Empty => return Ok(tree::empty(repo)),
Op::Fold => Ok(tree),
Op::Squash => Ok(tree),
Op::Linear => Ok(tree),

Op::Glob(pattern) => {
let pattern = glob::Pattern::new(pattern)?;
Expand Down Expand Up @@ -555,6 +576,7 @@ fn unapply2<'a>(
) -> JoshResult<git2::Tree<'a>> {
return match op {
Op::Nop => Ok(tree),
Op::Linear => Ok(tree),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see that it works, but I don't understand why. How does it find the parents after this?

Copy link
Member Author

@christian-schilling christian-schilling May 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic 🪄

Just like with any other filter: When we need to find the original commits we search them by traversing the commit graph until we find apply(sha) == filtered_sha.

Many filters drop commits, how exactly does not matter for the algorithm to work.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. Thinking about it more, this didn't confuse me on other filters, but for some reason I got stuck on this one :)

Op::Empty => Ok(parent_tree),

Op::Chain(a, b) => {
Expand Down
1 change: 1 addition & 0 deletions src/filter/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ fn make_op(args: &[&str]) -> JoshResult<Op> {
"#
))),
["SQUASH"] => Ok(Op::Squash),
["linear"] => Ok(Op::Linear),
["PATHS"] => Ok(Op::Paths),
#[cfg(feature = "search")]
["INDEX"] => Ok(Op::Index),
Expand Down
73 changes: 73 additions & 0 deletions tests/filter/linear.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
$ git init -q 1> /dev/null

$ echo contents1 > file1
$ git add .
$ git commit -m "add file1" 1> /dev/null

$ git log --graph --pretty=%s
* add file1

$ git checkout -b branch2
Switched to a new branch 'branch2'

$ echo contents2 > file1
$ git add .
$ git commit -m "mod file1" 1> /dev/null

$ git checkout master
Switched to branch 'master'

$ echo contents3 > file2
$ git add .
$ git commit -m "add file2" 1> /dev/null

$ git merge -q branch2 --no-ff

$ git log --graph --pretty=%s
* Merge branch 'branch2'
|\
| * mod file1
* | add file2
|/
* add file1

$ josh-filter -s :linear refs/heads/master --update refs/heads/filtered

$ git log --graph --pretty=%s refs/heads/filtered
* Merge branch 'branch2'
* add file2
* add file1

$ git ls-tree --name-only -r refs/heads/filtered
file1
file2

$ git checkout filtered
Switched to branch 'filtered'

$ echo contents4 > file2
$ git add .
$ git commit -m "mod file2" 1> /dev/null

$ git log --graph --pretty=%s refs/heads/filtered
* mod file2
* Merge branch 'branch2'
* add file2
* add file1

$ josh-filter -s :linear refs/heads/master --update refs/heads/filtered --reverse

$ git log --graph --pretty=%s refs/heads/master
* mod file2
* Merge branch 'branch2'
|\
| * mod file1
* | add file2
|/
* add file1

$ git log --graph --pretty=%s refs/heads/filtered
* mod file2
* Merge branch 'branch2'
* add file2
* add file1