Skip to content

Commit 008bdce

Browse files
author
Evgenii Pecherkin
committed
Support merge-base
1 parent c8d1012 commit 008bdce

File tree

4 files changed

+246
-0
lines changed

4 files changed

+246
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ And here are the operations that will need to write to your git repository.
215215
g.merge(g.branch('master'))
216216
g.merge([branch1, branch2])
217217

218+
g.merge_base('branch1', 'branch2')
219+
218220
r = g.add_remote(name, uri) # Git::Remote
219221
r = g.add_remote(name, Git::Base) # Git::Remote
220222

lib/git/base/factory.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ def tag(tag_name)
6868
Git::Object.new(self, tag_name, 'tag', true)
6969
end
7070

71+
# Find as good common ancestors as possible for a merge
72+
# example: g.merge_base('master', 'some_branch', 'some_sha', octopus: true)
73+
# returns Array<Git::Object::Commit>
74+
def merge_base(*args)
75+
shas = self.lib.merge_base(*args)
76+
shas.map { |sha| gcommit(sha) }
77+
end
78+
7179
end
7280

7381
end

lib/git/lib.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,21 @@ def merge(branch, message = nil)
660660
command('merge', arr_opts)
661661
end
662662

663+
def merge_base(*args)
664+
opts = args.last.is_a?(Hash) ? args.pop : {}
665+
666+
arg_opts = []
667+
668+
arg_opts << '--octopus' if opts[:octopus]
669+
arg_opts << '--independent' if opts[:independent]
670+
arg_opts << '--fork-point' if opts[:fork_point]
671+
arg_opts << '--all' if opts[:all]
672+
673+
arg_opts += args
674+
675+
command('merge-base', arg_opts).lines.map(&:strip)
676+
end
677+
663678
def unmerged
664679
unmerged = []
665680
command_lines('diff', ["--cached"]).each do |line|

