Skip to content

Commit cc30bd4

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

File tree

4 files changed

+166
-0
lines changed

4 files changed

+166
-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: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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+
repo = Git.clone(@wbare, 'branch_merge_test')
13+
Dir.chdir('branch_merge_test') do
14+
true_ancestor_sha = repo.gcommit('master').sha
15+
16+
add_commit(repo, 'new_branch')
17+
add_commit(repo, 'master')
18+
19+
ancestors = repo.merge_base('master', 'new_branch')
20+
assert_equal(ancestors.size, 1) # there is only one true ancestor
21+
assert_equal(ancestors.first.sha, true_ancestor_sha) # proper common ancestor
22+
end
23+
end
24+
end
25+
26+
def test_branch_and_master_independent_merge_base
27+
in_temp_dir do |_path|
28+
repo = Git.clone(@wbare, 'branch_merge_test')
29+
Dir.chdir('branch_merge_test') do
30+
true_ancestor_sha = repo.gcommit('master').sha
31+
32+
add_commit(repo, 'new_branch')
33+
add_commit(repo, 'master')
34+
35+
independent_commits = repo.merge_base(true_ancestor_sha, 'master', 'new_branch', independent: true)
36+
assert_equal(independent_commits.size, 2) # both new master and a branch are unreachable from each other
37+
true_independent_commits_shas = [repo.gcommit('master').sha, repo.gcommit('new_branch').sha]
38+
assert_equal(independent_commits.map(&:sha).sort, true_independent_commits_shas.sort)
39+
end
40+
end
41+
end
42+
43+
def test_branch_and_master_fork_point_merge_base
44+
in_temp_dir do |_path|
45+
repo = Git.clone(@wbare, 'branch_merge_test')
46+
Dir.chdir('branch_merge_test') do
47+
add_commit(repo, 'master')
48+
49+
true_ancestor_sha = repo.gcommit('master').sha
50+
51+
add_commit(repo, 'new_branch')
52+
add_commit(repo, 'master') { repo.reset_hard(repo.gcommit('HEAD^')) }
53+
54+
ancestors = repo.merge_base('master', 'new_branch', fork_point: true)
55+
assert_equal(ancestors.size, 1) # there is only one true ancestor
56+
assert_equal(ancestors.first.sha, true_ancestor_sha) # proper common ancestor
57+
end
58+
end
59+
end
60+
61+
def test_branch_and_master_all_merge_base
62+
in_temp_dir do |_path|
63+
repo = Git.clone(@wbare, 'branch_merge_test')
64+
Dir.chdir('branch_merge_test') do
65+
add_commit(repo, 'new_branch_1')
66+
67+
first_commit_sha = repo.gcommit('new_branch_1').sha
68+
69+
add_commit(repo, 'new_branch_2')
70+
71+
second_commit_sha = repo.gcommit('new_branch_2').sha
72+
73+
repo.branch('new_branch_1').merge('new_branch_2')
74+
repo.branch('new_branch_2').merge('new_branch_1^')
75+
76+
add_commit(repo, 'new_branch_1')
77+
add_commit(repo, 'new_branch_2')
78+
79+
true_ancestors_shas = [first_commit_sha, second_commit_sha]
80+
81+
ancestors = repo.merge_base('new_branch_1', 'new_branch_2')
82+
assert_equal(ancestors.size, 1) # default behavior returns only one ancestor
83+
assert(true_ancestors_shas.include?(ancestors.first.sha))
84+
85+
all_ancestors = repo.merge_base('new_branch_1', 'new_branch_2', all: true)
86+
assert_equal(all_ancestors.size, 2) # there are two best ancestors in such case
87+
assert_equal(all_ancestors.map(&:sha).sort, true_ancestors_shas.sort)
88+
end
89+
end
90+
end
91+
92+
def test_branches_and_master_merge_base
93+
in_temp_dir do |_path|
94+
repo = Git.clone(@wbare, 'branch_merge_test')
95+
Dir.chdir('branch_merge_test') do
96+
add_commit(repo, 'new_branch_1')
97+
add_commit(repo, 'master')
98+
99+
non_octopus_ancestor_sha = repo.gcommit('master').sha
100+
101+
add_commit(repo, 'new_branch_2')
102+
add_commit(repo, 'master')
103+
104+
ancestors = repo.merge_base('master', 'new_branch_1', 'new_branch_2')
105+
assert_equal(ancestors.size, 1) # there is only one true ancestor
106+
assert_equal(ancestors.first.sha, non_octopus_ancestor_sha) # proper common ancestor
107+
end
108+
end
109+
end
110+
111+
def test_branches_and_master_octopus_merge_base
112+
in_temp_dir do |_path|
113+
repo = Git.clone(@wbare, 'branch_merge_test')
114+
Dir.chdir('branch_merge_test') do
115+
true_ancestor_sha = repo.gcommit('master').sha
116+
117+
add_commit(repo, 'new_branch_1')
118+
add_commit(repo, 'master')
119+
add_commit(repo, 'new_branch_2')
120+
add_commit(repo, 'master')
121+
122+
ancestors = repo.merge_base('master', 'new_branch_1', 'new_branch_2', octopus: true)
123+
assert_equal(ancestors.size, 1) # there is only one true ancestor
124+
assert_equal(ancestors.first.sha, true_ancestor_sha) # proper common ancestor
125+
end
126+
end
127+
end
128+
129+
private
130+
131+
def add_commit(repo, branch_name, &block)
132+
@commit_number = (@commit_number || 0) + 1
133+
134+
repo.branch(branch_name).in_branch("test commit #{@commit_number}") do
135+
yield block if block
136+
new_file("new_file_#{@commit_number}", 'hello')
137+
repo.add
138+
true
139+
end
140+
end
141+
end

0 commit comments

Comments
 (0)