Skip to content

Commit

Permalink
Checkout branch looks to remote tracking branches as fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
louis-z committed Nov 19, 2021
1 parent 4daf618 commit 15fbe8e
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 5 deletions.
46 changes: 46 additions & 0 deletions LibGit2Sharp.Tests/CheckoutFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,52 @@ public void CanCheckoutPathFromCurrentBranch(string fileName)
}
}

[Theory]
[InlineData("br2", "origin")]
[InlineData("unique/branch", "another/remote")]
public void CheckoutBranchTriesRemoteTrackingBranchAsFallbackAndSucceedsIfOnlyOne(string branchName, string expectedRemoteName)
{
string path = SandboxStandardTestRepo();
using (var repo = new Repository(path))
{
ResetAndCleanWorkingDirectory(repo);

// Define another remote
var otherRemote = "another/remote";
repo.Network.Remotes.Add(otherRemote, "https://github.com/libgit2/TestGitRepository");

// Define an extra remote tracking branch that does not conflict
repo.Refs.Add($"refs/remotes/{otherRemote}/unique/branch", repo.Head.Tip.Sha);

Branch branch = Commands.Checkout(repo, branchName);

Assert.NotNull(branch);
Assert.True(branch.IsTracking);
Assert.Equal($"refs/remotes/{expectedRemoteName}/{branchName}", branch.TrackedBranch.CanonicalName);
}
}

[Fact]
public void CheckoutBranchTriesRemoteTrackingBranchAsFallbackAndThrowsIfMoreThanOne()
{
string path = SandboxStandardTestRepo();
using (var repo = new Repository(path))
{
ResetAndCleanWorkingDirectory(repo);

// Define another remote
var otherRemote = "another/remote";
repo.Network.Remotes.Add(otherRemote, "https://github.com/libgit2/TestGitRepository");

// Define remote tracking branches that conflict
var branchName = "conflicting/branch";
repo.Refs.Add($"refs/remotes/origin/{branchName}", repo.Head.Tip.Sha);
repo.Refs.Add($"refs/remotes/{otherRemote}/{branchName}", repo.Head.Tip.Sha);

Assert.Throws<AmbiguousSpecificationException>(() => Commands.Checkout(repo, branchName));
}
}

/// <summary>
/// Helper method to populate a simple repository with
/// a single file and two branches.
Expand Down
40 changes: 35 additions & 5 deletions LibGit2Sharp/Commands/Checkout.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using LibGit2Sharp.Core;

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

Reference reference;
GitObject obj;
Reference reference = null;
GitObject obj = null;
Branch branch = null;

try
{
repository.RevParse(committishOrBranchSpec, out reference, out obj);
}
catch (NotFoundException)
{
// If committishOrBranchSpec is not a local branch but matches a tracking branch
// in exactly one remote, use it. This is the "git checkout" command's default behavior.
// https://git-scm.com/docs/git-checkout#Documentation/git-checkout.txt-emgitcheckoutemltbranchgt
var remoteBranches = repository.Network.Remotes
.SelectMany(r => repository.Branches.Where(b =>
b.IsRemote &&
b.CanonicalName == $"refs/remotes/{r.Name}/{committishOrBranchSpec}"))
.ToList();

if (remoteBranches.Count == 1)
{
branch = repository.CreateBranch(committishOrBranchSpec, remoteBranches[0].Tip);
repository.Branches.Update(branch, b => b.TrackedBranch = remoteBranches[0].CanonicalName);
return Checkout(repository, branch, options);
}

if (remoteBranches.Count > 1)
throw new AmbiguousSpecificationException(
$"'{committishOrBranchSpec}' matched multiple ({remoteBranches.Count}) remote tracking branches");

throw;
}

repository.RevParse(committishOrBranchSpec, out reference, out obj);
if (reference != null && reference.IsLocalBranch)
{
Branch branch = repository.Branches[reference.CanonicalName];
branch = repository.Branches[reference.CanonicalName];
return Checkout(repository, branch, options);
}

Commit commit = obj.Peel<Commit>(true);
Checkout(repository, commit.Tree, options, committishOrBranchSpec);
Checkout(repository, commit.Tree, options, committishOrBranchSpec);

return repository.Head;
}
Expand Down

0 comments on commit 15fbe8e

Please sign in to comment.