Skip to content

Commit cad89d0

Browse files
committed
Merge pull request #990 from libgit2/ethomson/mergecommits
Introduce `Repository.MergeCommits`
2 parents ba5f7d2 + 534d4e7 commit cad89d0

18 files changed

+544
-343
lines changed

LibGit2Sharp.Tests/MergeFixture.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,106 @@ public void CanMergeIntoOrphanedBranch()
748748
}
749749
}
750750

751+
752+
[Fact]
753+
public void CanMergeTreeIntoSameTree()
754+
{
755+
string path = SandboxMergeTestRepo();
756+
using (var repo = new Repository(path))
757+
{
758+
var master = repo.Branches["master"].Tip;
759+
760+
var result = repo.ObjectDatabase.MergeCommits(master, master, null);
761+
Assert.Equal(MergeTreeStatus.Succeeded, result.Status);
762+
Assert.Equal(0, result.Conflicts.Count());
763+
}
764+
}
765+
766+
[Fact]
767+
public void CanMergeTreeIntoTreeFromUnbornBranch()
768+
{
769+
string path = SandboxMergeTestRepo();
770+
using (var repo = new Repository(path))
771+
{
772+
repo.Refs.UpdateTarget("HEAD", "refs/heads/unborn");
773+
774+
Touch(repo.Info.WorkingDirectory, "README", "Yeah!\n");
775+
repo.Index.Clear();
776+
repo.Stage("README");
777+
778+
repo.Commit("A new world, free of the burden of the history", Constants.Signature, Constants.Signature);
779+
780+
var master = repo.Branches["master"].Tip;
781+
var branch = repo.Branches["unborn"].Tip;
782+
783+
var result = repo.ObjectDatabase.MergeCommits(master, branch, null);
784+
Assert.Equal(MergeTreeStatus.Succeeded, result.Status);
785+
Assert.NotNull(result.Tree);
786+
Assert.Equal(0, result.Conflicts.Count());
787+
}
788+
}
789+
790+
[Fact]
791+
public void CanMergeCommitsAndDetectConflicts()
792+
{
793+
string path = SandboxMergeTestRepo();
794+
using (var repo = new Repository(path))
795+
{
796+
repo.Refs.UpdateTarget("HEAD", "refs/heads/unborn");
797+
798+
repo.Index.Replace(repo.Lookup<Commit>("conflicts"));
799+
800+
repo.Commit("A conflicting world, free of the burden of the history", Constants.Signature, Constants.Signature);
801+
802+
var master = repo.Branches["master"].Tip;
803+
var branch = repo.Branches["unborn"].Tip;
804+
805+
var result = repo.ObjectDatabase.MergeCommits(master, branch, null);
806+
Assert.Equal(MergeTreeStatus.Conflicts, result.Status);
807+
Assert.Null(result.Tree);
808+
Assert.NotEqual(0, result.Conflicts.Count());
809+
}
810+
}
811+
812+
[Fact]
813+
public void CanMergeFastForwardTreeWithoutConflicts()
814+
{
815+
string path = SandboxMergeTestRepo();
816+
using (var repo = new Repository(path))
817+
{
818+
var master = repo.Lookup<Commit>("master");
819+
var branch = repo.Lookup<Commit>("fast_forward");
820+
821+
var result = repo.ObjectDatabase.MergeCommits(master, branch, null);
822+
Assert.Equal(MergeTreeStatus.Succeeded, result.Status);
823+
Assert.NotNull(result.Tree);
824+
Assert.Equal(0, result.Conflicts.Count());
825+
}
826+
}
827+
828+
[Fact]
829+
public void CanIdentifyConflictsInMergeCommits()
830+
{
831+
string path = SandboxMergeTestRepo();
832+
using (var repo = new Repository(path))
833+
{
834+
var master = repo.Lookup<Commit>("master");
835+
var branch = repo.Lookup<Commit>("conflicts");
836+
837+
var result = repo.ObjectDatabase.MergeCommits(master, branch, null);
838+
839+
Assert.Equal(MergeTreeStatus.Conflicts, result.Status);
840+
841+
Assert.Null(result.Tree);
842+
Assert.Equal(1, result.Conflicts.Count());
843+
844+
var conflict = result.Conflicts.First();
845+
Assert.Equal(new ObjectId("8e9daea300fbfef6c0da9744c6214f546d55b279"), conflict.Ancestor.Id);
846+
Assert.Equal(new ObjectId("610b16886ca829cebd2767d9196f3c4378fe60b5"), conflict.Ours.Id);
847+
Assert.Equal(new ObjectId("3dd9738af654bbf1c363f6c3bbc323bacdefa179"), conflict.Theirs.Id);
848+
}
849+
}
850+
751851
private Commit AddFileCommitToRepo(IRepository repository, string filename, string content = null)
752852
{
753853
Touch(repository.Info.WorkingDirectory, filename, content);

LibGit2Sharp/CherryPickOptions.cs

Lines changed: 4 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -6,108 +6,26 @@ namespace LibGit2Sharp
66
/// <summary>
77
/// Options controlling CherryPick behavior.
88
/// </summary>
9-
public sealed class CherryPickOptions : IConvertableToGitCheckoutOpts
9+
public sealed class CherryPickOptions : MergeAndCheckoutOptionsBase
1010
{
1111
/// <summary>
1212
/// Initializes a new instance of the <see cref="CherryPickOptions"/> class.
1313
/// By default the cherry pick will be committed if there are no conflicts.
1414
/// </summary>
1515
public CherryPickOptions()
1616
{
17-
CommitOnSuccess = true;
18-
19-
FindRenames = true;
20-
21-
// TODO: libgit2 should provide reasonable defaults for these
22-
// values, but it currently does not.
23-
RenameThreshold = 50;
24-
TargetLimit = 200;
2517
}
2618

27-
/// <summary>
28-
/// The Flags specifying what conditions are
29-
/// reported through the OnCheckoutNotify delegate.
30-
/// </summary>
31-
public CheckoutNotifyFlags CheckoutNotifyFlags { get; set; }
32-
33-
/// <summary>
34-
/// Delegate that checkout progress will be reported through.
35-
/// </summary>
36-
public CheckoutProgressHandler OnCheckoutProgress { get; set; }
37-
38-
/// <summary>
39-
/// Delegate that checkout will notify callers of
40-
/// certain conditions. The conditions that are reported is
41-
/// controlled with the CheckoutNotifyFlags property.
42-
/// </summary>
43-
public CheckoutNotifyHandler OnCheckoutNotify { get; set; }
44-
45-
/// <summary>
46-
/// Commit the cherry pick if the cherry pick is successful.
47-
/// </summary>
48-
public bool CommitOnSuccess { get; set; }
49-
5019
/// <summary>
5120
/// When cherry picking a merge commit, the parent number to consider as
5221
/// mainline, starting from offset 1.
5322
/// <para>
5423
/// As a merge commit has multiple parents, cherry picking a merge commit
55-
/// will reverse all the changes brought in by the merge except for
56-
/// one parent's line of commits. The parent to preserve is called the
57-
/// mainline, and must be specified by its number (i.e. offset).
24+
/// will take only the changes relative to the given parent. The parent
25+
/// to consider changes based on is called the mainline, and must be
26+
/// specified by its number (i.e. offset).
5827
/// </para>
5928
/// </summary>
6029
public int Mainline { get; set; }
61-
62-
/// <summary>
63-
/// How to handle conflicts encountered during a merge.
64-
/// </summary>
65-
public MergeFileFavor MergeFileFavor { get; set; }
66-
67-
/// <summary>
68-
/// How Checkout should handle writing out conflicting index entries.
69-
/// </summary>
70-
public CheckoutFileConflictStrategy FileConflictStrategy { get; set; }
71-
72-
/// <summary>
73-
/// Find renames. Default is true.
74-
/// </summary>
75-
public bool FindRenames { get; set; }
76-
77-
/// <summary>
78-
/// Similarity to consider a file renamed (default 50). If
79-
/// `FindRenames` is enabled, added files will be compared
80-
/// with deleted files to determine their similarity. Files that are
81-
/// more similar than the rename threshold (percentage-wise) will be
82-
/// treated as a rename.
83-
/// </summary>
84-
public int RenameThreshold;
85-
86-
/// <summary>
87-
/// Maximum similarity sources to examine for renames (default 200).
88-
/// If the number of rename candidates (add / delete pairs) is greater
89-
/// than this value, inexact rename detection is aborted.
90-
///
91-
/// This setting overrides the `merge.renameLimit` configuration value.
92-
/// </summary>
93-
public int TargetLimit;
94-
95-
#region IConvertableToGitCheckoutOpts
96-
97-
CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks()
98-
{
99-
return CheckoutCallbacks.From(OnCheckoutProgress, OnCheckoutNotify);
100-
}
101-
102-
CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy
103-
{
104-
get
105-
{
106-
return CheckoutStrategy.GIT_CHECKOUT_SAFE |
107-
GitCheckoutOptsWrapper.CheckoutStrategyFromFileConflictStrategy(FileConflictStrategy);
108-
}
109-
}
110-
111-
#endregion IConvertableToGitCheckoutOpts
11230
}
11331
}

