Skip to content

Commit 504f95e

Browse files
committed
Add MergeCommitsIntoIndex to ObjectDatabase
1 parent 2a8af3a commit 504f95e

File tree

3 files changed

+149
-30
lines changed

3 files changed

+149
-30
lines changed

LibGit2Sharp.Tests/MergeFixture.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,48 @@ public void CanIgnoreWhitespaceChangeMergeConflict(string branchName)
902902
}
903903
}
904904

905+
[Fact]
906+
public void CanMergeIntoIndex()
907+
{
908+
string path = SandboxMergeTestRepo();
909+
using (var repo = new Repository(path))
910+
{
911+
var master = repo.Lookup<Commit>("master");
912+
913+
Index index = repo.ObjectDatabase.MergeCommitsIntoIndex(master, master, null);
914+
var tree = index.WriteToTree();
915+
Assert.Equal(master.Tree.Id, tree.Id);
916+
}
917+
}
918+
919+
[Fact]
920+
public void CanMergeIntoIndexWithConflicts()
921+
{
922+
string path = SandboxMergeTestRepo();
923+
using (var repo = new Repository(path))
924+
{
925+
var master = repo.Lookup<Commit>("master");
926+
var branch = repo.Lookup<Commit>("conflicts");
927+
928+
Index index = repo.ObjectDatabase.MergeCommitsIntoIndex(branch, master, null);
929+
Assert.False(index.IsFullyMerged);
930+
931+
var conflict = index.Conflicts.First();
932+
933+
//Resolve the conflict by taking the blob from branch
934+
var blob = repo.Lookup<Blob>(conflict.Ours.Id);
935+
//Add() does not remove conflict entries for the same path, so they must be explicitly removed first.
936+
index.Remove(conflict.Ours.Path);
937+
index.Add(blob, conflict.Ours.Path, Mode.NonExecutableFile);
938+
939+
Assert.True(index.IsFullyMerged);
940+
var tree = index.WriteToTree();
941+
942+
//Since we took the conflicted blob from the branch, the merged result should be the same as the branch.
943+
Assert.Equal(branch.Tree.Id, tree.Id);
944+
}
945+
}
946+
905947
private Commit AddFileCommitToRepo(IRepository repository, string filename, string content = null)
906948
{
907949
Touch(repository.Info.WorkingDirectory, filename, content);

LibGit2Sharp/Index.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,20 @@ public class Index : IEnumerable<IndexEntry>
2626
protected Index()
2727
{ }
2828

29-
internal Index(Repository repo)
29+
internal Index(IndexHandle handle, Repository repo)
3030
{
3131
this.repo = repo;
32-
33-
handle = Proxy.git_repository_index(repo.Handle);
32+
this.handle = handle;
3433
conflicts = new ConflictCollection(this);
3534

3635
repo.RegisterForCleanup(handle);
3736
}
3837

38+
internal Index(Repository repo)
39+
: this(Proxy.git_repository_index(repo.Handle), repo)
40+
{
41+
}
42+
3943
internal Index(Repository repo, string indexPath)
4044
{
4145
this.repo = repo;
@@ -305,5 +309,16 @@ public virtual void Write()
305309
{
306310
Proxy.git_index_write(handle);
307311
}
312+
313+
/// <summary>
314+
/// Write the contents of this <see cref="Index"/> to a tree
315+
/// </summary>
316+
/// <returns></returns>
317+
public virtual Tree WriteToTree()
318+
{
319+
var treeId = Proxy.git_index_write_tree_to(this.handle, this.repo.Handle);
320+
var result = this.repo.Lookup<Tree>(treeId);
321+
return result;
322+
}
308323
}
309324
}

