Skip to content

Commit

Permalink
#449: Respect remote default branch name on vanilla Git clone
Browse files Browse the repository at this point in the history
Attempt to determine the remote's default branch name when performing a scalar clone using the vanilla Git protocols (not using the GVFS helper).

Use ls-remote to lookup the HEAD ref on the remote, and parse the results. If the remote's HEAD is not a branch, or we fail to parse the output for any reason, consult Git itself for the default branch name as a fallback.

Scalar clones using the GVFS protocol & helper work differently, and already take the default remote branch name from the manual call to /info/refs?service=git-upload-pack.

Fixes #438
  • Loading branch information
mjcheetham committed Oct 15, 2020
2 parents cf783cb + 3c267a1 commit 3d83058
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 5 deletions.
57 changes: 57 additions & 0 deletions Scalar.Common/Git/GitProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,63 @@ public Result RemoteAdd(string remoteName, string url)
return this.InvokeGitAgainstDotGitFolder("remote add " + remoteName + " " + url);
}

public bool TryGetRemoteDefaultBranch(string remoteName, out string defaultBranch, out string error)
{
defaultBranch = null;
error = null;

Result lsRemoteResult = this.InvokeGitAgainstDotGitFolder("ls-remote --symref origin HEAD");
if (lsRemoteResult.ExitCodeIsFailure)
{
error = "Failed to call ls-remote to determine remote HEAD";
return false;
}

string output = lsRemoteResult.Output;

const string refPrefix = "ref: ";
const string headsPrefix = "ref: refs/heads/";
const string suffix = "\tHEAD";

var cmp = StringComparison.Ordinal;
foreach (string line in output.Split('\n'))
{
int suffixStart = line.IndexOf(suffix, cmp);
if (line.StartsWith(refPrefix, cmp) && suffixStart > -1)
{
// HEAD is a branch
if (line.StartsWith(headsPrefix, cmp))
{
defaultBranch = line.Substring(headsPrefix.Length, suffixStart - headsPrefix.Length);
return true;
}

error = "HEAD is not a branch";
return false;
}
}

defaultBranch = null;
error = $"HEAD not found in ls-remote output: {output}";
return false;
}

public bool TryGetSymbolicRef(string name, bool shortName, out string branch, out string error)
{
branch = null;
error = null;

Result result = this.InvokeGitAgainstDotGitFolder($"symbolic-ref {(shortName ? "--short" : string.Empty)} HEAD");
if (result.ExitCodeIsSuccess)
{
branch = result.Output.Trim();
return true;
}

error = $"Failed to read symbolic ref '{name}': {result.Errors}";
return false;
}

public Result PrunePacked(string gitObjectDirectory)
{
return this.InvokeGitAgainstDotGitFolder(
Expand Down
23 changes: 18 additions & 5 deletions Scalar/CommandLine/CloneVerb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,10 +339,6 @@ private Result GitClone()
git.SetInLocalConfig("remote.origin.promisor", "true");
git.SetInLocalConfig("remote.origin.partialCloneFilter", "blob:none");

string branch = this.Branch ?? "master";
git.SetInLocalConfig($"branch.{branch}.remote", "origin");
git.SetInLocalConfig($"branch.{branch}.merge", $"refs/heads/{branch}");

if (!this.FullClone)
{
GitProcess.SparseCheckoutInit(this.enlistment);
Expand Down Expand Up @@ -393,8 +389,25 @@ private Result GitClone()
return new Result($"Failed to complete regular clone: {fetchResult?.Errors}");
}

GitProcess.Result checkoutResult = null;
// Configure the specified branch, or the default branch on the remote if not specified
string branch = this.Branch;
if (branch is null && !git.TryGetRemoteDefaultBranch("origin", out branch, out string defaultBranchError))
{
// Failed to get the remote's default branch name - ask Git for the prefered local default branch
// instead, and show a warning message.
this.Output.WriteLine($"warning: failed to get default branch name from remote; using local default: {defaultBranchError}");

if (!git.TryGetSymbolicRef("HEAD", shortName: true, out branch, out string localDefaultError))
{
return new Result($"Failed to determine local default branch name: {localDefaultError}");
}
}

git.SetInLocalConfig($"branch.{branch}.remote", "origin");
git.SetInLocalConfig($"branch.{branch}.merge", $"refs/heads/{branch}");

// Checkout the branch
GitProcess.Result checkoutResult = null;
if (!this.ShowStatusWhileRunning(() =>
{
using (ITracer activity = this.tracer.StartActivity("git-checkout", EventLevel.LogAlways))
Expand Down

0 comments on commit 3d83058

Please sign in to comment.