8000 Initial merge functionality. · GiTechLab/libgit2sharp@cc4bb2d · GitHub
[go: up one dir, main page]

Skip to content

Commit cc4bb2d

Browse files
angusbjonesjamill
authored andcommitted
Initial merge functionality.
Bring initial merge functionality to LibGit2Sharp.
1 parent 9165ae3 commit cc4bb2d

12 files changed

+642
-1
lines changed

LibGit2Sharp.Tests/MergeFixture.cs

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Linq;
1+
using System;
2+
using System.Linq;
23
using LibGit2Sharp.Tests.TestHelpers;
34
using Xunit;
45

@@ -80,5 +81,192 @@ public void CanRetrieveTheBranchBeingMerged()
8081
Assert.Null(mergedHeads[1].Tip);
8182
}
8283
}
84+
85+
[Fact]
86+
public void CanMergeRepoNonFastForward()
87+
{
88+
const string firstBranchFileName = "first branch file.txt";
89+
const string secondBranchFileName = "second branch file.txt";
90+
const string sharedBranchFileName = "first+second branch file.txt";
91+
92+
string path = CloneStandardTestRepo();
93+
94+
using (var repo = new Repository(path))
95+
{
96+
var firstBranch = repo.CreateBranch("FirstBranch");
97+
firstBranch.Checkout();
98+
var originalTreeCount = firstBranch.Tip.Tree.Count;
99+
100+
// Commit with ONE new file to both first & second branch (SecondBranch is created on this commit).
101+
AddFileCommitToRepo(repo, sharedBranchFileName);
102+
103+
var secondBranch = repo.CreateBranch("SecondBranch");
104+
// Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one).
105+
AddFileCommitToRepo(repo, firstBranchFileName);
106+
107+
secondBranch.Checkout();
108+
109+
// Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit).
110+
AddFileCommitToRepo(repo, secondBranchFileName);
111+
112+
MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature);
113+
114+
Assert.Equal(MergeStatus.NonFastForward, mergeResult.Status);
115+
116+
Assert.Equal(repo.Head.Tip, mergeResult.Commit);
117+
Assert.Equal(originalTreeCount + 3, mergeResult.Commit.Tree.Count); // Expecting original tree count plussed by the 3 added files.
118+
Assert.Equal(2, mergeResult.Commit.Parents.Count()); // Merge commit should have 2 parents
119+
}
120+
}
121+
122+
[Fact]
123+
public void IsUpToDateMerge()
124+
{
125+
const string sharedBranchFileName = "first+second branch file.txt";
126+
127+
string path = CloneStandardTestRepo();
128+
using (var repo = new Repository(path))
129+
{
130+
var firstBranch = repo.CreateBranch("FirstBranch");
131+
firstBranch.Checkout();
132+
133+
// Commit with ONE new file to both first & second branch (SecondBranch is created on this commit).
134+
AddFileCommitToRepo(repo, sharedBranchFileName);
135+
136+
var secondBranch = repo.CreateBranch("SecondBranch");
137+
138+
secondBranch.Checkout();
139+
140+
MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature);
141+
142+
Assert.Equal(MergeStatus.UpToDate, mergeResult.Status);
143+
}
144+
}
145+
146+
[Fact]
147+
public void CanFastForwardRepos()
148+
{
149+
const string firstBranchFileName = "first branch file.txt";
150+
const string sharedBranchFileName = "first+second branch file.txt";
151+
152+
string path = CloneStandardTestRepo();
153+
using (var repo = new Repository(path))
154+
{
155+
// Reset the index and the working tree.
156+
repo.Reset(ResetMode.Hard);
157+
158+
// Clean the working directory.
159+
repo.RemoveUntrackedFiles();
160+
161+
var firstBranch = repo.CreateBranch("FirstBranch");
162+
firstBranch.Checkout();
163+
164+
// Commit with ONE new file to both first & second branch (SecondBranch is created on this commit).
165+
AddFileCommitToRepo(repo, sharedBranchFileName);
166+
167+
var secondBranch = repo.CreateBranch("SecondBranch");
168+
169+
// Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one).
170+
AddFileCommitToRepo(repo, firstBranchFileName);
171+
172+
secondBranch.Checkout();
173+
174+
MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature);
175+
176+
Assert.Equal(MergeStatus.FastForward, mergeResult.Status);
177+
Assert.Equal(repo.Branches["FirstBranch"].Tip, mergeResult.Commit);
178+
Assert.Equal(repo.Branches["FirstBranch"].Tip, repo.Head.Tip);
179+
Assert.Equal(0, repo.Index.RetrieveStatus().Count());
180+
}
181+
}
182+
183+
[Fact]
184+
public void ConflictingMergeRepos()
185+
{
186+
const string firstBranchFileName = "first branch file.txt";
187+
const string secondBranchFileName = "second branch file.txt";
188+
const string sharedBranchFileName = "first+second branch file.txt";
189+
190+
string path = CloneStandardTestRepo();
191+
using (var repo = new Repository(path))
192+
{
193+
var firstBranch = repo.CreateBranch("FirstBranch");
194+
firstBranch.Checkout();
195+
196+
// Commit with ONE new file to both first & second branch (SecondBranch is created on this commit).
197+
AddFileCommitToRepo(repo, sharedBranchFileName);
198+
199+
var secondBranch = repo.CreateBranch("SecondBranch");
200+
// Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one).
201+
AddFileCommitToRepo(repo, firstBranchFileName);
202+
AddFileCommitToRepo(repo, sharedBranchFileName, "The first branches comment"); // Change file in first branch
203+
204+
secondBranch.Checkout();
205+
// Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit).
206+
AddFileCommitToRepo(repo, secondBranchFileName);
207+
AddFileCommitToRepo(repo, sharedBranchFileName, "The second branches comment"); // Change file in second branch
208+
209+
MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature);
210+
211+
Assert.Equal(MergeStatus.Conflicts, mergeResult.Status);
212+
213+
Assert.Null(mergeResult.Commit);
214+
Assert.Equal(1, repo.Index.Conflicts.Count());
215+
216+
var conflict = repo.Index.Conflicts.First();
217+
var changes = repo.Diff.Compare(repo.Lookup<Blob>(conflict.Theirs.Id), repo.Lookup<Blob>(conflict.Ours.Id));
218+
219+
Assert.False(changes.IsBinaryComparison);
220+
}
221+
}
222+
223+
[Fact]
224+
public void ConflictingMergeReposBinary()
225+
{
226+
const string firstBranchFileName = "first branch file.bin";
227+
const string secondBranchFileName = "second branch file.bin";
228+
const string sharedBranchFileName = "first+second branch file.bin";
229+
230+
string path = CloneStandardTestRepo();
231+
using (var repo = new Repository(path))
232+
{
233+
var firstBranch = repo.CreateBranch("FirstBranch");
234+
firstBranch.Checkout();
235+
236+
// Commit with ONE new file to both first & second branch (SecondBranch is created on this commit).
237+
AddFileCommitToRepo(repo, sharedBranchFileName);
238+
239+
var secondBranch = repo.CreateBranch("SecondBranch");
240+
// Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one).
241+
AddFileCommitToRepo(repo, firstBranchFileName);
242+
AddFileCommitToRepo(repo, sharedBranchFileName, "\0The first branches comment\0"); // Change file in first branch
243+
244+
secondBranch.Checkout();
245+
// Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit).
246+
AddFileCommitToRepo(repo, secondBranchFileName);
247+
AddFileCommitToRepo(repo, sharedBranchFileName, "\0The second branches comment\0"); // Change file in second branch
248+
249+
MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature);
250+
251+
Assert.Equal(MergeStatus.Conflicts, mergeResult.Status);
252+
253+
Assert.Equal(1, repo.Index.Conflicts.Count());
254+
255+
Conflict conflict = repo.Index.Conflicts.First();
256+
257+
var changes = repo.Diff.Compare(repo.Lookup<Blob>(conflict.Theirs.Id), repo.Lookup<Blob>(conflict.Ours.Id));
258+
259+
Assert.True(changes.IsBinaryComparison);
260+
}
261+
}
262+
263+
private Commit AddFileCommitToRepo(IRepository repository, string filename, string content = null)
264+
{
265+
Touch(repository.Info.WorkingDirectory, filename, content);
266+
267+
repository.Index.Stage(filename);
268+
269+
return repository.Commit("New commit", Constants.Signature, Constants.Signature);
270+
}
83271
}
84272
}

