8000 Teach Checkout() to append to the reflog · freevoid/libgit2sharp@bccacb9 · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bccacb9

Browse files
committed
Teach Checkout() to append to the reflog
When no user information exists in the configuration, a dummy signature is created to value the reflog entry.
1 parent 0efaf0d commit bccacb9

File tree

4 files changed

+200
-6
lines changed

4 files changed

+200
-6
lines changed

LibGit2Sharp.Tests/CheckoutFixture.cs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ public void CanCheckoutAnExistingBranch(string branchName)
4444

4545
// Working directory should not be dirty
4646
Assert.False(repo.Index.RetrieveStatus().IsDirty);
47+
48+
// Assert reflog entry is created
49+
var reflogEntry = repo.Refs.Log(repo.Refs.Head).First();
50+
Assert.Equal(master.Tip.Id, reflogEntry.From);
51+
Assert.Equal(branch.Tip.Id, reflogEntry.To);
52+
Assert.NotNull(reflogEntry.Commiter.Email);
53+
Assert.NotNull(reflogEntry.Commiter.Name);
54+
Assert.Equal(string.Format("checkout: moving from master to {0}", branchName), reflogEntry.Message);
4755
}
4856
}
4957

@@ -74,6 +82,14 @@ public void CanCheckoutAnExistingBranchByName(string branchName)
7482

7583
// Working directory should not be dirty
7684
Assert.False(repo.Index.RetrieveStatus().IsDirty);
85+
86+
// Assert reflog entry is created
87+
var reflogEntry = repo.Refs.Log(repo.Refs.Head).First();
88+
Assert.Equal(master.Tip.Id, reflogEntry.From);
89+
Assert.Equal(repo.Branches[branchName].Tip.Id, reflogEntry.To);
90+
Assert.NotNull(reflogEntry.Commiter.Email);
91+
Assert.NotNull(reflogEntry.Commiter.Name);
92+
Assert.Equal(string.Format("checkout: moving from master to {0}", branchName), reflogEntry.Message);
7793
}
7894
}
7995

@@ -111,6 +127,14 @@ public void CanCheckoutAnArbitraryCommit(string commitPointer)
111127
Assert.Equal("(no branch)", detachedHead.CanonicalName);
112128

113129
Assert.False(master.IsCurrentRepositoryHead);
130+
131+
// Assert reflog entry is created
132+
var reflogEntry = repo.Refs.Log(repo.Refs.Head).First();
133+
Assert.Equal(master.Tip.Id, reflogEntry.From);
134+
Assert.Equal(commitSha, reflogEntry.To.Sha);
135+
Assert.NotNull(reflogEntry.Commiter.Email);
136+
Assert.NotNull(reflogEntry.Commiter.Name);
137+
Assert.Equal(string.Format("checkout: moving from master to {0}", commitPointer), reflogEntry.Message);
114138
}
115139
}
116140

@@ -572,6 +596,14 @@ public void CheckingOutRemoteBranchResultsInDetachedHead()
572596
// Verify that HEAD is detached.
573597
Assert.Equal(repo.Refs["HEAD"].TargetIdentifier, repo.Branches["origin/master"].Tip.Sha);
574598
Assert.True(repo.Info.IsHeadDetached);
599+
600+
// Assert reflog entry is created
601+
var reflogEntry = repo.Refs.Log(repo.Refs.Head).First();
602+
Assert.Equal(master.Tip.Id, reflogEntry.From);
603+
Assert.Equal(repo.Branches["origin/master"].Tip.Id, reflogEntry.To);
604+
Assert.NotNull(reflogEntry.Commiter.Email);
605+
Assert.NotNull(reflogEntry.Commiter.Name);
606+
Assert.Equal("checkout: moving from master to origin/master", reflogEntry.Message);
575607
}
576608
}
577609

@@ -601,6 +633,136 @@ public void CheckingOutABranchDoesNotAlterBinaryFiles()
601633
}
602634
}
603635

