From 90155789aedd51bc8f2ccb122f48977bc0fccc66 Mon Sep 17 00:00:00 2001 From: Jameson Miller Date: Tue, 22 Apr 2014 09:25:49 -0400 Subject: [PATCH] Update Checkout and Merge options Checkout methods now use CheckoutOptions Merge now takes several options: - Option to specify what is checked out for file conflicts. - Report CheckoutProgress and CheckoutNotify - Option to specify MergeFileFavor Updates for code review feedback --- LibGit2Sharp.Tests/CheckoutFixture.cs | 32 ++- LibGit2Sharp.Tests/MergeFixture.cs | 227 ++++++++++++++++- LibGit2Sharp.Tests/RepositoryFixture.cs | 2 +- .../Resources/merge_testrepo_wd/dot_git/index | Bin 176 -> 176 bytes .../merge_testrepo_wd/dot_git/info/refs | 2 + .../dot_git/logs/refs/heads/rename | 3 + .../dot_git/logs/refs/heads/rename_source | 2 + .../dot_git/objects/info/packs | 2 +- ...e214e240728e3b0ce2fc2d5e6513772fed0523.idx | Bin 1520 -> 0 bytes ...214e240728e3b0ce2fc2d5e6513772fed0523.pack | Bin 1402 -> 0 bytes ...b2f231d5e59d4a265578d02283f848a5dc4694.idx | Bin 0 -> 1828 bytes ...2f231d5e59d4a265578d02283f848a5dc4694.pack | Bin 0 -> 2480 bytes .../merge_testrepo_wd/dot_git/packed-refs | 2 + LibGit2Sharp/Branch.cs | 30 ++- LibGit2Sharp/CheckoutCallbacks.cs | 2 +- LibGit2Sharp/CheckoutFileConflictStrategy.cs | 43 ++++ LibGit2Sharp/CheckoutNotificationOptions.cs | 1 + LibGit2Sharp/CheckoutOptions.cs | 37 ++- LibGit2Sharp/CloneOptions.cs | 29 ++- LibGit2Sharp/Core/GitCheckoutOpts.cs | 21 +- LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs | 98 ++++++++ LibGit2Sharp/Core/GitMergeOpts.cs | 10 +- LibGit2Sharp/IRepository.cs | 41 ++++ LibGit2Sharp/LibGit2Sharp.csproj | 2 + LibGit2Sharp/MergeOptions.cs | 120 ++++++++- LibGit2Sharp/Repository.cs | 230 +++++++++++------- LibGit2Sharp/RepositoryExtensions.cs | 9 +- 27 files changed, 805 insertions(+), 140 deletions(-) create mode 100644 LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename create mode 100644 LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename_source delete mode 100644 LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-52e214e240728e3b0ce2fc2d5e6513772fed0523.idx delete mode 100644 LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-52e214e240728e3b0ce2fc2d5e6513772fed0523.pack create mode 100644 LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-f9b2f231d5e59d4a265578d02283f848a5dc4694.idx create mode 100644 LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-f9b2f231d5e59d4a265578d02283f848a5dc4694.pack create mode 100644 LibGit2Sharp/CheckoutFileConflictStrategy.cs create mode 100644 LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs diff --git a/LibGit2Sharp.Tests/CheckoutFixture.cs b/LibGit2Sharp.Tests/CheckoutFixture.cs index f4ea97bc2..abe4ad9aa 100644 --- a/LibGit2Sharp.Tests/CheckoutFixture.cs +++ b/LibGit2Sharp.Tests/CheckoutFixture.cs @@ -266,7 +266,7 @@ public void CanForcefullyCheckoutWithConflictingStagedChanges() Assert.Throws(() => repo.Checkout(master.CanonicalName)); // Checkout with force option should succeed. - repo.Checkout(master.CanonicalName, CheckoutModifiers.Force, null, null); + repo.Checkout(master.CanonicalName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force}); // Assert that master branch is checked out. Assert.True(repo.Branches["master"].IsCurrentRepositoryHead); @@ -338,8 +338,14 @@ public void CanCancelCheckoutThroughNotifyCallback() // Verify that we get called for the notify conflict cb string conflictPath = string.Empty; - CheckoutNotificationOptions checkoutNotifications = new CheckoutNotificationOptions((path, flags) => { conflictPath = path; return false; }, CheckoutNotifyFlags.Conflict); - Assert.Throws(() => repo.Checkout("master", CheckoutModifiers.None, null, checkoutNotifications)); + + CheckoutOptions options = new CheckoutOptions() + { + OnCheckoutNotify = (path, flags) => { conflictPath = path; return false; }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Conflict, + }; + + Assert.Throws(() => repo.Checkout("master", options)); Assert.Equal(relativePath, conflictPath); } } @@ -398,7 +404,7 @@ public void CheckingOutThroughBranchCallsCheckoutProgress() bool wasCalled = false; Branch branch = repo.Branches[otherBranchName]; - branch.Checkout(CheckoutModifiers.None, (path, completed, total) => wasCalled = true, null); + branch.Checkout(new CheckoutOptions() { OnCheckoutProgress = (path, completed, total) => wasCalled = true}); Assert.True(wasCalled); } @@ -414,7 +420,7 @@ public void CheckingOutThroughRepositoryCallsCheckoutProgress() PopulateBasicRepository(repo); bool wasCalled = false; - repo.Checkout(otherBranchName, CheckoutModifiers.None, (path, completed, total) => wasCalled = true, null); + repo.Checkout(otherBranchName, new CheckoutOptions() { OnCheckoutProgress = (path, completed, total) => wasCalled = true}); Assert.True(wasCalled); } @@ -486,11 +492,13 @@ public void CheckingOutCallsCheckoutNotify(CheckoutNotifyFlags notifyFlags, stri string actualNotificationPath = string.Empty; CheckoutNotifyFlags actualNotifyFlags = CheckoutNotifyFlags.None; - CheckoutNotificationOptions checkoutNotifications = new CheckoutNotificationOptions( - (path, notificationType) => { wasCalled = true; actualNotificationPath = path; actualNotifyFlags = notificationType; return true; }, - notifyFlags); + CheckoutOptions options = new CheckoutOptions() + { + OnCheckoutNotify = (path, notificationType) => { wasCalled = true; actualNotificationPath = path; actualNotifyFlags = notificationType; return true; }, + CheckoutNotifyFlags = notifyFlags, + }; - Assert.Throws(() => repo.Checkout("master", CheckoutModifiers.None, null, checkoutNotifications)); + Assert.Throws(() => repo.Checkout("master", options)); Assert.True(wasCalled); Assert.Equal(expectedNotificationPath, actualNotificationPath); @@ -538,7 +546,7 @@ public void ForceCheckoutRetainsUntrackedChanges() Assert.Equal(1, repo.Index.RetrieveStatus().Untracked.Count()); Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(fullPathFileB)); - repo.Checkout(otherBranchName, CheckoutModifiers.Force, null, null); + repo.Checkout(otherBranchName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); // Verify untracked entry still exists. Assert.Equal(1, repo.Index.RetrieveStatus().Untracked.Count()); @@ -641,7 +649,7 @@ public void ForceCheckoutRetainsIgnoredChanges() Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(ignoredFilePath)); - repo.Checkout(otherBranchName, CheckoutModifiers.Force, null, null); + repo.Checkout(otherBranchName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); // Verify that the ignored file still exists. Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(ignoredFilePath)); @@ -718,7 +726,7 @@ public void CheckingOutABranchDoesNotAlterBinaryFiles() // The blob actually exists in the object database with the correct Sha Assert.Equal(expectedSha, repo.Lookup(expectedSha).Sha); - repo.Checkout("refs/heads/logo", CheckoutModifiers.Force, null, null); + repo.Checkout("refs/heads/logo", new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); // The Index has been updated as well with the blob Assert.Equal(expectedSha, repo.Index["square-logo.png"].Id.Sha); diff --git a/LibGit2Sharp.Tests/MergeFixture.cs b/LibGit2Sharp.Tests/MergeFixture.cs index ae84fb02b..2fcf9c646 100644 --- a/LibGit2Sharp.Tests/MergeFixture.cs +++ b/LibGit2Sharp.Tests/MergeFixture.cs @@ -1,4 +1,7 @@ -using System.Linq; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; @@ -341,6 +344,122 @@ public void CanNonFastForwardMergeCommit(bool fromDetachedHead, FastForwardStrat } } + [Fact] + public void MergeReportsCheckoutProgress() + { + string repoPath = CloneMergeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Commit commitToMerge = repo.Branches["normal_merge"].Tip; + + bool wasCalled = false; + + MergeOptions options = new MergeOptions() + { + OnCheckoutProgress = (path, completed, total) => wasCalled = true, + }; + + MergeResult result = repo.Merge(commitToMerge, Constants.Signature, options); + + Assert.True(wasCalled); + } + } + + [Fact] + public void MergeReportsCheckoutNotifications() + { + string repoPath = CloneMergeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Commit commitToMerge = repo.Branches["normal_merge"].Tip; + + bool wasCalled = false; + CheckoutNotifyFlags actualNotifyFlags = CheckoutNotifyFlags.None; + + MergeOptions options = new MergeOptions() + { + OnCheckoutNotify = (path, notificationType) => { wasCalled = true; actualNotifyFlags = notificationType; return true; }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, + }; + + MergeResult result = repo.Merge(commitToMerge, Constants.Signature, options); + + Assert.True(wasCalled); + Assert.Equal(CheckoutNotifyFlags.Updated, actualNotifyFlags); + } + } + + [Fact] + public void FastForwardMergeReportsCheckoutProgress() + { + string repoPath = CloneMergeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Commit commitToMerge = repo.Branches["fast_forward"].Tip; + + bool wasCalled = false; + + MergeOptions options = new MergeOptions() + { + OnCheckoutProgress = (path, completed, total) => wasCalled = true, + }; + + MergeResult result = repo.Merge(commitToMerge, Constants.Signature, options); + + Assert.True(wasCalled); + } + } + + [Fact] + public void FastForwardMergeReportsCheckoutNotifications() + { + string repoPath = CloneMergeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Commit commitToMerge = repo.Branches["fast_forward"].Tip; + + bool wasCalled = false; + CheckoutNotifyFlags actualNotifyFlags = CheckoutNotifyFlags.None; + + MergeOptions options = new MergeOptions() + { + OnCheckoutNotify = (path, notificationType) => { wasCalled = true; actualNotifyFlags = notificationType; return true; }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, + }; + + MergeResult result = repo.Merge(commitToMerge, Constants.Signature, options); + + Assert.True(wasCalled); + Assert.Equal(CheckoutNotifyFlags.Updated, actualNotifyFlags); + } + } + + [Fact] + public void MergeCanDetectRenames() + { + // The environment is set up such that: + // file b.txt is edited in the "rename" branch and + // edited and renamed in the "rename_source" branch. + // The edits are automergable. + // We can rename "rename_source" into "rename" + // if rename detection is enabled, + // but the merge will fail with conflicts if this + // change is not detected as a rename. + + string repoPath = CloneMergeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Branch currentBranch = repo.Checkout("rename_source"); + Assert.NotNull(currentBranch); + + Branch branchToMerge = repo.Branches["rename"]; + + MergeResult result = repo.Merge(branchToMerge, Constants.Signature); + + Assert.Equal(MergeStatus.NonFastForward, result.Status); + } + } + [Fact] public void FastForwardNonFastForwardableMergeThrows() { @@ -422,6 +541,112 @@ public void CanMergeCommittish(string committish, FastForwardStrategy strategy, } } + [Theory] + [InlineData(CheckoutFileConflictStrategy.Ours)] + [InlineData(CheckoutFileConflictStrategy.Theirs)] + public void CanSpecifyConflictFileStrategy(CheckoutFileConflictStrategy conflictStrategy) + { + const string conflictFile = "a.txt"; + const string conflictBranchName = "conflicts"; + + string path = CloneMergeTestRepo(); + using (var repo = new Repository(path)) + { + Branch branch = repo.Branches[conflictBranchName]; + Assert.NotNull(branch); + + MergeOptions mergeOptions = new MergeOptions() + { + FileConflictStrategy = conflictStrategy, + }; + + MergeResult result = repo.Merge(branch, Constants.Signature, mergeOptions); + Assert.Equal(MergeStatus.Conflicts, result.Status); + + // Get the information on the conflict. + Conflict conflict = repo.Index.Conflicts[conflictFile]; + + Assert.NotNull(conflict); + Assert.NotNull(conflict.Theirs); + Assert.NotNull(conflict.Ours); + + // Get the blob containing the expected content. + Blob expectedBlob = null; + switch(conflictStrategy) + { + case CheckoutFileConflictStrategy.Theirs: + expectedBlob = repo.Lookup(conflict.Theirs.Id); + break; + case CheckoutFileConflictStrategy.Ours: + expectedBlob = repo.Lookup(conflict.Ours.Id); + break; + default: + throw new Exception("Unexpected FileConflictStrategy"); + } + + Assert.NotNull(expectedBlob); + + // Check the content of the file on disk matches what is expected. + string expectedContent = expectedBlob.GetContentText(new FilteringOptions(conflictFile)); + Assert.Equal(expectedContent, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, conflictFile))); + } + } + + [Theory] + [InlineData(MergeFileFavor.Ours)] + [InlineData(MergeFileFavor.Theirs)] + public void MergeCanSpecifyMergeFileFavorOption(MergeFileFavor fileFavorFlag) + { + const string conflictFile = "a.txt"; + const string conflictBranchName = "conflicts"; + + string path = CloneMergeTestRepo(); + using (var repo = InitIsolatedRepository(path)) + { + Branch branch = repo.Branches[conflictBranchName]; + Assert.NotNull(branch); + + var status = repo.Index.RetrieveStatus(); + MergeOptions mergeOptions = new MergeOptions() + { + MergeFileFavor = fileFavorFlag, + }; + + MergeResult result = repo.Merge(branch, Constants.Signature, mergeOptions); + + Assert.Equal(MergeStatus.NonFastForward, result.Status); + + // Verify that the index and working directory are clean + Assert.True(repo.Index.IsFullyMerged); + Assert.False(repo.Index.RetrieveStatus().IsDirty); + + // Get the blob containing the expected content. + Blob expectedBlob = null; + switch (fileFavorFlag) + { + case MergeFileFavor.Theirs: + expectedBlob = repo.Lookup("3dd9738af654bbf1c363f6c3bbc323bacdefa179"); + break; + case MergeFileFavor.Ours: + expectedBlob = repo.Lookup("610b16886ca829cebd2767d9196f3c4378fe60b5"); + break; + default: + throw new Exception("Unexpected MergeFileFavor"); + } + + Assert.NotNull(expectedBlob); + + // Verify the index has the expected contents + IndexEntry entry = repo.Index[conflictFile]; + Assert.NotNull(entry); + Assert.Equal(expectedBlob.Id, entry.Id); + + // Verify the content of the file on disk matches what is expected. + string expectedContent = expectedBlob.GetContentText(new FilteringOptions(conflictFile)); + Assert.Equal(expectedContent, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, conflictFile))); + } + } + [Theory] [InlineData("refs/heads/normal_merge", FastForwardStrategy.Default, MergeStatus.NonFastForward)] [InlineData("fast_forward", FastForwardStrategy.Default, MergeStatus.FastForward)] diff --git a/LibGit2Sharp.Tests/RepositoryFixture.cs b/LibGit2Sharp.Tests/RepositoryFixture.cs index c7d1e8140..f5916f5f2 100644 --- a/LibGit2Sharp.Tests/RepositoryFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryFixture.cs @@ -592,7 +592,7 @@ public void QueryingTheRemoteForADetachedHeadBranchReturnsNull() string path = CloneStandardTestRepo(); using (var repo = new Repository(path)) { - repo.Checkout(repo.Head.Tip.Sha, CheckoutModifiers.Force, null, null); + repo.Checkout(repo.Head.Tip.Sha, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); Branch trackLocal = repo.Head; Assert.Null(trackLocal.Remote); } diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/index index 1cadb2febe1810f3c6706dbde5bb1bd69972be3b..c8ef07b1ed7de7d421f86ef621f156ccdcf54404 100644 GIT binary patch delta 71 zcmdnMxPei^#WTp6fq{Vugo8z$<$yFpaHP(ci3!pZjg`28EQE-Sh$_<^ySlV9{{Ci3!pZjg`3JA};+C6Kq6me{Z^c)<{5h Q@k;vuxsUOg`}^!w05V|{i2wiq diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/info/refs b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/info/refs index 6568969b2..d3a074d46 100644 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/info/refs +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/info/refs @@ -2,3 +2,5 @@ 4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53 refs/heads/fast_forward 83cebf5389a4adbcb80bda6b68513caee4559802 refs/heads/master 625186280ed2a6ec9b65d250ed90cf2e4acef957 refs/heads/normal_merge +24434077dec097c1203ef9e1345c0545c190936a refs/heads/rename +2bc71d0e8acfbb9fd1cc2d9d48c23dbf8aea52c9 refs/heads/rename_source diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename new file mode 100644 index 000000000..a2466a9d2 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1398366152 -0400 branch: Created from HEAD +83cebf5389a4adbcb80bda6b68513caee4559802 9075c06ff9cd736610dea688bca7e912903ff2d1 Jameson Miller 1398366254 -0400 commit: Add content to b.txt +9075c06ff9cd736610dea688bca7e912903ff2d1 24434077dec097c1203ef9e1345c0545c190936a Jameson Miller 1398366641 -0400 commit: edit to b.txt diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename_source b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename_source new file mode 100644 index 000000000..1515dd26e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename_source @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 9075c06ff9cd736610dea688bca7e912903ff2d1 Jameson Miller 1398366267 -0400 branch: Created from rename +9075c06ff9cd736610dea688bca7e912903ff2d1 2bc71d0e8acfbb9fd1cc2d9d48c23dbf8aea52c9 Jameson Miller 1398366593 -0400 commit: rename and edit b.txt diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/info/packs b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/info/packs index 3cea2e143..2dd2e88a9 100644 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/info/packs +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/info/packs @@ -1,2 +1,2 @@ -P pack-52e214e240728e3b0ce2fc2d5e6513772fed0523.pack +P pack-f9b2f231d5e59d4a265578d02283f848a5dc4694.pack diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-52e214e240728e3b0ce2fc2d5e6513772fed0523.idx b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-52e214e240728e3b0ce2fc2d5e6513772fed0523.idx deleted file mode 100644 index a2a5e21ed332193fcb4f7112d38d1ed4e7ca2f71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1520 zcmexg;-AdGz`z8=_y8kNk`fFue=uTZpt~r+EI|F##H>K`*aiXS0J?!#%n4Kr!(2c% zKFkf2CkFEX)$sx`-zdfz2K>OVAqEQo)e*$(h10Tl1$E8a8M5+kMJK5(FL)Rw{^;rc zxut@ADUoZ#yR3`fuZv$|ZF{r0>s!d~kB5`L9o~IddDq$Z3oCtpEev2)Q}lYi*?zW; zpv$T5Z290sZn2J>6`JSvs;A$S%(ro__?NIXDX>k0@6xh2vr{hxyq$1f&+FXJ@Mhug zL;5adQ*tva%Rhas%_?C&@~`>a{@~6fYxnHnzLlL3XtVA~=nSU5x$71){Qmdtz^&;n z#}xfTazi&&PTaD<<#pr7lGSNb%C1U&thgv@_i5%PlZtEMsUEgr`u9KeoBupl@Vjd9 znPaKOleC#ydsn{om~(M%xNUO95(evkGcNvN>rAuczr8wl&gFTo%uA!A4}JdKx&7$w zymP%vSfti*d%gn4jUl?E@X`ljk+mO=&Cn_UGamOBv0CJag6E8=CuGShW6a z>B@2v-)r$H|FGc2Z(sDk8M|Hm{PQC3ogIyxZ`GS`tXm`Mde^u0l8Z{Pm)qkfUnE6i z#ENT9WM){Y?vp*XOzDCzuy~b(6eUcrfb3czdp0oMHUf($9Uu+N56oA9xEENAE(T&H zpnND0-vo*Q)w0T4x%|rHe!NG>psCR6_5uH$2fqbuWIS+!JvOlS4Bv|=**?M4wkFqW F2LK=c>H7cx diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-52e214e240728e3b0ce2fc2d5e6513772fed0523.pack b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-52e214e240728e3b0ce2fc2d5e6513772fed0523.pack deleted file mode 100644 index faf8cbdd695ef6ac92464c766191cd617b464c7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1402 zcmWG=boORoU|<4bfoc2|bLO6L^kY@zX?SS6`~|BCPc7@L8RFN~*4a2}s`gd#v>p6y z-DzI%{ds&%_N+z$Q7tK{ZIgDYEDc(;ap8Jt)4Zizzh<1NJ-sElalxu%9vw1H9~Fbw z9SX~zxiv3FGqHEZrpfcaTB_~w++Hm6VV}L6*~6#aze3m7Ubj4V@!ICTx^`a4o9tfZ z6`!8$-JN&;`lfp3cZW-4ME@!5XwZB9bZ7i#W}nCJSsPR<=I|ajkqKL!)*rRtH9wwW#O8nBz5UhIc)zzIU9g#(x zT%`q?!;W9F z(|0ph%+Wr1LMM!2(UvoFLRww+PU;e})jKcL+H)j{Rk?M}nLSsMT951zX?1CR@@LPM zIcJ_sID8j+3%nJY3sDP(+sUn{X1v$?9$Aqj1k=O zVrhXDbGo<8N;tWKoq;Wqu?NGaFJ|w~Gd%ph{yyKGS?`K`F50V@mjo|Wba51lX#BBw z`S0h`xV|lQS;zZ+lV06^y}C|QhN>Xx?^~FN@TsjH!y`@ZS^GeKIt5JS20W}6YyCg^ zPv~WzwBjPmEsAbVHz>=TV_kjjUH=n!Nt0g$7uD`n^L}^v#6i zU+-?P{a*LMPU`yBlRH}CzMfM!kKqrGCt|NpL@un%TVM6d_hgOq^R9Z%XkJCGNe8%Q zwI1ewxqFK5$+K&8{(sqOwz}o#344Z@DUwe6h>kF!6`LmYgtX2%@ZAX6^7?3#6KJV*jCCV*Rn8F_Df&2a`~0X{dkX%K~tgC?F0Th4}J^S2msoUk_P|) diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-f9b2f231d5e59d4a265578d02283f848a5dc4694.idx b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-f9b2f231d5e59d4a265578d02283f848a5dc4694.idx new file mode 100644 index 0000000000000000000000000000000000000000..72d56baa56598115a64bcac23281fea32247815a GIT binary patch literal 1828 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 VtGPmy+F8uV@mo8{iphWE{1^YHjdTD2 literal 0 HcmV?d00001 diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/packed-refs b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/packed-refs index c952e5bc6..7930b20e4 100644 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/packed-refs +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/packed-refs @@ -3,3 +3,5 @@ 4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53 refs/heads/fast_forward 83cebf5389a4adbcb80bda6b68513caee4559802 refs/heads/master 625186280ed2a6ec9b65d250ed90cf2e4acef957 refs/heads/normal_merge +24434077dec097c1203ef9e1345c0545c190936a refs/heads/rename +2bc71d0e8acfbb9fd1cc2d9d48c23dbf8aea52c9 refs/heads/rename_source diff --git a/LibGit2Sharp/Branch.cs b/LibGit2Sharp/Branch.cs index e13c5c578..1e64b8b9c 100644 --- a/LibGit2Sharp/Branch.cs +++ b/LibGit2Sharp/Branch.cs @@ -233,9 +233,37 @@ public virtual void Checkout() /// Options controlling checkout behavior. /// Callback method to report checkout progress updates through. /// to manage checkout notifications. + [Obsolete("This overload will be removed in the next release. Please use Branch.Checkout(CheckoutOptions, Signature) instead.")] public virtual void Checkout(CheckoutModifiers checkoutModifiers, CheckoutProgressHandler onCheckoutProgress, CheckoutNotificationOptions checkoutNotificationOptions) { - repo.Checkout(this, checkoutModifiers, onCheckoutProgress, checkoutNotificationOptions); + var options = new CheckoutOptions + { + CheckoutModifiers = checkoutModifiers, + OnCheckoutProgress = onCheckoutProgress, + }; + + if (checkoutNotificationOptions != null) + { + options.OnCheckoutNotify = checkoutNotificationOptions.CheckoutNotifyHandler; + options.CheckoutNotifyFlags = checkoutNotificationOptions.NotifyFlags; + } + + Checkout(options, null); + } + + ///