tests/units/test_merge_base.rb

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#!/usr/bin/env ruby
2+
3+
require File.dirname(__FILE__) + '/../test_helper'
4+
5+
class TestMergeBase < Test::Unit::TestCase
6+
def setup
7+
set_file_paths
8+
end
9+
10+
def test_branch_and_master_merge_base
11+
in_temp_dir do |path|
12+
g = Git.clone(@wbare, 'branch_merge_test')
13+
Dir.chdir('branch_merge_test') do
14+
15+
true_ancestor_sha = g.gcommit('master').sha
16+
17+
g.branch('new_branch').in_branch('test commit 1') do
18+
new_file('new_file_1', 'hello')
19+
g.add
20+
true
21+
end
22+
23+
g.branch('master').in_branch('test commit 2') do
24+
new_file('new_file_2', 'hello')
25+
g.add
26+
true
27+
end
28+
29+
ancestors = g.merge_base('master', 'new_branch')
30+
assert_equal(ancestors.size, 1) # there is only one true ancestor
31+
assert_equal(ancestors.first.sha, true_ancestor_sha) # proper common ancestor
32+
end
33+
end
34+
end
35+
36+
def test_branch_and_master_independent_merge_base
37+
in_temp_dir do |path|
38+
g = Git.clone(@wbare, 'branch_merge_test')
39+
Dir.chdir('branch_merge_test') do
40+
41+
true_ancestor_sha = g.gcommit('master').sha
42+
43+
g.branch('new_branch').in_branch('test commit 1') do
44+
new_file('new_file_1', 'hello')
45+
g.add
46+
true
47+
end
48+
49+
g.branch('master').in_branch('test commit 2') do
50+
new_file('new_file_2', 'hello')
51+
g.add
52+
true
53+
end
54+
55+
independent_commits = g.merge_base(true_ancestor_sha, 'master', 'new_branch', independent: true)
56+
assert_equal(independent_commits.size, 2) # both new master and a branch are unreachable from each other
57+
true_independent_commits_shas = [g.gcommit('master').sha, g.gcommit('new_branch').sha]
58+
assert_equal(independent_commits.map(&:sha).sort, true_independent_commits_shas.sort)
59+
end
60+
end
61+
end
62+
63+
def test_branch_and_master_fork_point_merge_base
64+
in_temp_dir do |path|
65+
g = Git.clone(@wbare, 'branch_merge_test')
66+
Dir.chdir('branch_merge_test') do
67+
68+
g.branch('master').in_branch('test commit 1') do
69+
new_file('new_file_1', 'hello')
70+
g.add
71+
true
72+
end
73+
74+
true_ancestor_sha = g.gcommit('master').sha
75+
76+
g.branch('new_branch').in_branch('test commit 2') do
77+
new_file('new_file_2', 'hello')
78+
g.add
79+
true
80+
end
81+
82+
g.branch('master').in_branch('test commit 3') do
83+
g.reset_hard(g.gcommit('HEAD^'))
84+
85+
new_file('new_file_3', 'hello')
86+
g.add
87+
true
88+
end
89+
90+
ancestors = g.merge_base('master', 'new_branch', fork_point: true)
91+
assert_equal(ancestors.size, 1) # there is only one true ancestor
92+
assert_equal(ancestors.first.sha, true_ancestor_sha) # proper common ancestor
93+
end
94+
end
95+
end
96+
97+
def test_branch_and_master_all_merge_base
98+
in_temp_dir do |path|
99+
g = Git.clone(@wbare, 'branch_merge_test')
100+
Dir.chdir('branch_merge_test') do
101+
102+
g.branch('new_branch_1').in_branch('test commit 1') do
103+
new_file('new_file_1', 'hello')
104+
g.add
105+
true
106+
end
107+
108+
first_commit_sha = g.gcommit('new_branch_1').sha
109+
110+
g.branch('new_branch_2').in_branch('test commit 2') do
111+
new_file('new_file_2', 'hello')
112+
g.add
113+
true
114+
end
115+
116+
second_commit_sha = g.gcommit('new_branch_2').sha
117+
118+
g.branch('new_branch_1').merge('new_branch_2')
119+
g.branch('new_branch_2').merge('new_branch_1^')
120+
121+
g.branch('new_branch_1').in_branch('test commit 3') do
122+
new_file('new_file_3', 'hello')
123+
g.add
124+
true
125+
end
126+
127+
g.branch('new_branch_2').in_branch('test commit 4') do
128+
new_file('new_file_4', 'hello')
129+
g.add
130+
true
131+
end
132+
133+
true_ancestors_shas = [first_commit_sha, second_commit_sha]
134+
135+
ancestors = g.merge_base('new_branch_1', 'new_branch_2')
136+
assert_equal(ancestors.size, 1) # default behavior returns only one ancestor
137+
assert(true_ancestors_shas.include?(ancestors.first.sha))
138+
139+
all_ancestors = g.merge_base('new_branch_1', 'new_branch_2', all: true)
140+
assert_equal(all_ancestors.size, 2) # there are two best ancestors in such case
141+
assert_equal(all_ancestors.map(&:sha).sort, true_ancestors_shas.sort)
142+
end
143+
end
144+
end
145+
146+
def test_branches_and_master_merge_base
147+
in_temp_dir do |path|
148+
g = Git.clone(@wbare, 'branch_merge_test')
149+
Dir.chdir('branch_merge_test') do
150+
151+
g.branch('new_branch_1').in_branch('test commit 1') do
152+
new_file('new_file_1', 'hello')
153+
g.add
154+
true
155+
end
156+
157+
g.branch('master').in_branch('test commit 2') do
158+
new_file('new_file_2', 'hello')
159+
g.add
160+
true
161+
end
162+
163+
non_octopus_ancestor_sha = g.gcommit('master').sha
164+
165+
g.branch('new_branch_2').in_branch('test commit 3') do
166+
new_file('new_file_3', 'hello')
167+
g.add
168+
true
169+
end
170+
171+
g.branch('master').in_branch('test commit 4') do
172+
new_file('new_file_4', 'hello')
173+
g.add
174+
true
175+
end
176+
177+
ancestors = g.merge_base('master', 'new_branch_1', 'new_branch_2')
178+
assert_equal(ancestors.size, 1) # there is only one true ancestor
179+
assert_equal(ancestors.first.sha, non_octopus_ancestor_sha) # proper common ancestor
180+
end
181+
end
182+
end
183+
184+
def test_branches_and_master_octopus_merge_base
185+
in_temp_dir do |path|
186+
g = Git.clone(@wbare, 'branch_merge_test')
187+
Dir.chdir('branch_merge_test') do
188+
189+
true_ancestor_sha = g.gcommit('master').sha
190+
191+
g.branch('new_branch_1').in_branch('test commit 1') do
192+
new_file('new_file_1', 'hello')
193+
g.add
194+
true
195+
end
196+
197+
g.branch('master').in_branch('test commit 2') do
198+
new_file('new_file_2', 'hello')
199+
g.add
200+
true
201+
end
202+
203+
g.branch('new_branch_2').in_branch('test commit 3') do
204+
new_file('new_file_3', 'hello')
205+
g.add
206+
true
207+
end
208+
209+
g.branch('master').in_branch('test commit 4') do
210+
new_file('new_file_4', 'hello')
211+
g.add
212+
true
213+
end
214+
215+
ancestors = g.merge_base('master', 'new_branch_1', 'new_branch_2', octopus: true)
216+
assert_equal(ancestors.size, 1) # there is only one true ancestor
217+
assert_equal(ancestors.first.sha, true_ancestor_sha) # proper common ancestor
218+
end
219+
end
220+
end
221+
end

0 commit comments

Comments
 (0)