LibGit2Sharp/Core/GitMergeOpts.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace LibGit2Sharp.Core
5+
{
6+
internal enum GitMergeFlags
7+
{
8+
/// <summary>
9+
/// Default
10+
/// </summary>
11+
GIT_MERGE_DEFAULT = 0,
12+
13+
/// <summary>
14+
/// Do not fast-forward.
15+
/// </summary>
16+
GIT_MERGE_NO_FASTFORWARD = 1,
17+
18+
/// <summary>
19+
/// Only perform fast-forward.
20+
/// </summary>
21+
GIT_MERGE_FASTFORWARD_ONLY = 2,
22+
}
23+
24+
[StructLayout(LayoutKind.Sequential)]
25+
internal struct GitMergeOpts
26+
{
27+
public uint Version;
28+
29+
public GitMergeFlags MergeFlags;
30+
public GitMergeTreeOpts MergeTreeOpts;
31+
public GitCheckoutOpts CheckoutOpts;
32+
}
33+
}

LibGit2Sharp/Core/GitMergeResult.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using LibGit2Sharp.Core;
2+
using LibGit2Sharp.Core.Handles;
3+
4+
namespace LibGit2Sharp
5+
{
6+
internal class GitMergeResult
7+
{
8+
internal GitMergeResult(GitMergeResultHandle handle)
9+
{
10+
IsUpToDate = Proxy.git_merge_result_is_uptodate(handle);
11+
IsFastForward = Proxy.git_merge_result_is_fastforward(handle);
12+
13+
if (IsFastForward)
14+
{
15+
FastForwardId = Proxy.git_merge_result_fastforward_oid(handle);
16+
}
17+
}
18+
19+
public virtual bool IsUpToDate { get; private set; }
20+
21+
public virtual bool IsFastForward { get; private set; }
22+
23+
/// <summary>
24+
/// The ID that a fast-forward merge should advance to.
25+
/// </summary>
26+
public virtual ObjectId FastForwardId { get; private set; }
27+
28+
public virtual MergeStatus Status
29+
{
30+
get
31+
{
32+
if (IsUpToDate)
33+
{
34+
return MergeStatus.UpToDate;
35+
}
36+
else if (IsFastForward)
37+
{
38+
return MergeStatus.FastForward;
39+
}
40+
else
41+
{
42+
return MergeStatus.NonFastForward;
43+
}
44+
}
45+
}
46+
}
47+
}