LibGit2Sharp/ObjectDatabase.cs

Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -755,47 +755,36 @@ public virtual Commit FindMergeBase(IEnumerable<Commit> commits, MergeBaseFindin
755755

756756
/// <summary>
757757
/// Perform a three-way merge of two commits, looking up their
758-
/// commit ancestor. The returned index will contain the results
759-
/// of the merge and can be examined for conflicts. The returned
760-
/// index must be disposed.
758+
/// commit ancestor. The returned <see cref="MergeTreeResult"/> will contain the results
759+
/// of the merge and can be examined for conflicts.
761760
/// </summary>
762-
/// <param name="ours">The first tree</param>
763-
/// <param name="theirs">The second tree</param>
761+
/// <param name="ours">The first commit</param>
762+
/// <param name="theirs">The second commit</param>
764763
/// <param name="options">The <see cref="MergeTreeOptions"/> controlling the merge</param>
765-
/// <returns>The <see cref="Index"/> containing the merged trees and any conflicts</returns>
764+
/// <returns>The <see cref="MergeTreeResult"/> containing the merged trees and any conflicts</returns>
766765
public virtual MergeTreeResult MergeCommits(Commit ours, Commit theirs, MergeTreeOptions options)
767766
{
768767
Ensure.ArgumentNotNull(ours, "ours");
769768
Ensure.ArgumentNotNull(theirs, "theirs");
770769

771-
options = options ?? new MergeTreeOptions();
770+
var modifiedOptions = new MergeTreeOptions();
772771

773772
// We throw away the index after looking at the conflicts, so we'll never need the REUC
774773
// entries to be there
775-
GitMergeFlag mergeFlags = GitMergeFlag.GIT_MERGE_NORMAL | GitMergeFlag.GIT_MERGE_SKIP_REUC;
776-
if (options.FindRenames)
777-
{
778-
mergeFlags |= GitMergeFlag.GIT_MERGE_FIND_RENAMES;
779-
}
780-
if (options.FailOnConflict)
781-
{
782-
mergeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT;
783-
}
774+
modifiedOptions.SkipReuc = true;
784775

785-
786-
var mergeOptions = new GitMergeOpts
776+
if (options != null)
787777
{
788-
Version = 1,
789-
MergeFileFavorFlags = options.MergeFileFavor,
790-
MergeTreeFlags = mergeFlags,
791-
RenameThreshold = (uint)options.RenameThreshold,
792-
TargetLimit = (uint)options.TargetLimit,
793-
};
778+
modifiedOptions.FailOnConflict = options.FailOnConflict;
779+
modifiedOptions.FindRenames = options.FindRenames;
780+
modifiedOptions.IgnoreWhitespaceChange = options.IgnoreWhitespaceChange;
781+
modifiedOptions.MergeFileFavor = options.MergeFileFavor;
782+
modifiedOptions.RenameThreshold = options.RenameThreshold;
783+
modifiedOptions.TargetLimit = options.TargetLimit;
784+
}
794785

795786
bool earlyStop;
796-
using (var oneHandle = Proxy.git_object_lookup(repo.Handle, ours.Id, GitObjectType.Commit))
797-
using (var twoHandle = Proxy.git_object_lookup(repo.Handle, theirs.Id, GitObjectType.Commit))
798-
using (var indexHandle = Proxy.git_merge_commits(repo.Handle, oneHandle, twoHandle, mergeOptions, out earlyStop))
787+
using (var indexHandle = MergeCommits(ours, theirs, modifiedOptions, out earlyStop))
799788
{
800789
MergeTreeResult mergeResult;
801790

@@ -859,6 +848,79 @@ public virtual PackBuilderResults Pack(PackBuilderOptions options, Action<PackBu
859848
return InternalPack(options, packDelegate);
860849
}
861850

851+
/// <summary>
852+
/// Perform a three-way merge of two commits, looking up their
853+
/// commit ancestor. The returned index will contain the results
854+
/// of the merge and can be examined for conflicts.
855+
/// </summary>
856+
/// <param name="ours">The first tree</param>
857+
/// <param name="theirs">The second tree</param>
858+
/// <param name="options">The <see cref="MergeTreeOptions"/> controlling the merge</param>
859+
/// <returns>The <see cref="Index"/> containing the merged trees and any conflicts, or null if the merge stopped early due to conflicts</returns>
860+
public virtual Index MergeCommitsIntoIndex(Commit ours, Commit theirs, MergeTreeOptions options)
861+
{
862+
Ensure.ArgumentNotNull(ours, "ours");
863+
Ensure.ArgumentNotNull(theirs, "theirs");
864+
865+
options = options ?? new MergeTreeOptions();
866+
867+
bool earlyStop;
868+
var indexHandle = MergeCommits(ours, theirs, options, out earlyStop);
869+
if (earlyStop)
870+
{
871+
if (indexHandle != null)
872+
{
873+
indexHandle.Dispose();
874+
}
875+
return null;
876+
}
877+
var result = new Index(indexHandle, repo);
878+
return result;
879+
}
880+
881+
/// <summary>
882+
/// Perform a three-way merge of two commits, looking up their
883+
/// commit ancestor. The returned index will contain the results
884+
/// of the merge and can be examined for conflicts.
885+
/// </summary>
886+
/// <param name="ours">The first tree</param>
887+
/// <param name="theirs">The second tree</param>
888+
/// <param name="options">The <see cref="MergeTreeOptions"/> controlling the merge</param>
889+
/// <param name="earlyStop">True if the merge stopped early due to conflicts</param>
890+
/// <returns>The <see cref="IndexHandle"/> containing the merged trees and any conflicts</returns>
891+
private IndexHandle MergeCommits(Commit ours, Commit theirs, MergeTreeOptions options, out bool earlyStop)
892+
{
893+
GitMergeFlag mergeFlags = GitMergeFlag.GIT_MERGE_NORMAL;
894+
if (options.SkipReuc)
895+
{
896+
mergeFlags |= GitMergeFlag.GIT_MERGE_SKIP_REUC;
897+
}
898+
if (options.FindRenames)
899+
{
900+
mergeFlags |= GitMergeFlag.GIT_MERGE_FIND_RENAMES;
901+
}
902+
if (options.FailOnConflict)
903+
{
904+
mergeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT;
905+
}
906+
907+
var mergeOptions = new GitMergeOpts
908+
{
909+
Version = 1,
910+
MergeFileFavorFlags = options.MergeFileFavor,
911+
MergeTreeFlags = mergeFlags,
912+
RenameThreshold = (uint)options.RenameThreshold,
913+
TargetLimit = (uint)options.TargetLimit,
914+
};
915+
using (var oneHandle = Proxy.git_object_lookup(repo.Handle, ours.Id, GitObjectType.Commit))
916+
using (var twoHandle = Proxy.git_object_lookup(repo.Handle, theirs.Id, GitObjectType.Commit))
917+
{
918+
var indexHandle = Proxy.git_merge_commits(repo.Handle, oneHandle, twoHandle, mergeOptions, out earlyStop);
919+
return indexHandle;
920+
}
921+
}
922+
923+
862924
/// <summary>
863925
/// Packs objects in the <see cref="ObjectDatabase"/> and write a pack (.pack) and index (.idx) files for them.
864926
/// For internal use only.

0 commit comments

Comments
 (0)