Skip to content

Commit f98274d

Browse files
committed
Checkout branch looks to remote tracking branches as fallback
1 parent f5098f4 commit f98274d

File tree

2 files changed

+81
-5
lines changed

2 files changed

+81
-5
lines changed

LibGit2Sharp.Tests/CheckoutFixture.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,52 @@ public void CanCheckoutPathFromCurrentBranch(string fileName)
10281028
}
10291029
}
10301030

1031+
[Theory]
1032+
[InlineData("br2", "origin")]
1033+
[InlineData("unique/branch", "another/remote")]
1034+
public void CheckoutBranchTriesRemoteTrackingBranchAsFallbackAndSucceedsIfOnlyOne(string branchName, string expectedRemoteName)
1035+
{
1036+
string path = SandboxStandardTestRepo();
1037+
using (var repo = new Repository(path))
1038+
{
1039+
ResetAndCleanWorkingDirectory(repo);
1040+
1041+
// Define another remote
1042+
var otherRemote = "another/remote";
1043+
repo.Network.Remotes.Add(otherRemote, "https://github.com/libgit2/TestGitRepository");
1044+
1045+
// Define an extra remote tracking branch that does not conflict
1046+
repo.Refs.Add($"refs/remotes/{otherRemote}/unique/branch", repo.Head.Tip.Sha);
1047+
1048+
Branch branch = Commands.Checkout(repo, branchName);
1049+
1050+
Assert.NotNull(branch);
1051+
Assert.True(branch.IsTracking);
1052+
Assert.Equal($"refs/remotes/{expectedRemoteName}/{branchName}", branch.TrackedBranch.CanonicalName);
1053+
}
1054+
}
1055+
1056+
[Fact]
1057+
public void CheckoutBranchTriesRemoteTrackingBranchAsFallbackAndThrowsIfMoreThanOne()
1058+
{
1059+
string path = SandboxStandardTestRepo();
1060+
using (var repo = new Repository(path))
1061+
{
1062+
ResetAndCleanWorkingDirectory(repo);
1063+
1064+
// Define another remote
1065+
var otherRemote = "another/remote";
1066+
repo.Network.Remotes.Add(otherRemote, "https://github.com/libgit2/TestGitRepository");
1067+
1068+
// Define remote tracking branches that conflict
1069+
var branchName = "conflicting/branch";
1070+
repo.Refs.Add($"refs/remotes/origin/{branchName}", repo.Head.Tip.Sha);
1071+
repo.Refs.Add($"refs/remotes/{otherRemote}/{branchName}", repo.Head.Tip.Sha);
1072+
1073+
Assert.Throws<AmbiguousSpecificationException>(() => Commands.Checkout(repo, branchName));
1074+
}
1075+
}
1076+
10311077
/// <summary>
10321078
/// Helper method to populate a simple repository with
10331079
/// a single file and two branches.

LibGit2Sharp/Commands/Checkout.cs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Linq;
23
using LibGit2Sharp.Core;
34

45
namespace LibGit2Sharp
@@ -37,18 +38,47 @@ public static Branch Checkout(IRepository repository, string committishOrBranchS
3738
Ensure.ArgumentNotNullOrEmptyString(committishOrBranchSpec, "committishOrBranchSpec");
3839
Ensure.ArgumentNotNull(options, "options");
3940

40-
Reference reference;
41-
GitObject obj;
41+
Reference reference = null;
42+
GitObject obj = null;
43+
Branch branch = null;
44+
45+
try
46+
{
47+
repository.RevParse(committishOrBranchSpec, out reference, out obj);
48+
}
49+
catch (NotFoundException)
50+
{
51+
// If committishOrBranchSpec is not a local branch but matches a tracking branch
52+
// in exactly one remote, use it. This is the "git checkout" command's default behavior.
53+
// https://git-scm.com/docs/git-checkout#Documentation/git-checkout.txt-emgitcheckoutemltbranchgt
54+
var remoteBranches = repository.Network.Remotes
55+
.SelectMany(r => repository.Branches.Where(b =>
56+
b.IsRemote &&
57+
b.CanonicalName == $"refs/remotes/{r.Name}/{committishOrBranchSpec}"))
58+
.ToList();
59+
60+
if (remoteBranches.Count == 1)
61+
{
62+
branch = repository.CreateBranch(committishOrBranchSpec, remoteBranches[0].Tip);
63+
repository.Branches.Update(branch, b => b.TrackedBranch = remoteBranches[0].CanonicalName);
64+
return Checkout(repository, branch, options);
65+
}
66+
67+
if (remoteBranches.Count > 1)
68+
throw new AmbiguousSpecificationException(
69+
$"'{committishOrBranchSpec}' matched multiple ({remoteBranches.Count}) remote tracking branches");
70+
71+
throw;
72+
}
4273

43-
repository.RevParse(committishOrBranchSpec, out reference, out obj);
4474
if (reference != null && reference.IsLocalBranch)
4575
{
46-
Branch branch = repository.Branches[reference.CanonicalName];
76+
branch = repository.Branches[reference.CanonicalName];
4777
return Checkout(repository, branch, options);
4878
}
4979

5080
Commit commit = obj.Peel<Commit>(true);
51-
Checkout(repository, commit.Tree, options, committishOrBranchSpec);
81+
Checkout(repository, commit.Tree, options, committishOrBranchSpec);
5282

5383
return repository.Head;
5484
}

0 commit comments

Comments
 (0)