Skip to content

Commit 3c267a1

Browse files
committed
clone: respect remote default branch name
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.
1 parent cf783cb commit 3c267a1

2 files changed

Lines changed: 75 additions & 5 deletions

File tree

Scalar.Common/Git/GitProcess.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,63 @@ public Result RemoteAdd(string remoteName, string url)
604604
return this.InvokeGitAgainstDotGitFolder("remote add " + remoteName + " " + url);
605605
}
606606

607+
public bool TryGetRemoteDefaultBranch(string remoteName, out string defaultBranch, out string error)
608+
{
609+
defaultBranch = null;
610+
error = null;
611+
612+
Result lsRemoteResult = this.InvokeGitAgainstDotGitFolder("ls-remote --symref origin HEAD");
613+
if (lsRemoteResult.ExitCodeIsFailure)
614+
{
615+
error = "Failed to call ls-remote to determine remote HEAD";
616+
return false;
617+
}
618+
619+
string output = lsRemoteResult.Output;
620+
621+
const string refPrefix = "ref: ";
622+
const string headsPrefix = "ref: refs/heads/";
623+
const string suffix = "\tHEAD";
624+
625+
var cmp = StringComparison.Ordinal;
626+
foreach (string line in output.Split('\n'))
627+
{
628+
int suffixStart = line.IndexOf(suffix, cmp);
629+
if (line.StartsWith(refPrefix, cmp) && suffixStart > -1)
630+
{
631+
// HEAD is a branch
632+
if (line.StartsWith(headsPrefix, cmp))
633+
{
634+
defaultBranch = line.Substring(headsPrefix.Length, suffixStart - headsPrefix.Length);
635+
return true;
636+
}
637+
638+
error = "HEAD is not a branch";
639+
return false;
640+
}
641+
}
642+
643+
defaultBranch = null;
644+
error = $"HEAD not found in ls-remote output: {output}";
645+
return false;
646+
}
647+
648+
public bool TryGetSymbolicRef(string name, bool shortName, out string branch, out string error)
649+
{
650+
branch = null;
651+
error = null;
652+
653+
Result result = this.InvokeGitAgainstDotGitFolder($"symbolic-ref {(shortName ? "--short" : string.Empty)} HEAD");
654+
if (result.ExitCodeIsSuccess)
655+
{
656+
branch = result.Output.Trim();
657+
return true;
658+
}
659+
660+
error = $"Failed to read symbolic ref '{name}': {result.Errors}";
661+
return false;
662+
}
663+
607664
public Result PrunePacked(string gitObjectDirectory)
608665
{
609666
return this.InvokeGitAgainstDotGitFolder(

Scalar/CommandLine/CloneVerb.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -339,10 +339,6 @@ private Result GitClone()
339339
git.SetInLocalConfig("remote.origin.promisor", "true");
340340
git.SetInLocalConfig("remote.origin.partialCloneFilter", "blob:none");
341341

342-
string branch = this.Branch ?? "master";
343-
git.SetInLocalConfig($"branch.{branch}.remote", "origin");
344-
git.SetInLocalConfig($"branch.{branch}.merge", $"refs/heads/{branch}");
345-
346342
if (!this.FullClone)
347343
{
348344
GitProcess.SparseCheckoutInit(this.enlistment);
@@ -393,8 +389,25 @@ private Result GitClone()
393389
return new Result($"Failed to complete regular clone: {fetchResult?.Errors}");
394390
}
395391

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

400+
if (!git.TryGetSymbolicRef("HEAD", shortName: true, out branch, out string localDefaultError))
401+
{
402+
return new Result($"Failed to determine local default branch name: {localDefaultError}");
403+
}
404+
}
405+
406+
git.SetInLocalConfig($"branch.{branch}.remote", "origin");
407+
git.SetInLocalConfig($"branch.{branch}.merge", $"refs/heads/{branch}");
408+
409+
// Checkout the branch
410+
GitProcess.Result checkoutResult = null;
398411
if (!this.ShowStatusWhileRunning(() =>
399412
{
400413
using (ITracer activity = this.tracer.StartActivity("git-checkout", EventLevel.LogAlways))

0 commit comments

Comments
 (0)