Skip to content

Add CherryPickCommitIntoIndex to ObjectDatabase #1569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions LibGit2Sharp.Tests/CherryPickFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,56 @@ public void CherryPickWithConflictsReturnsConflicts()
}
}

[Fact]
public void CanCherryPickCommitIntoIndex()
{
string path = SandboxMergeTestRepo();
using (var repo = new Repository(path))
{
var ours = repo.Head.Tip;

Commit commitToMerge = repo.Branches["fast_forward"].Tip;

using (TransientIndex index = repo.ObjectDatabase.CherryPickCommitIntoIndex(commitToMerge, ours, 0, null))
{
var tree = index.WriteToTree();
Assert.Equal(commitToMerge.Tree.Id, tree.Id);
}
}
}

[Fact]
public void CanCherryPickIntoIndexWithConflicts()
{
const string conflictBranchName = "conflicts";

string path = SandboxMergeTestRepo();
using (var repo = new Repository(path))
{
Branch branch = repo.Branches[conflictBranchName];
Assert.NotNull(branch);

using (TransientIndex index = repo.ObjectDatabase.CherryPickCommitIntoIndex(branch.Tip, repo.Head.Tip, 0, null))
{
Assert.False(index.IsFullyMerged);

var conflict = index.Conflicts.First();

//Resolve the conflict by taking the blob from branch
var blob = repo.Lookup<Blob>(conflict.Theirs.Id);
//Add() does not remove conflict entries for the same path, so they must be explicitly removed first.
index.Remove(conflict.Ours.Path);
index.Add(blob, conflict.Ours.Path, Mode.NonExecutableFile);

Assert.True(index.IsFullyMerged);
var tree = index.WriteToTree();

//Since we took the conflicted blob from the branch, the merged result should be the same as the branch.
Assert.Equal(branch.Tip.Tree.Id, tree.Id);
}
}
}