+ /// Checkout the tip commit of this object with + /// parameter specifying checkout + /// behavior. If this commit is the current tip of the branch, will + /// checkout the named branch. Otherwise, will checkout the tip + /// commit as a detached HEAD. + /// + /// controlling checkout behavior. + /// Identity for use when updating the reflog. + public virtual void Checkout(CheckoutOptions options, Signature signature = null) + { + Ensure.ArgumentNotNull(options, "options"); + repo.Checkout(this, options, signature); } private Branch ResolveTrackedBranch() diff --git a/LibGit2Sharp/CheckoutCallbacks.cs b/LibGit2Sharp/CheckoutCallbacks.cs index fa8817b81..dc03846bf 100644 --- a/LibGit2Sharp/CheckoutCallbacks.cs +++ b/LibGit2Sharp/CheckoutCallbacks.cs @@ -69,7 +69,7 @@ public checkout_notify_cb CheckoutNotifyCallback /// that should be wrapped in the native callback. /// delegate to call in response to checkout notification callback. /// The delegate with signature matching the expected native callback. - internal static CheckoutCallbacks GenerateCheckoutCallbacks(CheckoutProgressHandler onCheckoutProgress, CheckoutNotifyHandler onCheckoutNotify) + internal static CheckoutCallbacks From(CheckoutProgressHandler onCheckoutProgress, CheckoutNotifyHandler onCheckoutNotify) { return new CheckoutCallbacks(onCheckoutProgress, onCheckoutNotify); } diff --git a/LibGit2Sharp/CheckoutFileConflictStrategy.cs b/LibGit2Sharp/CheckoutFileConflictStrategy.cs new file mode 100644 index 000000000..578ebe03e --- /dev/null +++ b/LibGit2Sharp/CheckoutFileConflictStrategy.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// Enum specifying what content checkout should write to disk + /// for conflicts. + /// + public enum CheckoutFileConflictStrategy + { + /// + /// Use the default behavior for handling file conflicts. This is + /// controlled by the merge.conflictstyle config option, and is "Merge" + /// if no option is explicitly set. + /// + Normal, + + /// + /// For conflicting files, checkout the "ours" (stage 2) version of + /// the file from the index. + /// + Ours, + + /// + /// For conflicting files, checkout the "theirs" (stage 3) version of + /// the file from the index. + /// + Theirs, + + /// + /// Write normal merge files for conflicts. + /// + Merge, + + /// + /// Write diff3 formated files for conflicts. + /// + Diff3 + } +} diff --git a/LibGit2Sharp/CheckoutNotificationOptions.cs b/LibGit2Sharp/CheckoutNotificationOptions.cs index 97b5012c0..c1d0a1672 100644 --- a/LibGit2Sharp/CheckoutNotificationOptions.cs +++ b/LibGit2Sharp/CheckoutNotificationOptions.cs @@ -45,6 +45,7 @@ public enum CheckoutNotifyFlags /// /// Class to specify options and callback on CheckoutNotifications. /// + [Obsolete("This class will be removed in the next release. Specify CheckoutNotification options through CheckoutOptions instead.")] public class CheckoutNotificationOptions { /// diff --git a/LibGit2Sharp/CheckoutOptions.cs b/LibGit2Sharp/CheckoutOptions.cs index 69fd04398..9e297cd47 100644 --- a/LibGit2Sharp/CheckoutOptions.cs +++ b/LibGit2Sharp/CheckoutOptions.cs @@ -1,11 +1,12 @@ -using LibGit2Sharp.Handlers; +using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; namespace LibGit2Sharp { /// /// Collection of parameters controlling Checkout behavior. /// - public sealed class CheckoutOptions + public sealed class CheckoutOptions : IConvertableToGitCheckoutOpts { /// /// Options controlling checkout behavior. @@ -13,13 +14,39 @@ public sealed class CheckoutOptions public CheckoutModifiers CheckoutModifiers { get; set; } /// - /// Callback method to report checkout progress updates through. + /// The flags specifying what conditions are + /// reported through the OnCheckoutNotify delegate. /// + public CheckoutNotifyFlags CheckoutNotifyFlags { get; set; } + + /// + /// Delegate to be called during checkout for files that match + /// desired filter specified with the NotifyFlags property. + /// + public CheckoutNotifyHandler OnCheckoutNotify { get; set; } + + /// Delegate through which checkout will notify callers of + /// certain conditions. The conditions that are reported is + /// controlled with the CheckoutNotifyFlags property. public CheckoutProgressHandler OnCheckoutProgress { get; set; } + CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy + { + get + { + return CheckoutModifiers.HasFlag(CheckoutModifiers.Force) ? + CheckoutStrategy.GIT_CHECKOUT_FORCE : CheckoutStrategy.GIT_CHECKOUT_SAFE; + } + } + /// - /// Options to manage checkout notifications. + /// Generate a object with the delegates + /// hooked up to the native callbacks. /// - public CheckoutNotificationOptions CheckoutNotificationOptions { get; set; } + /// + CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks() + { + return CheckoutCallbacks.From(OnCheckoutProgress, OnCheckoutNotify); + } } } diff --git a/LibGit2Sharp/CloneOptions.cs b/LibGit2Sharp/CloneOptions.cs index 09e513f75..65b98bd04 100644 --- a/LibGit2Sharp/CloneOptions.cs +++ b/LibGit2Sharp/CloneOptions.cs @@ -1,11 +1,12 @@ -using LibGit2Sharp.Handlers; +using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; namespace LibGit2Sharp { /// /// Options to define clone behaviour /// - public sealed class CloneOptions + public sealed class CloneOptions : IConvertableToGitCheckoutOpts { /// /// Creates default for a non-bare clone @@ -40,5 +41,29 @@ public CloneOptions() /// Credentials to use for user/pass authentication /// public Credentials Credentials { get; set; } + + #region IConvertableToGitCheckoutOpts + + CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks() + { + return CheckoutCallbacks.From(OnCheckoutProgress, null); + } + + CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy + { + get + { + return this.Checkout ? + CheckoutStrategy.GIT_CHECKOUT_SAFE_CREATE : + CheckoutStrategy.GIT_CHECKOUT_NONE; + } + } + + CheckoutNotifyFlags IConvertableToGitCheckoutOpts.CheckoutNotifyFlags + { + get { return CheckoutNotifyFlags.None; } + } + + #endregion } } diff --git a/LibGit2Sharp/Core/GitCheckoutOpts.cs b/LibGit2Sharp/Core/GitCheckoutOpts.cs index ca6943959..214251d60 100644 --- a/LibGit2Sharp/Core/GitCheckoutOpts.cs +++ b/LibGit2Sharp/Core/GitCheckoutOpts.cs @@ -120,7 +120,7 @@ internal delegate void progress_cb( IntPtr payload); [StructLayout(LayoutKind.Sequential)] - internal struct GitCheckoutOpts :IDisposable + internal struct GitCheckoutOpts { public uint version; @@ -146,15 +146,18 @@ internal struct GitCheckoutOpts :IDisposable public IntPtr ancestor_label; public IntPtr our_label; public IntPtr their_label; + } + + /// + /// An inteface for objects that specify parameters from which a + /// GitCheckoutOpts struct can be populated. + /// + internal interface IConvertableToGitCheckoutOpts + { + CheckoutCallbacks GenerateCallbacks(); - public void Dispose() - { - if (paths == null) - { - return; - } + CheckoutStrategy CheckoutStrategy { get; } - paths.Dispose(); - } + CheckoutNotifyFlags CheckoutNotifyFlags { get; } } } diff --git a/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs b/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs new file mode 100644 index 000000000..b98a5cd96 --- /dev/null +++ b/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LibGit2Sharp.Core +{ + /// + /// A wrapper around the native GitCheckoutOpts structure. This class is responsible + /// for the managed objects that the native code points to. + /// + internal class GitCheckoutOptsWrapper : IDisposable + { + /// + /// Create wrapper around from . + /// + /// Options to create native GitCheckoutOpts structure from. + /// Paths to checkout. + public GitCheckoutOptsWrapper(IConvertableToGitCheckoutOpts options, FilePath[] paths = null) + { + Callbacks = options.GenerateCallbacks(); + + if (paths != null) + { + PathArray = GitStrArrayIn.BuildFrom(paths); + } + + Options = new GitCheckoutOpts + { + version = 1, + checkout_strategy = options.CheckoutStrategy, + progress_cb = Callbacks.CheckoutProgressCallback, + notify_cb = Callbacks.CheckoutNotifyCallback, + notify_flags = options.CheckoutNotifyFlags, + paths = PathArray, + }; + } + + /// + /// Native struct to pass to libgit. + /// + public GitCheckoutOpts Options { get; set; } + + /// + /// The managed class mapping native callbacks into the + /// corresponding managed delegate. + /// + public CheckoutCallbacks Callbacks { get; private set; } + + /// + /// Keep the paths around so we can dispose them. + /// + private GitStrArrayIn PathArray; + + public void Dispose() + { + Dispose(true); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + if (PathArray != null) + { + PathArray.Dispose(); + PathArray = null; + } + } + } + + /// + /// Method to translate from to flags. + /// + internal static CheckoutStrategy CheckoutStrategyFromFileConflictStrategy(CheckoutFileConflictStrategy fileConflictStrategy) + { + CheckoutStrategy flags = default(CheckoutStrategy); + + switch (fileConflictStrategy) + { + case CheckoutFileConflictStrategy.Ours: + flags = CheckoutStrategy.GIT_CHECKOUT_USE_OURS; + break; + case CheckoutFileConflictStrategy.Theirs: + flags = CheckoutStrategy.GIT_CHECKOUT_USE_THEIRS; + break; + case CheckoutFileConflictStrategy.Merge: + flags = CheckoutStrategy.GIT_CHECKOUT_CONFLICT_STYLE_MERGE; + break; + case CheckoutFileConflictStrategy.Diff3: + flags = CheckoutStrategy.GIT_CHECKOUT_CONFLICT_STYLE_DIFF3; + break; + } + + return flags; + } + } +} diff --git a/LibGit2Sharp/Core/GitMergeOpts.cs b/LibGit2Sharp/Core/GitMergeOpts.cs index 36ccd45fa..0c677817b 100644 --- a/LibGit2Sharp/Core/GitMergeOpts.cs +++ b/LibGit2Sharp/Core/GitMergeOpts.cs @@ -30,7 +30,7 @@ internal struct GitMergeOpts /// /// Flags for automerging content. /// - public GitMergeFileFavorFlags MergeFileFavorFlags; + public MergeFileFavor MergeFileFavorFlags; } /// @@ -84,12 +84,4 @@ internal enum GitMergeTreeFlags /// GIT_MERGE_TREE_FIND_RENAMES = (1 << 0), } - - internal enum GitMergeFileFavorFlags - { - GIT_MERGE_FILE_FAVOR_NORMAL = 0, - GIT_MERGE_FILE_FAVOR_OURS = 1, - GIT_MERGE_FILE_FAVOR_THEIRS = 2, - GIT_MERGE_FILE_FAVOR_UNION = 3, - } } diff --git a/LibGit2Sharp/IRepository.cs b/LibGit2Sharp/IRepository.cs index 44e40d2aa..28ce29892 100644 --- a/LibGit2Sharp/IRepository.cs +++ b/LibGit2Sharp/IRepository.cs @@ -83,6 +83,7 @@ public interface IRepository : IDisposable /// to manage checkout notifications. /// Identity for use when updating the reflog. /// The that was checked out. + [Obsolete("This overload will be removed in the next release. Please use Repository.Checkout(Branch, CheckoutOptions, Signature) instead.")] Branch Checkout(Branch branch, CheckoutModifiers checkoutModifiers, CheckoutProgressHandler onCheckoutProgress, CheckoutNotificationOptions checkoutNotificationOptions, Signature signature = null); /// @@ -98,6 +99,7 @@ public interface IRepository : IDisposable /// to manage checkout notifications. /// Identity for use when updating the reflog. /// The that was checked out. + [Obsolete("This overload will be removed in the next release. Please use Repository.Checkout(string, CheckoutOptions, Signature) instead.")] Branch Checkout(string committishOrBranchSpec, CheckoutModifiers checkoutModifiers, CheckoutProgressHandler onCheckoutProgress, CheckoutNotificationOptions checkoutNotificationOptions, Signature signature = null); /// @@ -112,8 +114,47 @@ public interface IRepository : IDisposable /// to manage checkout notifications. /// Identity for use when updating the reflog. /// The that was checked out. + [Obsolete("This overload will be removed in the next release. Please use Repository.Checkout(Commit, CheckoutOptions, Signature) instead.")] Branch Checkout(Commit commit, CheckoutModifiers checkoutModifiers, CheckoutProgressHandler onCheckoutProgress, CheckoutNotificationOptions checkoutNotificationOptions, Signature signature = null); + /// + /// Checkout the commit pointed at by the tip of the specified . + /// + /// If this commit is the current tip of the branch as it exists in the repository, the HEAD + /// will point to this branch. Otherwise, the HEAD will be detached, pointing at the commit sha. + /// + /// + /// The to check out. + /// controlling checkout behavior. + /// Identity for use when updating the reflog. + /// The that was checked out. + Branch Checkout(Branch branch, CheckoutOptions options, Signature signature = null); + + /// + /// Checkout the specified branch, reference or SHA. + /// + /// If the committishOrBranchSpec parameter resolves to a branch name, then the checked out HEAD will + /// will point to the branch. Otherwise, the HEAD will be detached, pointing at the commit sha. + /// + /// + /// A revparse spec for the commit or branch to checkout. + /// controlling checkout behavior. + /// Identity for use when updating the reflog. + /// The that was checked out. + Branch Checkout(string committishOrBranchSpec, CheckoutOptions options, Signature signature = null); + + /// + /// Checkout the specified . + /// + /// Will detach the HEAD and make it point to this commit sha. + /// + /// + /// The to check out. + /// controlling checkout behavior. + /// Identity for use when updating the reflog. + /// The that was checked out. + Branch Checkout(Commit commit, CheckoutOptions options, Signature signature = null); + /// /// Updates specifed paths in the index and working directory with the versions from the specified branch, reference, or SHA. /// diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 566e40050..e82619627 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -69,6 +69,7 @@ + @@ -79,6 +80,7 @@ + diff --git a/LibGit2Sharp/MergeOptions.cs b/LibGit2Sharp/MergeOptions.cs index 06eb13cea..be25665ee 100644 --- a/LibGit2Sharp/MergeOptions.cs +++ b/LibGit2Sharp/MergeOptions.cs @@ -1,21 +1,39 @@ - +using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; + namespace LibGit2Sharp { /// /// Options controlling Merge behavior. /// - public sealed class MergeOptions + public sealed class MergeOptions : IConvertableToGitCheckoutOpts { /// /// Initializes a new instance of the class. - /// By default, a fast-forward merge will be performed if possible, and - /// if a merge commit is created, then it will be commited. + /// + /// Default behavior: + /// A fast-forward merge will be performed if possible. + /// A merge commit will be committed, if one was created. + /// Merge will attempt to find renames. + /// /// public MergeOptions() { CommitOnSuccess = true; + + FindRenames = true; + // TODO: libgit2 should provide reasonable defaults for these + // values, but it currently does not. + RenameThreshold = 50; + TargetLimit = 200; } + /// + /// The Flags specifying what conditions are + /// reported through the OnCheckoutNotify delegate. + /// + public CheckoutNotifyFlags CheckoutNotifyFlags { get; set; } + /// /// Commit the merge if the merge is successful and this is a non-fast-forward merge. /// If this is a fast-forward merge, then there is no merge commit and this option @@ -27,6 +45,63 @@ public MergeOptions() /// The type of merge to perform. /// public FastForwardStrategy FastForwardStrategy { get; set; } + + /// + /// How conflicting index entries should be written out during checkout. + /// + public CheckoutFileConflictStrategy FileConflictStrategy { get; set; } + + /// + /// Find renames. Default is true. + /// + public bool FindRenames { get; set; } + + /// + /// Similarity to consider a file renamed. + /// + public int RenameThreshold; + + /// + /// Maximum similarity sources to examine (overrides + /// 'merge.renameLimit' config (default 200) + /// + public int TargetLimit; + + /// + /// How to handle conflicts encountered during a merge. + /// + public MergeFileFavor MergeFileFavor { get; set; } + + /// + /// Delegate that the checkout will report progress through. + /// + public CheckoutProgressHandler OnCheckoutProgress { get; set; } + + /// + /// Delegate that checkout will notify callers of + /// certain conditions. The conditions that are reported is + /// controlled with the CheckoutNotifyFlags property. + /// + public CheckoutNotifyHandler OnCheckoutNotify { get; set; } + + #region IConvertableToGitCheckoutOpts + + CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks() + { + return CheckoutCallbacks.From(OnCheckoutProgress, OnCheckoutNotify); + } + + CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy + { + get + { + return CheckoutStrategy.GIT_CHECKOUT_SAFE| + CheckoutStrategy.GIT_CHECKOUT_ALLOW_CONFLICTS | + GitCheckoutOptsWrapper.CheckoutStrategyFromFileConflictStrategy(FileConflictStrategy); + } + } + + #endregion } /// @@ -51,4 +126,41 @@ public enum FastForwardStrategy /// FastForwardOnly = 2, /* GIT_MERGE_FASTFORWARD_ONLY */ } + + /// + /// Enum specifying how merge should deal with conflicting regions + /// of the files. + /// + public enum MergeFileFavor + { + /// + /// When a region of a file is changed in both branches, a conflict + /// will be recorded in the index so that the checkout operation can produce + /// a merge file with conflict markers in the working directory. + /// This is the default. + /// + Normal = 0, + + /// + /// When a region of a file is changed in both branches, the file + /// created in the index will contain the "ours" side of any conflicting + /// region. The index will not record a conflict. + /// + Ours = 1, + + /// + /// When a region of a file is changed in both branches, the file + /// created in the index will contain the "theirs" side of any conflicting + /// region. The index will not record a conflict. + /// + Theirs = 2, + + /// + /// When a region of a file is changed in both branches, the file + /// created in the index will contain each unique line from each side, + /// which has the result of combining both files. The index will not + /// record a conflict. + /// + Union = 3, + } } diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index ce99a25e0..36ffc279a 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -549,36 +549,29 @@ public static string Clone(string sourceUrl, string workdirPath, { options = options ?? new CloneOptions(); - CheckoutCallbacks checkoutCallbacks = CheckoutCallbacks.GenerateCheckoutCallbacks( - options.OnCheckoutProgress, null); + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + { + var gitCheckoutOptions = checkoutOptionsWrapper.Options; - var callbacks = new RemoteCallbacks(null, options.OnTransferProgress, null, - options.Credentials); - GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); + var remoteCallbacks = new RemoteCallbacks(null, options.OnTransferProgress, null, options.Credentials); + var gitRemoteCallbacks = remoteCallbacks.GenerateCallbacks(); - var cloneOpts = new GitCloneOptions - { - Version = 1, - Bare = options.IsBare ? 1 : 0, - CheckoutOpts = + var cloneOpts = new GitCloneOptions { - version = 1, - progress_cb = - checkoutCallbacks.CheckoutProgressCallback, - checkout_strategy = options.Checkout - ? CheckoutStrategy.GIT_CHECKOUT_SAFE_CREATE - : CheckoutStrategy.GIT_CHECKOUT_NONE - }, - RemoteCallbacks = gitCallbacks, - }; + Version = 1, + Bare = options.IsBare ? 1 : 0, + CheckoutOpts = gitCheckoutOptions, + RemoteCallbacks = gitRemoteCallbacks, + }; - FilePath repoPath; - using (RepositorySafeHandle repo = Proxy.git_clone(sourceUrl, workdirPath, ref cloneOpts)) - { - repoPath = Proxy.git_repository_path(repo); - } + FilePath repoPath; + using (RepositorySafeHandle repo = Proxy.git_clone(sourceUrl, workdirPath, ref cloneOpts)) + { + repoPath = Proxy.git_repository_path(repo); + } - return repoPath.Native; + return repoPath.Native; + } } /// @@ -605,9 +598,39 @@ public BlameHunkCollection Blame(string path, BlameOptions options = null) /// to manage checkout notifications. /// Identity for use when updating the reflog. /// The that was checked out. + [Obsolete("This overload will be removed in the next release. Please use Repository.Checkout(string, CheckoutOptions, Signature) instead.")] public Branch Checkout(string committishOrBranchSpec, CheckoutModifiers checkoutModifiers, CheckoutProgressHandler onCheckoutProgress, CheckoutNotificationOptions checkoutNotifications, Signature signature = null) + { + var options = new CheckoutOptions() + { + CheckoutModifiers = checkoutModifiers, + OnCheckoutProgress = onCheckoutProgress + }; + + if (checkoutNotifications != null) + { + options.OnCheckoutNotify = checkoutNotifications.CheckoutNotifyHandler; + options.CheckoutNotifyFlags = checkoutNotifications.NotifyFlags; + } + + return Checkout(committishOrBranchSpec, options, signature); + } + + /// + /// Checkout the specified , reference or SHA. + /// + /// If the committishOrBranchSpec parameter resolves to a branch name, then the checked out HEAD will + /// will point to the branch. Otherwise, the HEAD will be detached, pointing at the commit sha. + /// + /// + /// A revparse spec for the commit or branch to checkout. + /// controlling checkout behavior. + /// Identity for use when updating the reflog. + /// The that was checked out. + public Branch Checkout(string committishOrBranchSpec, CheckoutOptions options, Signature signature = null) { Ensure.ArgumentNotNullOrEmptyString(committishOrBranchSpec, "committishOrBranchSpec"); + Ensure.ArgumentNotNull(options, "options"); var handles = Proxy.git_revparse_ext(Handle, committishOrBranchSpec); if (handles == null) @@ -626,7 +649,7 @@ public Branch Checkout(string committishOrBranchSpec, CheckoutModifiers checkout if (reference.IsLocalBranch()) { Branch branch = Branches[reference.CanonicalName]; - return Checkout(branch, checkoutModifiers, onCheckoutProgress, checkoutNotifications); + return Checkout(branch, options, signature); } } @@ -640,7 +663,7 @@ public Branch Checkout(string committishOrBranchSpec, CheckoutModifiers checkout } Commit commit = obj.DereferenceToCommit(true); - Checkout(commit.Tree, checkoutModifiers, onCheckoutProgress, checkoutNotifications, commit.Id.Sha, committishOrBranchSpec, signature); + Checkout(commit.Tree, options, commit.Id.Sha, committishOrBranchSpec, signature); return Head; } @@ -656,10 +679,40 @@ public Branch Checkout(string committishOrBranchSpec, CheckoutModifiers checkout /// to manage checkout notifications. /// Identity for use when updating the reflog. /// The that was checked out. + [Obsolete("This overload will be removed in the next release. Please use Repository.Checkout(Branch, CheckoutOptions, Signature) instead.")] public Branch Checkout(Branch branch, CheckoutModifiers checkoutModifiers, CheckoutProgressHandler onCheckoutProgress, CheckoutNotificationOptions checkoutNotificationOptions, Signature signature = null) { Ensure.ArgumentNotNull(branch, "branch"); + var options = new CheckoutOptions + { + CheckoutModifiers = checkoutModifiers, + OnCheckoutProgress = onCheckoutProgress, + }; + + if (checkoutNotificationOptions != null) + { + options.OnCheckoutNotify = checkoutNotificationOptions.CheckoutNotifyHandler; + options.CheckoutNotifyFlags = checkoutNotificationOptions.NotifyFlags; + } + + return Checkout(branch, options, signature); + } + + /// + /// Checkout the tip commit of the specified object. If this commit is the + /// current tip of the branch, will checkout the named branch. Otherwise, will checkout the tip commit + /// as a detached HEAD. + /// + /// The to check out. + /// controlling checkout behavior. + /// Identity for use when updating the reflog. + /// The that was checked out. + public Branch Checkout(Branch branch, CheckoutOptions options, Signature signature = null) + { + Ensure.ArgumentNotNull(branch, "branch"); + Ensure.ArgumentNotNull(options, "options"); + // Make sure this is not an unborn branch. if (branch.Tip == null) { @@ -672,11 +725,11 @@ public Branch Checkout(Branch branch, CheckoutModifiers checkoutModifiers, Check string.Equals(Refs[branch.CanonicalName].TargetIdentifier, branch.Tip.Id.Sha, StringComparison.OrdinalIgnoreCase)) { - Checkout(branch.Tip.Tree, checkoutModifiers, onCheckoutProgress, checkoutNotificationOptions, branch.CanonicalName, branch.Name, signature); + Checkout(branch.Tip.Tree, options, branch.CanonicalName, branch.Name, signature); } else { - Checkout(branch.Tip.Tree, checkoutModifiers, onCheckoutProgress, checkoutNotificationOptions, branch.Tip.Id.Sha, branch.Name, signature); + Checkout(branch.Tip.Tree, options, branch.Tip.Id.Sha, branch.Name, signature); } return Head; @@ -694,9 +747,43 @@ public Branch Checkout(Branch branch, CheckoutModifiers checkoutModifiers, Check /// to manage checkout notifications. /// Identity for use when updating the reflog. /// The that was checked out. + [Obsolete("This overload will be removed in the next release. Please use Repository.Checkout(Commit, CheckoutOptions, Signature) instead.")] public Branch Checkout(Commit commit, CheckoutModifiers checkoutModifiers, CheckoutProgressHandler onCheckoutProgress, CheckoutNotificationOptions checkoutNotificationOptions, Signature signature = null) { - Checkout(commit.Tree, checkoutModifiers, onCheckoutProgress, checkoutNotificationOptions, commit.Id.Sha, commit.Id.Sha, signature); + + var options = new CheckoutOptions + { + CheckoutModifiers = checkoutModifiers, + OnCheckoutProgress = onCheckoutProgress, + }; + + if (checkoutNotificationOptions != null) + { + options.OnCheckoutNotify = checkoutNotificationOptions.CheckoutNotifyHandler; + options.CheckoutNotifyFlags = checkoutNotificationOptions.NotifyFlags; + } + + Checkout(commit.Tree, options, commit.Id.Sha, commit.Id.Sha, signature); + + return Head; + } + + /// + /// Checkout the specified . + /// + /// Will detach the HEAD and make it point to this commit sha. + /// + /// + /// The to check out. + /// controlling checkout behavior. + /// Identity for use when updating the reflog. + /// The that was checked out. + public Branch Checkout(Commit commit, CheckoutOptions options, Signature signature = null) + { + Ensure.ArgumentNotNull(commit, "commit"); + Ensure.ArgumentNotNull(options, "options"); + + Checkout(commit.Tree, options, commit.Id.Sha, commit.Id.Sha, signature); return Head; } @@ -706,29 +793,18 @@ public Branch Checkout(Commit commit, CheckoutModifiers checkoutModifiers, Check /// to already be in the form of a canonical branch name or a commit ID. /// /// The to checkout. - /// controlling checkout behavior. - /// that checkout progress is reported through. - /// to manage checkout notifications. + /// controlling checkout behavior. /// Target for the new HEAD. /// The spec which will be written as target in the reflog. /// Identity for use when updating the reflog. private void Checkout( Tree tree, - CheckoutModifiers checkoutModifiers, - CheckoutProgressHandler onCheckoutProgress, - CheckoutNotificationOptions checkoutNotificationOptions, + CheckoutOptions checkoutOptions, string headTarget, string refLogHeadSpec, Signature signature) { var previousHeadName = Info.IsHeadDetached ? Head.Tip.Sha : Head.Name; - var opts = new CheckoutOptions - { - CheckoutModifiers = checkoutModifiers, - OnCheckoutProgress = onCheckoutProgress, - CheckoutNotificationOptions = checkoutNotificationOptions - }; - - CheckoutTree(tree, null, opts); + CheckoutTree(tree, null, checkoutOptions); Refs.UpdateTarget("HEAD", headTarget, signature, string.Format( @@ -745,37 +821,14 @@ private void Checkout( private void CheckoutTree( Tree tree, IList paths, - CheckoutOptions opts) + IConvertableToGitCheckoutOpts opts) { - CheckoutNotifyHandler onCheckoutNotify = opts.CheckoutNotificationOptions != null ? opts.CheckoutNotificationOptions.CheckoutNotifyHandler : null; - CheckoutNotifyFlags checkoutNotifyFlags = opts.CheckoutNotificationOptions != null ? opts.CheckoutNotificationOptions.NotifyFlags : default(CheckoutNotifyFlags); - CheckoutCallbacks checkoutCallbacks = CheckoutCallbacks.GenerateCheckoutCallbacks(opts.OnCheckoutProgress, onCheckoutNotify); - - GitStrArrayIn strArray = (paths != null && paths.Count > 0) ? GitStrArrayIn.BuildFrom(ToFilePaths(paths)) : null; - - var options = new GitCheckoutOpts - { - version = 1, - checkout_strategy = CheckoutStrategy.GIT_CHECKOUT_SAFE, - progress_cb = checkoutCallbacks.CheckoutProgressCallback, - notify_cb = checkoutCallbacks.CheckoutNotifyCallback, - notify_flags = checkoutNotifyFlags, - paths = strArray - }; - try + using(GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(opts, ToFilePaths(paths))) { - if (opts.CheckoutModifiers.HasFlag(CheckoutModifiers.Force)) - { - options.checkout_strategy = CheckoutStrategy.GIT_CHECKOUT_FORCE; - } - + var options = checkoutOptionsWrapper.Options; Proxy.git_checkout_tree(Handle, tree.Id, ref options); } - finally - { - options.Dispose(); - } } /// @@ -977,14 +1030,7 @@ public void RemoveUntrackedFiles() | CheckoutStrategy.GIT_CHECKOUT_ALLOW_CONFLICTS, }; - try - { - Proxy.git_checkout_index(Handle, new NullGitObjectSafeHandle(), ref options); - } - finally - { - options.Dispose(); - } + Proxy.git_checkout_index(Handle, new NullGitObjectSafeHandle(), ref options); } private void CleanupDisposableDependencies() @@ -1168,7 +1214,7 @@ private MergeResult Merge(GitMergeHeadHandle[] mergeHeads, Signature merger, Mer throw new LibGit2SharpException("Unable to perform Fast-Forward merge with mith multiple merge heads."); } - mergeResult = FastForwardMerge(mergeHeads[0], merger); + mergeResult = FastForwardMerge(mergeHeads[0], merger, options); } else if (mergeAnalysis.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_NORMAL)) { @@ -1184,7 +1230,7 @@ private MergeResult Merge(GitMergeHeadHandle[] mergeHeads, Signature merger, Mer throw new LibGit2SharpException("Unable to perform Fast-Forward merge with mith multiple merge heads."); } - mergeResult = FastForwardMerge(mergeHeads[0], merger); + mergeResult = FastForwardMerge(mergeHeads[0], merger, options); } else { @@ -1226,15 +1272,20 @@ private MergeResult NormalMerge(GitMergeHeadHandle[] mergeHeads, Signature merge var mergeOptions = new GitMergeOpts { - Version = 1 + Version = 1, + MergeFileFavorFlags = options.MergeFileFavor, + MergeTreeFlags = options.FindRenames ? GitMergeTreeFlags.GIT_MERGE_TREE_FIND_RENAMES : + GitMergeTreeFlags.GIT_MERGE_TREE_NORMAL, + RenameThreshold = (uint) options.RenameThreshold, + TargetLimit = (uint) options.TargetLimit, }; - var checkoutOpts = new GitCheckoutOpts + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) { - version = 1 - }; + var checkoutOpts = checkoutOptionsWrapper.Options; - Proxy.git_merge(Handle, mergeHeads, mergeOptions, checkoutOpts); + Proxy.git_merge(Handle, mergeHeads, mergeOptions, checkoutOpts); + } if (Index.IsFullyMerged) { @@ -1260,18 +1311,15 @@ private MergeResult NormalMerge(GitMergeHeadHandle[] mergeHeads, Signature merge /// /// The merge head handle to fast-forward merge. /// The of who is performing the merge. + /// Options controlling merge behavior. /// The of the merge. - private MergeResult FastForwardMerge(GitMergeHeadHandle mergeHead, Signature merger) + private MergeResult FastForwardMerge(GitMergeHeadHandle mergeHead, Signature merger, MergeOptions options) { ObjectId id = Proxy.git_merge_head_id(mergeHead); Commit fastForwardCommit = (Commit) Lookup(id, ObjectType.Commit); Ensure.GitObjectIsNotNull(fastForwardCommit, id.Sha); - var checkoutOpts = new CheckoutOptions - { - CheckoutModifiers = CheckoutModifiers.None, - }; - CheckoutTree(fastForwardCommit.Tree, null, checkoutOpts); + CheckoutTree(fastForwardCommit.Tree, null, options); var reference = Refs.Head.ResolveToDirectReference(); diff --git a/LibGit2Sharp/RepositoryExtensions.cs b/LibGit2Sharp/RepositoryExtensions.cs index 8e859072f..43907b767 100644 --- a/LibGit2Sharp/RepositoryExtensions.cs +++ b/LibGit2Sharp/RepositoryExtensions.cs @@ -254,7 +254,8 @@ public static void Fetch(this IRepository repository, string remoteName, FetchOp /// The that was checked out. public static Branch Checkout(this IRepository repository, string commitOrBranchSpec, Signature signature = null) { - return repository.Checkout(commitOrBranchSpec, CheckoutModifiers.None, null, null, signature); + CheckoutOptions options = new CheckoutOptions(); + return repository.Checkout(commitOrBranchSpec, options, signature); } /// @@ -270,7 +271,8 @@ public static Branch Checkout(this IRepository repository, string commitOrBranch /// The that was checked out. public static Branch Checkout(this IRepository repository, Branch branch, Signature signature = null) { - return repository.Checkout(branch, CheckoutModifiers.None, null, null, signature); + CheckoutOptions options = new CheckoutOptions(); + return repository.Checkout(branch, options, signature); } /// @@ -285,7 +287,8 @@ public static Branch Checkout(this IRepository repository, Branch branch, Signat /// The that was checked out. public static Branch Checkout(this IRepository repository, Commit commit, Signature signature = null) { - return repository.Checkout(commit, CheckoutModifiers.None, null, null, signature); + CheckoutOptions options = new CheckoutOptions(); + return repository.Checkout(commit, options, signature); } internal static string BuildRelativePathFrom(this Repository repo, string path)