LibGit2Sharp/ConflictCollection.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ namespace LibGit2Sharp
1313
/// </summary>
1414
public class ConflictCollection : IEnumerable<Conflict>
1515
{
16-
private readonly Repository repo;
16+
private readonly Index index;
1717

1818
/// <summary>
1919
/// Needed for mocking purposes.
2020
/// </summary>
2121
protected ConflictCollection()
2222
{ }
2323

24-
internal ConflictCollection(Repository repo)
24+
internal ConflictCollection(Index index)
2525
{
26-
this.repo = repo;
26+
this.index = index;
2727
}
2828

2929
/// <summary>
@@ -36,7 +36,7 @@ public virtual Conflict this[string path]
3636
{
3737
get
3838
{
39-
return Proxy.git_index_conflict_get(repo.Index.Handle, repo, path);
39+
return Proxy.git_index_conflict_get(index.Handle, path);
4040
}
4141
}
4242

@@ -48,7 +48,7 @@ public virtual IndexReucEntryCollection ResolvedConflicts
4848
{
4949
get
5050
{
51-
return new IndexReucEntryCollection(repo);
51+
return new IndexReucEntryCollection(index);
5252
}
5353
}
5454

@@ -60,7 +60,7 @@ public virtual IndexNameEntryCollection Names
6060
{
6161
get
6262
{
63-
return new IndexNameEntryCollection(repo);
63+
return new IndexNameEntryCollection(index);
6464
}
6565
}
6666

@@ -72,7 +72,7 @@ private List<Conflict> AllConflicts()
7272
IndexEntry ancestor = null, ours = null, theirs = null;
7373
string currentPath = null;
7474

75-
foreach (IndexEntry entry in repo.Index)
75+
foreach (IndexEntry entry in index)
7676
{
7777
if (entry.StageLevel == StageLevel.Staged)
7878
{
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace LibGit2Sharp.Core.Handles
2+
{
3+
internal class ConflictIteratorSafeHandle : SafeHandleBase
4+
{
5+
protected override bool ReleaseHandleImpl()
6+
{
7+
Proxy.git_index_conflict_iterator_free(handle);
8+
return true;
9+
}
10+
}
11+
}

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,22 @@ internal static extern int git_index_conflict_get(
542542
IndexSafeHandle index,
543543
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path);
544544

545+
[DllImport(libgit2)]
546+
internal static extern int git_index_conflict_iterator_new(
547+
out ConflictIteratorSafeHandle iterator,
548+
IndexSafeHandle index);
549+
550+
[DllImport(libgit2)]
551+
internal static extern int git_index_conflict_next(
552+
out IndexEntrySafeHandle ancestor,
553+
out IndexEntrySafeHandle ours,
554+
out IndexEntrySafeHandle theirs,
555+
ConflictIteratorSafeHandle iterator);
556+
557+
[DllImport(libgit2)]
558+
internal static extern void git_index_conflict_iterator_free(
559+
IntPtr iterator);
560+
545561
[DllImport(libgit2)]
546562
internal static extern UIntPtr git_index_entrycount(IndexSafeHandle index);
547563

@@ -602,6 +618,9 @@ internal static extern IndexReucEntrySafeHandle git_index_reuc_get_bypath(
602618
[DllImport(libgit2)]
603619
internal static extern int git_index_write_tree(out GitOid treeOid, IndexSafeHandle index);
604620

621+
[DllImport(libgit2)]
622+
internal static extern int git_index_write_tree_to(out GitOid treeOid, IndexSafeHandle index, RepositorySafeHandle repo);
623+
605624
[DllImport(libgit2)]
606625
internal static extern int git_index_read_tree(IndexSafeHandle index, GitObjectSafeHandle tree);
607626

@@ -661,12 +680,11 @@ internal static extern int git_merge(
661680
ref GitCheckoutOpts checkout_opts);
662681

663682
[DllImport(libgit2)]
664-
internal static extern int git_merge_trees(
683+
internal static extern int git_merge_commits(
665684
out IndexSafeHandle index,
666685
RepositorySafeHandle repo,
667-
GitObjectSafeHandle ancestor_tree,
668-
GitObjectSafeHandle our_tree,
669-
GitObjectSafeHandle their_tree,
686+
GitObjectSafeHandle our_commit,
687+
GitObjectSafeHandle their_commit,
670688
ref GitMergeOpts merge_opts);
671689

672690
[DllImport(libgit2)]

0 commit comments

Comments
 (0)