636+
[Theory]
637+
[InlineData("a447ba2ca8")]
638+
[InlineData("refs/tags/lw")]
639+
[InlineData("e90810^{}")]
640+
public void CheckoutFromDetachedHead(string commitPointer)
641+
{
642+
string path = CloneStandardTestRepo();
643+
using (var repo = new Repository(path))
644+
{
645+
// Set the working directory to the current head
646+
ResetAndCleanWorkingDirectory(repo);
647+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
648+
649+
var commitSha = repo.Lookup(commitPointer).Sha;
650+
651+
Branch initialHead = repo.Checkout("6dcf9bf");
652+
653+
repo.Checkout(commitPointer);
654+
655+
// Assert reflog entry is created
656+
var reflogEntry = repo.Refs.Log(repo.Refs.Head).First();
657+
Assert.Equal(initialHead.Tip.Id, reflogEntry.From);
658+
Assert.Equal(commitSha, reflogEntry.To.Sha);
659+
Assert.NotNull(reflogEntry.Commiter.Email);
660+
Assert.NotNull(reflogEntry.Commiter.Name);
661+
Assert.Equal(string.Format("checkout: moving from {0} to {1}", initialHead.Tip.Sha, commitPointer), reflogEntry.Message);
662+
}
663+
}
664+
665+
[Fact]
666+
public void CheckoutBranchFromDetachedHead()
667+
{
668+
string path = CloneStandardTestRepo();
669+
using (var repo = new Repository(path))
670+
{
671+
// Set the working directory to the current head
672+
ResetAndCleanWorkingDirectory(repo);
673+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
674+
675+
Branch initialHead = repo.Checkout("6dcf9bf");
676+
677+
Assert.True(repo.Info.IsHeadDetached);
678+
679+
Branch newHead = repo.Checkout(repo.Branches["master"]);
680+
681+
// Assert reflog entry is created
682+
var reflogEntry = repo.Refs.Log(repo.Refs.Head).First();
683+
Assert.Equal(initialHead.Tip.Id, reflogEntry.From);
684+
Assert.Equal(newHead.Tip.Id, reflogEntry.To);
685+
Assert.NotNull(reflogEntry.Commiter.Email);
686+
Assert.NotNull(reflogEntry.Commiter.Name);
687+
Assert.Equal(string.Format("checkout: moving from {0} to {1}", initialHead.Tip.Sha, newHead.Name), reflogEntry.Message);
688+
}
689+
}
690+
691+
692+
[Fact(Skip = "Current libgit2 revparse implementation only returns the object being pointed at, not the reference pointing at it.")]
693+
public void CheckoutPreviousCheckedOutBranch()
694+
{
695+
string path = CloneStandardTestRepo();
696+
using (var repo = new Repository(path))
697+
{
698+
// Set the working directory to the current head
699+
ResetAndCleanWorkingDirectory(repo);
700+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
701+
702+
Branch previousHead = repo.Checkout("i-do-numbers");
703+
Branch newHead = repo.Checkout("diff-test-cases");
704+
705+
//Go back to previous branch checked out
706+
repo.Checkout(@"@{-1}");
707+
708+
// Assert reflog entry is created
709+
var reflogEntry = repo.Refs.Log(repo.Refs.Head).First();
710+
Assert.Equal(newHead.Tip.Id, reflogEntry.From);
711+
Assert.Equal(previousHead.Tip.Id, reflogEntry.To);
712+
Assert.NotNull(reflogEntry.Commiter.Email);
713+
Assert.NotNull(reflogEntry.Commiter.Name);
714+
Assert.Equal("checkout: moving from diff-test-cases to i-do-numbers", reflogEntry.Message);
715+
}
716+
}
717+
718+
[Fact]
719+
public void CheckoutCurrentReference()
720+
{
721+
string path = CloneStandardTestRepo();
722+
using (var repo = new Repository(path))
723+
{
724+
Branch master = repo.Branches["master"];
725+
Assert.True(master.IsCurrentRepositoryHead);
726+
727+
ResetAndCleanWorkingDirectory(repo);
728+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
729+
730+
var reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count();
731+
732+
// Checkout branch
733+
repo.Checkout(master);
734+
735+
Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count());
736+
737+
// Checkout in detached mode
738+
repo.Checkout(master.Tip.Sha);
739+
740+
Assert.True(repo.Info.IsHeadDetached);
741+
var reflogEntry = repo.Refs.Log(repo.Refs.Head).First();
742+
Assert.True(reflogEntry.To == reflogEntry.From);
743+
Assert.Equal(string.Format("checkout: moving from master to {0}", master.Tip.Sha), reflogEntry.Message);
744+
745+
// Checkout detached "HEAD" => nothing should happen
746+
reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count();
747+
748+
repo.Checkout(repo.Head);
749+
750+
Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count());
751+
752+
// Checkout attached "HEAD" => nothing should happen
753+
repo.Checkout("master");
754+
reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count();
755+
756+
repo.Checkout(repo.Head);
757+
758+
Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count());
759+
760+
repo.Checkout("HEAD");
761+
762+
Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count());
763+
}
764+
}
765+
604766
[Fact]
605767
public void CheckoutLowerCasedHeadThrows()
606768
{

LibGit2Sharp/Configuration.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,17 +288,20 @@ private IEnumerable<ConfigurationEntry<string>> BuildConfigEntries()
288288
});
289289
}
290290

