Skip to content

Commit 8d80230

Browse files
Add :linear filter (#741)
Change-Id: linear-filter
1 parent 81930c6 commit 8d80230

File tree

4 files changed

+105
-1
lines changed

4 files changed

+105
-1
lines changed

docs/src/reference/filters.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ All subdirectories matching the pattern in arbitarily deep subdirectories of the
7474
### Match nested files **`::**/X`**
7575
All files matching the pattern in arbitarily deep subdirectories of the input
7676

77+
## History filters
78+
79+
These filter do not modify git trees, but instead only operate on the commit graph.
80+
81+
### Linearize history **:linear**
82+
Produce a filtered history that does not contain any merge commits. This is done by
83+
simply dropping all parents except the first on every commit.
84+
7785
Filter order matters
7886
--------------------
7987

src/filter/mod.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ enum Op {
6161
Nop,
6262
Empty,
6363
Fold,
64-
Squash,
6564
Paths,
65+
Squash,
66+
Linear,
6667

6768
#[cfg(feature = "search")]
6869
Index,
@@ -185,6 +186,7 @@ fn spec2(op: &Op) -> String {
185186
Op::Index => ":INDEX".to_string(),
186187
Op::Fold => ":FOLD".to_string(),
187188
Op::Squash => ":SQUASH".to_string(),
189+
Op::Linear => ":linear".to_string(),
188190
Op::Subdir(path) => format!(":/{}", path.to_string_lossy()),
189191
Op::File(path) => format!("::{}", path.to_string_lossy()),
190192
Op::Prefix(path) => format!(":prefix={}", path.to_string_lossy()),
@@ -274,6 +276,24 @@ fn apply_to_commit2(
274276
Op::Squash => {
275277
return Some(history::rewrite_commit(repo, commit, &[], &commit.tree()?)).transpose()
276278
}
279+
Op::Linear => {
280+
let p: Vec<_> = commit.parents().collect();
281+
if p.len() == 0 {
282+
return Ok(Some(commit.id()));
283+
}
284+
let parent = some_or!(apply_to_commit2(op, &p[0], transaction)?, {
285+
return Ok(None);
286+
});
287+
288+
let parent_commit = repo.find_commit(parent)?;
289+
return Some(history::rewrite_commit(
290+
repo,
291+
commit,
292+
&[&parent_commit],
293+
&commit.tree()?,
294+
))
295+
.transpose();
296+
}
277297
_ => {
278298
if let Some(oid) = transaction.get(filter, commit.id()) {
279299
return Ok(Some(oid));
@@ -457,6 +477,7 @@ fn apply2<'a>(
457477
Op::Empty => return Ok(tree::empty(repo)),
458478
Op::Fold => Ok(tree),
459479
Op::Squash => Ok(tree),
480+
Op::Linear => Ok(tree),
460481

461482
Op::Glob(pattern) => {
462483
let pattern = glob::Pattern::new(pattern)?;
@@ -555,6 +576,7 @@ fn unapply2<'a>(
555576
) -> JoshResult<git2::Tree<'a>> {
556577
return match op {
557578
Op::Nop => Ok(tree),
579+
Op::Linear => Ok(tree),
558580
Op::Empty => Ok(parent_tree),
559581

560582
Op::Chain(a, b) => {

src/filter/parse.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ fn make_op(args: &[&str]) -> JoshResult<Op> {
3030
"#
3131
))),
3232
["SQUASH"] => Ok(Op::Squash),
33+
["linear"] => Ok(Op::Linear),
3334
["PATHS"] => Ok(Op::Paths),
3435
#[cfg(feature = "search")]
3536
["INDEX"] => Ok(Op::Index),

tests/filter/linear.t

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
$ git init -q 1> /dev/null
2+
3+
$ echo contents1 > file1
4+
$ git add .
5+
$ git commit -m "add file1" 1> /dev/null
6+
7+
$ git log --graph --pretty=%s
8+
* add file1
9+
10+
$ git checkout -b branch2
11+
Switched to a new branch 'branch2'
12+
13+
$ echo contents2 > file1
14+
$ git add .
15+
$ git commit -m "mod file1" 1> /dev/null
16+
17+
$ git checkout master
18+
Switched to branch 'master'
19+
20+
$ echo contents3 > file2
21+
$ git add .
22+
$ git commit -m "add file2" 1> /dev/null
23+
24+
$ git merge -q branch2 --no-ff
25+
26+
$ git log --graph --pretty=%s
27+
* Merge branch 'branch2'
28+
|\
29+
| * mod file1
30+
* | add file2
31+
|/
32+
* add file1
33+
34+
$ josh-filter -s :linear refs/heads/master --update refs/heads/filtered
35+
36+
$ git log --graph --pretty=%s refs/heads/filtered
37+
* Merge branch 'branch2'
38+
* add file2
39+
* add file1
40+
41+
$ git ls-tree --name-only -r refs/heads/filtered
42+
file1
43+
file2
44+
45+
$ git checkout filtered
46+
Switched to branch 'filtered'
47+
48+
$ echo contents4 > file2
49+
$ git add .
50+
$ git commit -m "mod file2" 1> /dev/null
51+
52+
$ git log --graph --pretty=%s refs/heads/filtered
53+
* mod file2
54+
* Merge branch 'branch2'
55+
* add file2
56+
* add file1
57+
58+
$ josh-filter -s :linear refs/heads/master --update refs/heads/filtered --reverse
59+
60+
$ git log --graph --pretty=%s refs/heads/master
61+
* mod file2
62+
* Merge branch 'branch2'
63+
|\
64+
| * mod file1
65+
* | add file2
66+
|/
67+
* add file1
68+
69+
$ git log --graph --pretty=%s refs/heads/filtered
70+
* mod file2
71+
* Merge branch 'branch2'
72+
* add file2
73+
* add file1

0 commit comments

Comments
 (0)