Skip to content

Commit d39a4d0

Browse files
christian-schillingLMG
authored andcommitted
Enable pushing partial merge commits
While merges of branches that where made in a filtered repo where supported for a long time. This allows merges of branches that where created on different versions of the canonical history. The ambiguity is resolved by picking the parent that is a descendant of the target branch tip.
1 parent 2b5ec99 commit d39a4d0

File tree

5 files changed

+204
-18
lines changed

5 files changed

+204
-18
lines changed

josh-proxy/src/lib.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,8 @@ pub fn process_repo_update(repo_update: RepoUpdate) -> josh::JoshResult<String>
155155
josh::UnapplyResult::BranchDoesNotExist => {
156156
return Err(josh::josh_error("branch does not exist on remote"));
157157
}
158-
josh::UnapplyResult::RejectMerge(parent_count) => {
159-
return Err(josh::josh_error(&format!(
160-
"rejecting merge with {} parents",
161-
parent_count
162-
)));
158+
josh::UnapplyResult::RejectMerge(msg) => {
159+
return Err(josh::josh_error(&msg));
163160
}
164161
josh::UnapplyResult::RejectAmend(msg) => {
165162
return Err(josh::josh_error(&format!(

src/bin/josh-filter.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,10 @@ fn run_filter(args: Vec<String>) -> josh::JoshResult<i32> {
313313
josh::UnapplyResult::Done(rewritten) => {
314314
repo.reference(&src, rewritten, true, "unapply_filter")?;
315315
}
316+
josh::UnapplyResult::RejectMerge(msg) => {
317+
println!("{}", msg);
318+
return Ok(1);
319+
}
316320
_ => {
317321
return Ok(1);
318322
}

src/history.rs

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ pub fn unapply_filter(
281281

282282
let commit_message = module_commit.summary().unwrap_or("NO COMMIT MESSAGE");
283283

284-
let new_trees: JoshResult<std::collections::HashSet<_>> = {
284+
let new_trees: JoshResult<Vec<_>> = {
285285
let s = tracing::span!(
286286
tracing::Level::TRACE,
287287
"unapply filter",
@@ -311,27 +311,57 @@ pub fn unapply_filter(
311311
}
312312
};
313313

314-
let new_tree = match new_trees.len() {
315-
1 => transaction
316-
.repo()
317-
.find_tree(*new_trees.iter().next().ok_or(josh_error("iter.next"))?)?,
314+
let mut dedup = new_trees.clone();
315+
dedup.sort();
316+
dedup.dedup();
317+
318+
let new_tree = match dedup.len() {
319+
// The normal case: Either there was only one parent or all of them where the same
320+
// outside of the current filter in which case they collapse into one tree and that
321+
// is the one we pick
322+
1 => transaction.repo().find_tree(new_trees[0])?,
323+
324+
// 0 means the history is unrelated. Pushing it will fail if we are not
325+
// dealing with either a force push or a push with the "merge" option set.
318326
0 => {
319327
tracing::debug!("unrelated history");
320-
// 0 means the history is unrelated. Pushing it will fail if we are not
321-
// dealing with either a force push or a push with the "merge" option set.
322328
filter::unapply(
323329
transaction,
324330
filterobj,
325331
tree,
326332
filter::tree::empty(transaction.repo()),
327333
)?
328334
}
335+
336+
// This will typically be parent_count == 2 and mean we are dealing with a merge
337+
// where the parents have differences outside of the filter. This is only possible
338+
// if one of the parents is a descendant of the target branch and the other is not.
339+
// In that case pick the tree of the one that is a descendant.
329340
parent_count => {
330-
// This is a merge commit where the parents in the upstream repo
331-
// have differences outside of the current filter.
332-
// It is unclear what base tree to pick in this case.
333-
tracing::warn!("rejecting merge");
334-
return Ok(UnapplyResult::RejectMerge(parent_count));
341+
let mut tid = git2::Oid::zero();
342+
for i in 0..parent_count {
343+
if transaction
344+
.repo()
345+
.graph_descendant_of(original_parents_refs[i].id(), original_target)?
346+
{
347+
tid = new_trees[i];
348+
break;
349+
}
350+
}
351+
352+
if tid != git2::Oid::zero() {
353+
transaction.repo().find_tree(tid)?
354+
} else {
355+
// This used to be our only fallback for the parent_count > 1 case.
356+
// It should never happen anymore.
357+
tracing::warn!("rejecting merge");
358+
let msg = format!(
359+
"rejecting merge with {} parents:\n{:?}",
360+
parent_count,
361+
module_commit.summary().unwrap_or_default()
362+
);
363+
return Ok(UnapplyResult::RejectMerge(msg));
364+
}
335365
}
336366
};
337367

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub mod shell;
4646
#[derive(Clone)]
4747
pub enum UnapplyResult {
4848
Done(git2::Oid),
49-
RejectMerge(usize),
49+
RejectMerge(String),
5050
RejectAmend(String),
5151
BranchDoesNotExist,
5252
}

tests/filter/ambigous_merge.t

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
$ export TESTTMP=${PWD}
2+
3+
$ cd ${TESTTMP}
4+
$ git init real_repo 1> /dev/null
5+
$ cd real_repo
6+
7+
$ mkdir sub2
8+
$ echo contents1 > sub2/file2
9+
$ git add sub2
10+
$ git commit -m "add sub2/file2" 1> /dev/null
11+
12+
$ mkdir sub1
13+
$ echo contents1 > sub1/file1
14+
$ git add sub1
15+
$ git commit -m "add sub1/file1" 1> /dev/null
16+
$ git branch branch1
17+
18+
$ echo contents1 > sub1/file2
19+
$ echo contents2 > sub2/fileX
20+
$ git add .
21+
$ git commit -m "add sub1/file2 sub2/fileX" 1> /dev/null
22+
23+
$ git log --graph --oneline --decorate
24+
* 0d0a43d (HEAD -> master) add sub1/file2 sub2/fileX
25+
* 8a87424 (branch1) add sub1/file1
26+
* 4d74643 add sub2/file2
27+
28+
$ git checkout branch1
29+
Switched to branch 'branch1'
30+
$ echo contents3 > sub2/fileY
31+
$ echo contents6 > sub1/fileY
32+
$ git add .
33+
$ git commit -m "add sub_/fileY" 1> /dev/null
34+
$ git log --graph --oneline --decorate
35+
* 96cc30d (HEAD -> branch1) add sub_/fileY
36+
* 8a87424 add sub1/file1
37+
* 4d74643 add sub2/file2
38+
39+
$ josh-filter -s ::sub1/ branch1 --update refs/heads/hidden_branch1
40+
[2] :prefix=sub1
41+
[3] :/sub1
42+
$ git checkout hidden_branch1
43+
Switched to branch 'hidden_branch1'
44+
$ git log --graph --oneline --decorate
45+
* 81a8353 (HEAD -> hidden_branch1) add sub_/fileY
46+
* 7671c2a add sub1/file1
47+
$ echo contents3 > sub1/file3
48+
$ git add .
49+
$ git commit -m "add sub1/file3" 1> /dev/null
50+
51+
$ git checkout master
52+
Switched to branch 'master'
53+
54+
$ josh-filter -s ::sub1/ master --update refs/heads/hidden_master
55+
[3] :prefix=sub1
56+
[4] :/sub1
57+
$ git checkout hidden_master
58+
Switched to branch 'hidden_master'
59+
$ git log --graph --oneline --decorate
60+
* 586737e (HEAD -> hidden_master) add sub1/file2 sub2/fileX
61+
* 7671c2a add sub1/file1
62+
$ echo contents4 > sub1/file4
63+
$ git add sub1/file4
64+
$ git commit -m "add sub1/file4" 1> /dev/null
65+
66+
$ git log hidden_master --graph --oneline --decorate
67+
* 6f816ed (HEAD -> hidden_master) add sub1/file4
68+
* 586737e add sub1/file2 sub2/fileX
69+
* 7671c2a add sub1/file1
70+
$ git log hidden_branch1 --graph --oneline --decorate
71+
* 24a7e40 (hidden_branch1) add sub1/file3
72+
* 81a8353 add sub_/fileY
73+
* 7671c2a add sub1/file1
74+
75+
$ git merge hidden_branch1 --no-ff
76+
Merge made by the 'recursive' strategy.
77+
sub1/file3 | 1 +
78+
sub1/fileY | 1 +
79+
2 files changed, 2 insertions(+)
80+
create mode 100644 sub1/file3
81+
create mode 100644 sub1/fileY
82+
$ git log --graph --oneline
83+
* 2fcb6a4 Merge branch 'hidden_branch1' into hidden_master
84+
|\
85+
| * 24a7e40 add sub1/file3
86+
| * 81a8353 add sub_/fileY
87+
* | 6f816ed add sub1/file4
88+
* | 586737e add sub1/file2 sub2/fileX
89+
|/
90+
* 7671c2a add sub1/file1
91+
92+
$ josh-filter -s ::sub1/ --reverse master --update refs/heads/hidden_master
93+
[3] :prefix=sub1
94+
[4] :/sub1
95+
96+
$ git checkout master
97+
Switched to branch 'master'
98+
$ git status
99+
On branch master
100+
nothing to commit, working tree clean
101+
102+
$ tree
103+
.
104+
|-- sub1
105+
| |-- file1
106+
| |-- file2
107+
| |-- file3
108+
| |-- file4
109+
| `-- fileY
110+
`-- sub2
111+
|-- file2
112+
`-- fileX
113+
114+
2 directories, 7 files
115+
116+
$ git log --graph --oneline --decorate master
117+
* 8cbae19 (HEAD -> master) Merge branch 'hidden_branch1' into hidden_master
118+
|\
119+
| * 7d3be00 add sub1/file3
120+
| * 5e05ab3 add sub_/fileY
121+
* | 4428b57 add sub1/file4
122+
* | 0d0a43d add sub1/file2 sub2/fileX
123+
|/
124+
* 8a87424 add sub1/file1
125+
* 4d74643 add sub2/file2
126+
$ git log --graph --oneline --decorate branch1
127+
* 96cc30d (branch1) add sub_/fileY
128+
* 8a87424 add sub1/file1
129+
* 4d74643 add sub2/file2
130+
131+
$ git diff 4428b57..8cbae19 --stat
132+
sub1/file3 | 1 +
133+
sub1/fileY | 1 +
134+
2 files changed, 2 insertions(+)
135+
$ git diff 7d3be00..8cbae19 --stat
136+
sub1/file2 | 1 +
137+
sub1/file4 | 1 +
138+
sub2/fileX | 1 +
139+
3 files changed, 3 insertions(+)
140+
$ git diff 5e05ab3..7d3be00 --stat
141+
sub1/file3 | 1 +
142+
1 file changed, 1 insertion(+)
143+
$ git diff 5e05ab3 --stat
144+
sub1/file2 | 1 +
145+
sub1/file3 | 1 +
146+
sub1/file4 | 1 +
147+
sub2/fileX | 1 +
148+
4 files changed, 4 insertions(+)
149+
$ git diff 4428b57..7d3be00 --stat
150+
sub1/file2 | 1 -
151+
sub1/file3 | 1 +
152+
sub1/file4 | 1 -
153+
sub1/fileY | 1 +
154+
sub2/fileX | 1 -
155+
5 files changed, 2 insertions(+), 3 deletions(-)

0 commit comments

Comments
 (0)