private Commit AddFileCommitToRepo(IRepository repository, string filename, string content = null)
{
Touch(repository.Info.WorkingDirectory, filename, content);
Expand Down
105 changes: 83 additions & 22 deletions LibGit2Sharp/ObjectDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -562,37 +562,26 @@ public virtual HistoryDivergence CalculateHistoryDivergence(Commit one, Commit a
public virtual MergeTreeResult CherryPickCommit(Commit cherryPickCommit, Commit cherryPickOnto, int mainline, MergeTreeOptions options)
{
Ensure.ArgumentNotNull(cherryPickCommit, "cherryPickCommit");
Ensure.ArgumentNotNull(cherryPickOnto, "ours");
Ensure.ArgumentNotNull(cherryPickOnto, "cherryPickOnto");

options = options ?? new MergeTreeOptions();
var modifiedOptions = new MergeTreeOptions();

// We throw away the index after looking at the conflicts, so we'll never need the REUC
// entries to be there
GitMergeFlag mergeFlags = GitMergeFlag.GIT_MERGE_NORMAL | GitMergeFlag.GIT_MERGE_SKIP_REUC;
if (options.FindRenames)
{
mergeFlags |= GitMergeFlag.GIT_MERGE_FIND_RENAMES;
}
if (options.FailOnConflict)
{
mergeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT;
}

modifiedOptions.SkipReuc = true;

var opts = new GitMergeOpts
if (options != null)
{
Version = 1,
MergeFileFavorFlags = options.MergeFileFavor,
MergeTreeFlags = mergeFlags,
RenameThreshold = (uint)options.RenameThreshold,
TargetLimit = (uint)options.TargetLimit
};
modifiedOptions.FailOnConflict = options.FailOnConflict;
modifiedOptions.FindRenames = options.FindRenames;
modifiedOptions.MergeFileFavor = options.MergeFileFavor;
modifiedOptions.RenameThreshold = options.RenameThreshold;
modifiedOptions.TargetLimit = options.TargetLimit;
}

bool earlyStop;

using (var cherryPickOntoHandle = Proxy.git_object_lookup(repo.Handle, cherryPickOnto.Id, GitObjectType.Commit))
using (var cherryPickCommitHandle = Proxy.git_object_lookup(repo.Handle, cherryPickCommit.Id, GitObjectType.Commit))
using (var indexHandle = Proxy.git_cherrypick_commit(repo.Handle, cherryPickCommitHandle, cherryPickOntoHandle, (uint)mainline, opts, out earlyStop))
using (var indexHandle = CherryPickCommit(cherryPickCommit, cherryPickOnto, mainline, modifiedOptions, out earlyStop))
{
MergeTreeResult cherryPickResult;

Expand Down Expand Up @@ -879,6 +868,36 @@ public virtual TransientIndex MergeCommitsIntoIndex(Commit ours, Commit theirs,
return result;
}

/// <summary>
/// Performs a cherry-pick of <paramref name="cherryPickCommit"/> onto <paramref name="cherryPickOnto"/> commit.
/// </summary>
/// <param name="cherryPickCommit">The commit to cherry-pick.</param>
/// <param name="cherryPickOnto">The commit to cherry-pick onto.</param>
/// <param name="mainline">Which commit to consider the parent for the diff when cherry-picking a merge commit.</param>
/// <param name="options">The options for the merging in the cherry-pick operation.</param>
/// <returns>The <see cref="TransientIndex"/> containing the cherry-pick result tree and any conflicts, or null if the merge stopped early due to conflicts.
/// The index must be disposed by the caller. </returns>
public virtual TransientIndex CherryPickCommitIntoIndex(Commit cherryPickCommit, Commit cherryPickOnto, int mainline, MergeTreeOptions options)
{
Ensure.ArgumentNotNull(cherryPickCommit, "cherryPickCommit");
Ensure.ArgumentNotNull(cherryPickOnto, "cherryPickOnto");

options = options ?? new MergeTreeOptions();

bool earlyStop;
var indexHandle = CherryPickCommit(cherryPickCommit, cherryPickOnto, mainline, options, out earlyStop);
if (earlyStop)
{
if (indexHandle != null)
{
indexHandle.Dispose();
}
return null;
}
var result = new TransientIndex(indexHandle, repo);
return result;
}

/// <summary>
/// Perform a three-way merge of two commits, looking up their
/// commit ancestor. The returned index will contain the results
Expand Down Expand Up @@ -921,6 +940,48 @@ private IndexHandle MergeCommits(Commit ours, Commit theirs, MergeTreeOptions op
}
}

/// <summary>
/// Performs a cherry-pick of <paramref name="cherryPickCommit"/> onto <paramref name="cherryPickOnto"/> commit.
/// </summary>
/// <param name="cherryPickCommit">The commit to cherry-pick.</param>
/// <param name="cherryPickOnto">The commit to cherry-pick onto.</param>
/// <param name="mainline">Which commit to consider the parent for the diff when cherry-picking a merge commit.</param>
/// <param name="options">The options for the merging in the cherry-pick operation.</param>
/// <param name="earlyStop">True if the cherry-pick stopped early due to conflicts</param>
/// <returns>The <see cref="IndexHandle"/> containing the cherry-pick result tree and any conflicts</returns>
private IndexHandle CherryPickCommit(Commit cherryPickCommit, Commit cherryPickOnto, int mainline, MergeTreeOptions options, out bool earlyStop)
{
GitMergeFlag mergeFlags = GitMergeFlag.GIT_MERGE_NORMAL;
if (options.SkipReuc)
{
mergeFlags |= GitMergeFlag.GIT_MERGE_SKIP_REUC;
}
if (options.FindRenames)
{
mergeFlags |= GitMergeFlag.GIT_MERGE_FIND_RENAMES;
}
if (options.FailOnConflict)
{
mergeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT;
}

var mergeOptions = new GitMergeOpts
{
Version = 1,
MergeFileFavorFlags = options.MergeFileFavor,
MergeTreeFlags = mergeFlags,
RenameThreshold = (uint)options.RenameThreshold,
TargetLimit = (uint)options.TargetLimit,
};

using (var cherryPickOntoHandle = Proxy.git_object_lookup(repo.Handle, cherryPickOnto.Id, GitObjectType.Commit))
using (var cherryPickCommitHandle = Proxy.git_object_lookup(repo.Handle, cherryPickCommit.Id, GitObjectType.Commit))
{
var indexHandle = Proxy.git_cherrypick_commit(repo.Handle, cherryPickCommitHandle, cherryPickOntoHandle, (uint)mainline, mergeOptions, out earlyStop);
return indexHandle;
}
}


/// <summary>
/// Packs objects in the <see cref="ObjectDatabase"/> and write a pack (.pack) and index (.idx) files for them.
Expand Down