Skip to content

Commit c23179e

Browse files
committed
move methods from RepositoryExtensions
1 parent 0d23180 commit c23179e

File tree

5 files changed

+250
-237
lines changed

5 files changed

+250
-237
lines changed

src/GitVersionCore.Tests/Core/RepositoryExtensionsTests.cs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using GitVersion;
45
using GitVersion.Extensions;
56
using GitVersion.Logging;
@@ -13,14 +14,65 @@ namespace GitVersionCore.Tests
1314
[TestFixture]
1415
public class RepositoryExtensionsTests : TestBase
1516
{
17+
private static void EnsureLocalBranchExistsForCurrentBranch(IGitRepository repo, ILog log, Remote remote, string currentBranch)
18+
{
19+
if (log is null)
20+
{
21+
throw new ArgumentNullException(nameof(log));
22+
}
23+
24+
if (remote is null)
25+
{
26+
throw new ArgumentNullException(nameof(remote));
27+
}
28+
29+
if (string.IsNullOrEmpty(currentBranch)) return;
30+
31+
var isRef = currentBranch.Contains("refs");
32+
var isBranch = currentBranch.Contains("refs/heads");
33+
var localCanonicalName = !isRef
34+
? "refs/heads/" + currentBranch
35+
: isBranch
36+
? currentBranch
37+
: currentBranch.Replace("refs/", "refs/heads/");
38+
39+
var repoTip = repo.Head.Tip;
40+
41+
// We currently have the rep.Head of the *default* branch, now we need to look up the right one
42+
var originCanonicalName = $"{remote.Name}/{currentBranch}";
43+
var originBranch = repo.Branches[originCanonicalName];
44+
if (originBranch != null)
45+
{
46+
repoTip = originBranch.Tip;
47+
}
48+
49+
var repoTipId = repoTip.Id;
50+
51+
if (repo.Branches.All(b => !b.CanonicalName.IsEquivalentTo(localCanonicalName)))
52+
{
53+
log.Info(isBranch ? $"Creating local branch {localCanonicalName}"
54+
: $"Creating local branch {localCanonicalName} pointing at {repoTipId}");
55+
repo.Refs.Add(localCanonicalName, repoTipId);
56+
}
57+
else
58+
{
59+
log.Info(isBranch ? $"Updating local branch {localCanonicalName} to point at {repoTip.Sha}"
60+
: $"Updating local branch {localCanonicalName} to match ref {currentBranch}");
61+
var localRef = repo.Refs[localCanonicalName];
62+
repo.Refs.UpdateTarget(localRef, repoTipId);
63+
}
64+
65+
repo.Commands.Checkout(localCanonicalName);
66+
}
67+
1668
[Test]
1769
public void EnsureLocalBranchExistsForCurrentBranch_CaseInsensitivelyMatchesBranches()
1870
{
1971
var log = Substitute.For<ILog>();
2072
var repository = MockRepository();
2173
var remote = MockRemote(repository);
2274

23-
repository.EnsureLocalBranchExistsForCurrentBranch(log, remote, "refs/heads/featurE/feat-test");
75+
EnsureLocalBranchExistsForCurrentBranch(repository, log, remote, "refs/heads/featurE/feat-test");
2476
}
2577

2678
private IGitRepository MockRepository()

src/GitVersionCore.Tests/Extensions/GitToolsTestingExtensions.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@ public static void DumpGraph(this IRepository repository, Action<string> writer
3131

3232
public static VersionVariables GetVersion(this RepositoryFixtureBase fixture, Config configuration = null, IRepository repository = null, string commitId = null, bool onlyTrackedBranches = true, string branch = null)
3333
{
34-
if (configuration == null)
35-
{
36-
configuration = new ConfigurationBuilder().Build();
37-
}
34+
configuration ??= new ConfigurationBuilder().Build();
3835

3936
repository ??= fixture.Repository;
4037

src/GitVersionCore/Core/GitPreparer.cs

Lines changed: 195 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Linq;
45
using GitVersion.Extensions;
@@ -208,9 +209,9 @@ private void NormalizeGitDirectory(string gitDirectory, bool noFetch, string cur
208209

209210
try
210211
{
211-
var remote = repository.EnsureOnlyOneRemoteIsDefined(log);
212+
var remote = EnsureOnlyOneRemoteIsDefined(repository, log);
212213

213-
repository.AddMissingRefSpecs(log, remote);
214+
AddMissingRefSpecs(repository, log, remote);
214215

215216
//If noFetch is enabled, then GitVersion will assume that the git repository is normalized before execution, so that fetching from remotes is not required.
216217
if (noFetch)
@@ -223,8 +224,8 @@ private void NormalizeGitDirectory(string gitDirectory, bool noFetch, string cur
223224
repository.Commands.Fetch(remote.Name, new string[0], authentication.ToFetchOptions(), null);
224225
}
225226

226-
repository.EnsureLocalBranchExistsForCurrentBranch(log, remote, currentBranch);
227-
repository.CreateOrUpdateLocalBranchesFromRemoteTrackingOnes(log, remote.Name);
227+
EnsureLocalBranchExistsForCurrentBranch(repository, log, remote, currentBranch);
228+
CreateOrUpdateLocalBranchesFromRemoteTrackingOnes(repository, log, remote.Name);
228229

229230
// Bug fix for https://github.com/GitTools/GitVersion/issues/1754, head maybe have been changed
230231
// if this is a dynamic repository. But only allow this in case the branches are different (branch switch)
@@ -294,7 +295,7 @@ private void NormalizeGitDirectory(string gitDirectory, bool noFetch, string cur
294295
else if (localBranchesWhereCommitShaIsHead.Count == 0)
295296
{
296297
log.Info($"No local branch pointing at the commit '{headSha}'. Fake branch needs to be created.");
297-
repository.CreateFakeBranchPointingAtThePullRequestTip(log, authentication);
298+
CreateFakeBranchPointingAtThePullRequestTip(repository, log, authentication);
298299
}
299300
else
300301
{
@@ -318,5 +319,194 @@ To disable this error set an environmental variable called IGNORE_NORMALISATION_
318319
}
319320
}
320321
}
322+
323+
private static void CreateOrUpdateLocalBranchesFromRemoteTrackingOnes(IGitRepository repo, ILog log, string remoteName)
324+
{
325+
var prefix = $"refs/remotes/{remoteName}/";
326+
var remoteHeadCanonicalName = $"{prefix}HEAD";
327+
var remoteTrackingReferences = repo.Refs
328+
.FromGlob(prefix + "*")
329+
.Where(r => !r.CanonicalName.IsEquivalentTo(remoteHeadCanonicalName));
330+
331+
foreach (var remoteTrackingReference in remoteTrackingReferences)
332+
{
333+
var remoteTrackingReferenceName = remoteTrackingReference.CanonicalName;
334+
var branchName = remoteTrackingReferenceName.Substring(prefix.Length);
335+
var localCanonicalName = "refs/heads/" + branchName;
336+
337+
// We do not want to touch our current branch
338+
if (branchName.IsEquivalentTo(repo.Head.FriendlyName)) continue;
339+
340+
if (repo.Refs.Any(x => x.CanonicalName.IsEquivalentTo(localCanonicalName)))
341+
{
342+
var localRef = repo.Refs[localCanonicalName];
343+
var remotedirectReference = remoteTrackingReference.ResolveToDirectReference();
344+
if (localRef.ResolveToDirectReference().TargetIdentifier == remotedirectReference.TargetIdentifier)
345+
{
346+
log.Info($"Skipping update of '{remoteTrackingReference.CanonicalName}' as it already matches the remote ref.");
347+
continue;
348+
}
349+
var remoteRefTipId = remotedirectReference.Target.Id;
350+
log.Info($"Updating local ref '{localRef.CanonicalName}' to point at {remoteRefTipId}.");
351+
repo.Refs.UpdateTarget(localRef, remoteRefTipId);
352+
continue;
353+
}
354+
355+
log.Info($"Creating local branch from remote tracking '{remoteTrackingReference.CanonicalName}'.");
356+
repo.Refs.Add(localCanonicalName, new ObjectId(remoteTrackingReference.ResolveToDirectReference().TargetIdentifier), true);
357+
358+
var branch = repo.Branches[branchName];
359+
repo.Branches.Update(branch, b => b.TrackedBranch = remoteTrackingReferenceName);
360+
}
361+
}
362+
363+
private static void EnsureLocalBranchExistsForCurrentBranch(IGitRepository repo, ILog log, Remote remote, string currentBranch)
364+
{
365+
if (log is null)
366+
{
367+
throw new ArgumentNullException(nameof(log));
368+
}
369+
370+
if (remote is null)
371+
{
372+
throw new ArgumentNullException(nameof(remote));
373+
}
374+
375+
if (string.IsNullOrEmpty(currentBranch)) return;
376+
377+
var isRef = currentBranch.Contains("refs");
378+
var isBranch = currentBranch.Contains("refs/heads");
379+
var localCanonicalName = !isRef
380+
? "refs/heads/" + currentBranch
381+
: isBranch
382+
? currentBranch
383+
: currentBranch.Replace("refs/", "refs/heads/");
384+
385+
var repoTip = repo.Head.Tip;
386+
387+
// We currently have the rep.Head of the *default* branch, now we need to look up the right one
388+
var originCanonicalName = $"{remote.Name}/{currentBranch}";
389+
var originBranch = repo.Branches[originCanonicalName];
390+
if (originBranch != null)
391+
{
392+
repoTip = originBranch.Tip;
393+
}
394+
395+
var repoTipId = repoTip.Id;
396+
397+
if (repo.Branches.All(b => !b.CanonicalName.IsEquivalentTo(localCanonicalName)))
398+
{
399+
log.Info(isBranch ? $"Creating local branch {localCanonicalName}"
400+
: $"Creating local branch {localCanonicalName} pointing at {repoTipId}");
401+
repo.Refs.Add(localCanonicalName, repoTipId);
402+
}
403+
else
404+
{
405+
log.Info(isBranch ? $"Updating local branch {localCanonicalName} to point at {repoTip.Sha}"
406+
: $"Updating local branch {localCanonicalName} to match ref {currentBranch}");
407+
var localRef = repo.Refs[localCanonicalName];
408+
repo.Refs.UpdateTarget(localRef, repoTipId);
409+
}
410+
411+
repo.Commands.Checkout(localCanonicalName);
412+
}
413+
414+
private static Remote EnsureOnlyOneRemoteIsDefined(IGitRepository repo, ILog log)
415+
{
416+
var remotes = repo.Network.Remotes;
417+
var howMany = remotes.Count();
418+
419+
if (howMany == 1)
420+
{
421+
var remote = remotes.Single();
422+
log.Info($"One remote found ({remote.Name} -> '{remote.Url}').");
423+
return remote;
424+
}
425+
426+
var message = $"{howMany} remote(s) have been detected. When being run on a build server, the Git repository is expected to bear one (and no more than one) remote.";
427+
throw new WarningException(message);
428+
}
429+
430+
private static void AddMissingRefSpecs(IGitRepository repo, ILog log, Remote remote)
431+
{
432+
if (remote.FetchRefSpecs.Any(r => r.Source == "refs/heads/*"))
433+
return;
434+
435+
var allBranchesFetchRefSpec = $"+refs/heads/*:refs/remotes/{remote.Name}/*";
436+
437+
log.Info($"Adding refspec: {allBranchesFetchRefSpec}");
438+
439+
repo.Network.Remotes.Update(remote.Name,
440+
r => r.FetchRefSpecs.Add(allBranchesFetchRefSpec));
441+
}
442+
443+
private static void CreateFakeBranchPointingAtThePullRequestTip(IGitRepository repo, ILog log, AuthenticationInfo authentication)
444+
{
445+
var remote = repo.Network.Remotes.Single();
446+
447+
log.Info("Fetching remote refs to see if there is a pull request ref");
448+
var remoteTips = (string.IsNullOrEmpty(authentication.Username) ?
449+
GetRemoteTipsForAnonymousUser(repo, remote) :
450+
GetRemoteTipsUsingUsernamePasswordCredentials(repo, remote, authentication.Username, authentication.Password))
451+
.ToList();
452+
453+
log.Info($"Remote Refs:{System.Environment.NewLine}" + string.Join(System.Environment.NewLine, remoteTips.Select(r => r.CanonicalName)));
454+
455+
var headTipSha = repo.Head.Tip.Sha;
456+
457+
var refs = remoteTips.Where(r => r.TargetIdentifier == headTipSha).ToList();
458+
459+
if (refs.Count == 0)
460+
{
461+
var message = $"Couldn't find any remote tips from remote '{remote.Url}' pointing at the commit '{headTipSha}'.";
462+
throw new WarningException(message);
463+
}
464+
465+
if (refs.Count > 1)
466+
{
467+
var names = string.Join(", ", refs.Select(r => r.CanonicalName));
468+
var message = $"Found more than one remote tip from remote '{remote.Url}' pointing at the commit '{headTipSha}'. Unable to determine which one to use ({names}).";
469+
throw new WarningException(message);
470+
}
471+
472+
var reference = refs[0];
473+
var canonicalName = reference.CanonicalName;
474+
log.Info($"Found remote tip '{canonicalName}' pointing at the commit '{headTipSha}'.");
475+
476+
if (canonicalName.StartsWith("refs/tags"))
477+
{
478+
log.Info($"Checking out tag '{canonicalName}'");
479+
repo.Commands.Checkout(reference.Target.Sha);
480+
return;
481+
}
482+
483+
if (!canonicalName.StartsWith("refs/pull/") && !canonicalName.StartsWith("refs/pull-requests/"))
484+
{
485+
var message = $"Remote tip '{canonicalName}' from remote '{remote.Url}' doesn't look like a valid pull request.";
486+
throw new WarningException(message);
487+
}
488+
489+
var fakeBranchName = canonicalName.Replace("refs/pull/", "refs/heads/pull/").Replace("refs/pull-requests/", "refs/heads/pull-requests/");
490+
491+
log.Info($"Creating fake local branch '{fakeBranchName}'.");
492+
repo.Refs.Add(fakeBranchName, new ObjectId(headTipSha));
493+
494+
log.Info($"Checking local branch '{fakeBranchName}' out.");
495+
repo.Commands.Checkout(fakeBranchName);
496+
}
497+
498+
private static IEnumerable<DirectReference> GetRemoteTipsUsingUsernamePasswordCredentials(IGitRepository repository, Remote remote, string username, string password)
499+
{
500+
return repository.Network.ListReferences(remote, (url, fromUrl, types) => new UsernamePasswordCredentials
501+
{
502+
Username = username,
503+
Password = password ?? string.Empty
504+
}).Select(r => r.ResolveToDirectReference());
505+
}
506+
507+
private static IEnumerable<DirectReference> GetRemoteTipsForAnonymousUser(IGitRepository repository, Remote remote)
508+
{
509+
return repository.Network.ListReferences(remote).Select(r => r.ResolveToDirectReference());
510+
}
321511
}
322512
}

src/GitVersionCore/Core/RepositoryMetadataProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ public Branch GetTargetBranch(string targetBranch)
200200

201201
public Branch FindBranch(string branchName)
202202
{
203-
return repository.FindBranch(branchName);
203+
return repository.Branches.FirstOrDefault(x => x.NameWithoutRemote() == branchName);
204204
}
205205

206206
public Branch GetChosenBranch(Config configuration)

0 commit comments

Comments
 (0)