LibGit2Sharp/Core/GitMergeTreeOpts.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace LibGit2Sharp.Core
5+
{
6+
[Flags]
7+
internal enum GitMergeTreeFlags
8+
{
9+
/// <summary>
10+
/// No options.
11+
/// </summary>
12+
GIT_MERGE_TREE_NORMAL = 0,
13+
14+
/// <summary>
15+
/// GIT_MERGE_TREE_FIND_RENAMES in libgit2
16+
/// </summary>
17+
GIT_MERGE_TREE_FIND_RENAMES = (1 << 0),
18+
}
19+
20+
internal enum GitMergeAutomergeFlags
21+
{
22+
GIT_MERGE_AUTOMERGE_NORMAL = 0,
23+
GIT_MERGE_AUTOMERGE_NONE = 1,
24+
GIT_MERGE_AUTOMERGE_FAVOR_OURS = 2,
25+
GIT_MERGE_AUTOMERGE_FAVOR_THEIRS = 3,
26+
}
27+
28+
[StructLayout(LayoutKind.Sequential)]
29+
internal struct GitMergeTreeOpts
30+
{
31+
public uint Version;
32+
33+
public GitMergeTreeFlags MergeTreeFlags;
34+
35+
/// <summary>
36+
/// Similarity to consider a file renamed.
37+
/// </summary>
38+
public uint RenameThreshold;
39+
40+
/// <summary>
41+
/// Maximum similarity sources to examine (overrides
42+
/// 'merge.renameLimit' config (default 200)
43+
/// </summary>
44+
public uint TargetLimit;
45+
46+
/// <summary>
47+
/// Pluggable similarityMetric; pass IntPtr.Zero
48+
/// to use internal metric.
49+
/// </summary>
50+
public IntPtr SimilarityMetric;
51+
52+
/// <summary>
53+
/// Flags for automerging content.
54+
/// </summary>
55+
public GitMergeAutomergeFlags MergeAutomergeFlags;
56+
}
57+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace LibGit2Sharp.Core.Handles
4+
{
5+
internal class GitMergeHeadHandle : SafeHandleBase
6+
{
7+
protected override bool ReleaseHandleImpl()
8+
{
9+
Proxy.git_merge_head_free(handle);
10+
return true;
11+
}
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace LibGit2Sharp.Core.Handles
4+
{
5+
internal class GitMergeResultHandle : SafeHandleBase
6+
{
7+
protected override bool ReleaseHandleImpl()
8+
{
9+
Proxy.git_merge_result_free(handle);
10+
return true;
11+
}
12+
}
13+
}

0 commit comments

Comments
 (0)
0