/// <summary> /// Normalization of a git directory turns all remote branches into local branches, /// turns pull request refs into a real branch and a few other things. /// This is designed to be run *only on the build server* which checks out repositories in different ways. /// It is not recommended to run normalization against a local repository /// </summary> private void NormalizeGitDirectory(bool noFetch, string currentBranchName, bool isDynamicRepository) { var authentication = options.Value.Authentication; // Need to ensure the HEAD does not move, this is essentially a BugCheck var expectedSha = repository.Head.Tip.Sha; var expectedBranchName = repository.Head.Name.Canonical; try { var remote = EnsureOnlyOneRemoteIsDefined(); //If noFetch is enabled, then GitVersion will assume that the git repository is normalized before execution, so that fetching from remotes is not required. if (noFetch) { log.Info("Skipping fetching, if GitVersion does not calculate your version as expected you might need to allow fetching or use dynamic repositories"); } else { var refSpecs = string.Join(", ", remote.FetchRefSpecs.Select(r => r.Specification)); log.Info($"Fetching from remote '{remote.Name}' using the following refspecs: {refSpecs}."); retryAction.Execute(() => repository.Fetch(remote.Name, Enumerable.Empty <string>(), authentication, null)); } EnsureLocalBranchExistsForCurrentBranch(remote, currentBranchName); CreateOrUpdateLocalBranchesFromRemoteTrackingOnes(remote.Name); var currentBranch = repositoryStore.FindBranch(currentBranchName); // Bug fix for https://github.com/GitTools/GitVersion/issues/1754, head maybe have been changed // if this is a dynamic repository. But only allow this in case the branches are different (branch switch) if (expectedSha != repository.Head.Tip.Sha && (isDynamicRepository || currentBranch is null || !repository.Head.Equals(currentBranch))) { var newExpectedSha = repository.Head.Tip.Sha; var newExpectedBranchName = repository.Head.Name.Canonical; log.Info($"Head has moved from '{expectedBranchName} | {expectedSha}' => '{newExpectedBranchName} | {newExpectedSha}', allowed since this is a dynamic repository"); expectedSha = newExpectedSha; } var headSha = repository.Refs.Head.TargetIdentifier; if (!repository.IsHeadDetached) { log.Info($"HEAD points at branch '{headSha}'."); return; } log.Info($"HEAD is detached and points at commit '{headSha}'."); log.Info($"Local Refs:{System.Environment.NewLine}" + string.Join(System.Environment.NewLine, repository.Refs.FromGlob("*").Select(r => $"{r.Name.Canonical} ({r.TargetIdentifier})"))); // In order to decide whether a fake branch is required or not, first check to see if any local branches have the same commit SHA of the head SHA. // If they do, go ahead and checkout that branch // If no, go ahead and check out a new branch, using the known commit SHA as the pointer var localBranchesWhereCommitShaIsHead = repository.Branches.Where(b => !b.IsRemote && b.Tip.Sha == headSha).ToList(); var matchingCurrentBranch = !string.IsNullOrEmpty(currentBranchName) ? localBranchesWhereCommitShaIsHead.SingleOrDefault(b => b.Name.Canonical.Replace("/heads/", "/") == currentBranchName.Replace("/heads/", "/")) : null; if (matchingCurrentBranch != null) { log.Info($"Checking out local branch '{currentBranchName}'."); Checkout(matchingCurrentBranch.Name.Canonical); } else if (localBranchesWhereCommitShaIsHead.Count > 1) { var branchNames = localBranchesWhereCommitShaIsHead.Select(r => r.Name.Canonical); var csvNames = string.Join(", ", branchNames); const string moveBranchMsg = "Move one of the branches along a commit to remove warning"; log.Warning($"Found more than one local branch pointing at the commit '{headSha}' ({csvNames})."); var main = localBranchesWhereCommitShaIsHead.SingleOrDefault(n => n.Name.EquivalentTo(Config.MainBranchKey)); if (main != null) { log.Warning("Because one of the branches is 'main', will build main." + moveBranchMsg); Checkout(Config.MainBranchKey); } else { var branchesWithoutSeparators = localBranchesWhereCommitShaIsHead.Where(b => !b.Name.Friendly.Contains('/') && !b.Name.Friendly.Contains('-')).ToList(); if (branchesWithoutSeparators.Count == 1) { var branchWithoutSeparator = branchesWithoutSeparators[0]; log.Warning($"Choosing {branchWithoutSeparator.Name.Canonical} as it is the only branch without / or - in it. " + moveBranchMsg); Checkout(branchWithoutSeparator.Name.Canonical); } else { throw new WarningException("Failed to try and guess branch to use. " + moveBranchMsg); } } } else if (localBranchesWhereCommitShaIsHead.Count == 0) { log.Info($"No local branch pointing at the commit '{headSha}'. Fake branch needs to be created."); retryAction.Execute(() => repository.CreateBranchForPullRequestBranch(authentication)); } else { log.Info($"Checking out local branch 'refs/heads/{localBranchesWhereCommitShaIsHead[0]}'."); Checkout(localBranchesWhereCommitShaIsHead[0].Name.Friendly); } } finally { if (repository.Head.Tip.Sha != expectedSha) { if (environment.GetEnvironmentVariable("IGNORE_NORMALISATION_GIT_HEAD_MOVE") != "1") { // Whoa, HEAD has moved, it shouldn't have. We need to blow up because there is a bug in normalisation throw new BugException($@"GitVersion has a bug, your HEAD has moved after repo normalisation. To disable this error set an environmental variable called IGNORE_NORMALISATION_GIT_HEAD_MOVE to 1 Please run `git {GitExtensions.CreateGitLogArgs(100)}` and submit it along with your build log (with personal info removed) in a new issue at https://github.com/GitTools/GitVersion"); } } } }
private IEnumerable <BaseVersion> MainTagsVersions() { var main = repositoryStore.FindBranch(Config.MainBranchKey); return(main != null?taggedCommitVersionStrategy.GetTaggedVersions(main, null) : new BaseVersion[0]); }