291-
internal Signature BuildSignatureFromGlobalConfiguration(DateTimeOffset now)
291+
internal Signature BuildSignatureFromGlobalConfiguration(DateTimeOffset now, bool shouldThrowIfNotFound)
292292
{
293293
var name = Get<string>("user.name");
294294
var email = Get<string>("user.email");
295295

296-
if ((name == null) || (email == null))
296+
if (shouldThrowIfNotFound && ((name == null) || (email == null)))
297297
{
298-
throw new LibGit2SharpException("Can not find Name and Email settings of the current user in Git configuration.");
298+
throw new LibGit2SharpException("Can not find Name or Email setting of the current user in Git configuration.");
299299
}
300300

301-
return new Signature(name.Value, email.Value, now);
301+
return new Signature(
302+
name != null ? name.Value : "unknown",
303+
email != null ? email.Value : string.Format("{0}@{1}", Environment.UserName, Environment.UserDomainName),
304+
now);
302305
}
303306
}
304307
}

LibGit2Sharp/Repository.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,11 +586,17 @@ public Branch Checkout(string committishOrBranchSpec, CheckoutOptions checkoutOp
586586
return Checkout(branch, checkoutOptions, onCheckoutProgress);
587587
}
588588

589+
var previousHeadName = Info.IsHeadDetached ? Head.Tip.Sha : Head.Name;
590+
589591
Commit commit = LookupCommit(committishOrBranchSpec);
590592
CheckoutTree(commit.Tree, checkoutOptions, onCheckoutProgress);
591593

592594
// Update HEAD.
593595
Refs.UpdateTarget("HEAD", commit.Id.Sha);
596+
if (committishOrBranchSpec != "HEAD")
597+
{
598+
LogCheckout(previousHeadName, commit.Id, committishOrBranchSpec);
599+
}
594600

595601
return Head;
596602
}
@@ -633,6 +639,9 @@ public Branch Checkout(Branch branch, CheckoutOptions checkoutOptions, CheckoutP
633639
"The tip of branch '{0}' is null. There's nothing to checkout.", branch.Name));
634640
}
635641

642+
var branchIsCurrentRepositoryHead = branch.IsCurrentRepositoryHead;
643+
var previousHeadName = Info.IsHeadDetached ? Head.Tip.Sha : Head.Name;
644+
636645
CheckoutTree(branch.Tip.Tree, checkoutOptions, onCheckoutProgress);
637646

638647
// Update HEAD.
@@ -647,9 +656,29 @@ public Branch Checkout(Branch branch, CheckoutOptions checkoutOptions, CheckoutP
647656
Refs.UpdateTarget("HEAD", branch.Tip.Id.Sha);
648657
}
649658

659+
if (!branchIsCurrentRepositoryHead)
660+
{
661+
LogCheckout(previousHeadName, branch);
662+
}
663+
650664
return Head;
651665
}
652666

667+
private void LogCheckout(string previousHeadName, Branch newHead 10000 )
668+
{
669+
LogCheckout(previousHeadName, newHead.Tip.Id, newHead.Name);
670+
}
671+
672+
private void LogCheckout(string previousHeadName, ObjectId newHeadTip, string newHeadSpec)
673+
{
674+
// Compute reflog message
675+
string reflogMessage = string.Format("checkout: moving from {0} to {1}", previousHeadName, newHeadSpec);
676+
677+
// Log checkout
678+
Signature author = Config.BuildSignatureFromGlobalConfiguration(DateTimeOffset.Now, false);
679+
Refs.Log(Refs.Head).Append(newHeadTip, author, reflogMessage);
680+
}
681+
653682
/// <summary>
654683
/// Internal implementation of Checkout that expects the ID of the checkout target
655684
/// to already be in the form of a canonical branch name or a commit ID.

LibGit2Sharp/RepositoryExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ private static Commit LookUpCommit(IRepository repository, string committish)
173173
/// <returns>The generated <see cref = "LibGit2Sharp.Commit" />.</returns>
174174
public static Commit Commit(this IRepository repository, string message, bool amendPreviousCommit = false)
175175
{
176-
Signature author = repository.Config.BuildSignatureFromGlobalConfiguration(DateTimeOffset.Now);
176+
Signature author = repository.Config.BuildSignatureFromGlobalConfiguration(DateTimeOffset.Now, true);
177177

178178
return repository.Commit(message, author, amendPreviousCommit);
179179
}
@@ -191,7 +191,7 @@ public static Commit Commit(this IRepository repository, string message, bool am
191191
/// <returns>The generated <see cref = "LibGit2Sharp.Commit" />.</returns>
192192
public static Commit Commit(this IRepository repository, string message, Signature author, bool amendPreviousCommit = false)
193193
{
194-
Signature committer = repository.Config.BuildSignatureFromGlobalConfiguration(DateTimeOffset.Now);
194+
Signature committer = repository.Config.BuildSignatureFromGlobalConfiguration(DateTimeOffset.Now, true);
195195

196196
return repository.Commit(message, author, committer, amendPreviousCommit);
197197
}

0 commit comments

Comments
 (0)
2A79
0