/// <summary> /// Find the commit where the given branch was branched from another branch. /// If there are multiple such commits and branches, tries to guess based on commit histories. /// </summary> public BranchCommit FindCommitBranchWasBranchedFrom(Branch branch, params Branch[] excludedBranches) { if (branch == null) { throw new ArgumentNullException("branch"); } using (Logger.IndentLog(string.Format("Finding branch source of '{0}'", branch.FriendlyName))) { if (branch.Tip == null) { Logger.WriteWarning(string.Format(missingTipFormat, branch.FriendlyName)); return(BranchCommit.Empty); } var possibleBranches = GetMergeCommitsForBranch(branch, excludedBranches) .Where(b => !branch.IsSameBranch(b.Branch)) .ToList(); if (possibleBranches.Count > 1) { var first = possibleBranches.First(); Logger.WriteInfo($"Multiple source branches have been found, picking the first one ({first.Branch.FriendlyName}).\n" + "This may result in incorrect commit counting.\nOptions were:\n " + string.Join(", ", possibleBranches.Select(b => b.Branch.FriendlyName))); return(first); } return(possibleBranches.SingleOrDefault()); } }
public void WriteVariablesToDiskCache(GitPreparer gitPreparer, GitVersionCacheKey cacheKey, VersionVariables variablesFromCache) { var cacheDir = PrepareCacheDirectory(gitPreparer); var cacheFileName = GetCacheFileName(cacheKey, cacheDir); variablesFromCache.FileName = cacheFileName; Dictionary <string, string> dictionary; using (Logger.IndentLog("Creating dictionary")) { dictionary = variablesFromCache.ToDictionary(x => x.Key, x => x.Value); } Action writeCacheOperation = () => { using (var stream = fileSystem.OpenWrite(cacheFileName)) { using (var sw = new StreamWriter(stream)) { using (Logger.IndentLog("Storing version variables to cache file " + cacheFileName)) { var serializer = new Serializer(); serializer.Serialize(sw, dictionary); } } } }; var retryOperation = new OperationWithExponentialBackoff <IOException>(new ThreadSleep(), writeCacheOperation, maxRetries: 6); retryOperation.ExecuteAsync().Wait(); }
public IEnumerable <SemanticVersion> GetVersionTagsOnBranch(Branch branch, string tagPrefixRegex) { if (semanticVersionTagsOnBranchCache.ContainsKey(branch)) { Logger.WriteDebug(string.Format("Cache hit for version tags on branch '{0}", branch.CanonicalName)); return(semanticVersionTagsOnBranchCache[branch]); } using (Logger.IndentLog(string.Format("Getting version tags from branch '{0}'.", branch.CanonicalName))) { var tags = this.Repository.Tags.Select(t => t).ToList(); var versionTags = this.Repository.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = branch.Tip }) .SelectMany(c => tags.Where(t => c.Sha == t.Target.Sha).SelectMany(t => { SemanticVersion semver; if (SemanticVersion.TryParse(t.FriendlyName, tagPrefixRegex, out semver)) { return new[] { semver } } ; return(new SemanticVersion[0]); })).ToList(); semanticVersionTagsOnBranchCache.Add(branch, versionTags); return(versionTags); } }
static string CreateDynamicRepository(string targetPath, AuthenticationInfo authentication, string repositoryUrl, string targetBranch, bool noFetch) { if (string.IsNullOrWhiteSpace(targetBranch)) { throw new Exception("Dynamic Git repositories must have a target branch (/b)"); } using (Logger.IndentLog($"Creating dynamic repository at '{targetPath}'")) { var gitDirectory = Path.Combine(targetPath, ".git"); if (Directory.Exists(targetPath)) { Logger.WriteInfo("Git repository already exists"); using (Logger.IndentLog($"Normalizing git directory for branch '{targetBranch}'")) { GitRepositoryHelper.NormalizeGitDirectory(gitDirectory, authentication, noFetch, targetBranch, true); } return(gitDirectory); } CloneRepository(repositoryUrl, gitDirectory, authentication); using (Logger.IndentLog($"Normalizing git directory for branch '{targetBranch}'")) { // Normalize (download branches) before using the branch GitRepositoryHelper.NormalizeGitDirectory(gitDirectory, authentication, noFetch, targetBranch, true); } return(gitDirectory); } }
public IEnumerable <SemanticVersion> GetVersionTagsOnBranch(Branch branch, string tagPrefixRegex) { if (semanticVersionTagsOnBranchCache.ContainsKey(branch)) { Logger.WriteDebug(string.Format("Cache hit for version tags on branch '{0}", branch.CanonicalName)); return(semanticVersionTagsOnBranchCache[branch]); } using (Logger.IndentLog(string.Format("Getting version tags from branch '{0}'.", branch.CanonicalName))) { var tags = new List <Tuple <Tag, SemanticVersion> >(); foreach (var t in this.Repository.Tags) { SemanticVersion semver; if (SemanticVersion.TryParse(t.FriendlyName, tagPrefixRegex, out semver)) { tags.Add(Tuple.Create(t, semver)); } } var versionTags = branch.Commits.SelectMany(c => tags.Where(t => c.Sha == t.Item1.Target.Sha).Select(t => t.Item2)).ToList(); semanticVersionTagsOnBranchCache.Add(branch, versionTags); return(versionTags); } }
/// <summary> /// Find the merge base of the two branches, i.e. the best common ancestor of the two branches' tips. /// </summary> public Commit FindMergeBase(Branch branch, Branch otherBranch) { var key = Tuple.Create(branch, otherBranch); if (mergeBaseCache.ContainsKey(key)) { Logger.WriteDebug(string.Format( "Cache hit for merge base between '{0}' and '{1}'.", branch.FriendlyName, otherBranch.FriendlyName)); return(mergeBaseCache[key].MergeBase); } using (Logger.IndentLog(string.Format("Finding merge base between '{0}' and '{1}'.", branch.FriendlyName, otherBranch.FriendlyName))) { // Otherbranch tip is a forward merge var commitToFindCommonBase = otherBranch.Tip; var commit = branch.Tip; if (otherBranch.Tip.Parents.Contains(commit)) { commitToFindCommonBase = otherBranch.Tip.Parents.First(); } var findMergeBase = this.Repository.ObjectDatabase.FindMergeBase(commit, commitToFindCommonBase); if (findMergeBase != null) { Logger.WriteInfo(string.Format("Found merge base of {0}", findMergeBase.Sha)); // We do not want to include merge base commits which got forward merged into the other branch bool mergeBaseWasForwardMerge; do { // Now make sure that the merge base is not a forward merge mergeBaseWasForwardMerge = otherBranch.Commits .SkipWhile(c => c != commitToFindCommonBase) .TakeWhile(c => c != findMergeBase) .Any(c => c.Parents.Contains(findMergeBase)); if (mergeBaseWasForwardMerge) { var second = commitToFindCommonBase.Parents.First(); var mergeBase = this.Repository.ObjectDatabase.FindMergeBase(commit, second); if (mergeBase == findMergeBase) { break; } findMergeBase = mergeBase; Logger.WriteInfo(string.Format("Merge base was due to a forward merge, next merge base is {0}", findMergeBase)); } } while (mergeBaseWasForwardMerge); } // Store in cache. mergeBaseCache.Add(key, new MergeBaseData(branch, otherBranch, this.Repository, findMergeBase)); return(findMergeBase); } }
static void CloneRepository(string repositoryUrl, string gitDirectory, AuthenticationInfo authentication) { Credentials credentials = null; if (authentication != null) { if (!string.IsNullOrWhiteSpace(authentication.Username) && !string.IsNullOrWhiteSpace(authentication.Password)) { Logger.WriteInfo($"Setting up credentials using name '{authentication.Username}'"); credentials = new UsernamePasswordCredentials { Username = authentication.Username, Password = authentication.Password }; } } try { using (Logger.IndentLog($"Cloning repository from url '{repositoryUrl}'")) { var cloneOptions = new CloneOptions { Checkout = false, CredentialsProvider = (url, usernameFromUrl, types) => credentials }; var returnedPath = Repository.Clone(repositoryUrl, gitDirectory, cloneOptions); Logger.WriteInfo($"Returned path after repository clone: {returnedPath}"); } } catch (LibGit2SharpException ex) { var message = ex.Message; if (message.Contains("401")) { throw new Exception("Unauthorised: Incorrect username/password"); } if (message.Contains("403")) { throw new Exception("Forbidden: Possbily Incorrect username/password"); } if (message.Contains("404")) { throw new Exception("Not found: The repository was not found"); } throw new Exception("There was an unknown problem with the Git repository you provided", ex); } }
// TODO Should we cache this? public IEnumerable <Branch> GetBranchesContainingCommit(Commit commit, IList <Branch> branches, bool onlyTrackedBranches) { if (commit == null) { throw new ArgumentNullException("commit"); } using (Logger.IndentLog(string.Format("Getting branches containing the commit '{0}'.", commit.Id))) { var directBranchHasBeenFound = false; Logger.WriteInfo("Trying to find direct branches."); // TODO: It looks wasteful looping through the branches twice. Can't these loops be merged somehow? @asbjornu foreach (var branch in branches) { if (branch.Tip != null && branch.Tip.Sha != commit.Sha || (onlyTrackedBranches && !branch.IsTracking)) { continue; } directBranchHasBeenFound = true; Logger.WriteInfo(string.Format("Direct branch found: '{0}'.", branch.FriendlyName)); yield return(branch); } if (directBranchHasBeenFound) { yield break; } Logger.WriteInfo(string.Format("No direct branches found, searching through {0} branches.", onlyTrackedBranches ? "tracked" : "all")); foreach (var branch in branches.Where(b => onlyTrackedBranches && !b.IsTracking)) { Logger.WriteInfo(string.Format("Searching for commits reachable from '{0}'.", branch.FriendlyName)); var commits = this.Repository.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = branch }).Where(c => c.Sha == commit.Sha); if (!commits.Any()) { Logger.WriteInfo(string.Format("The branch '{0}' has no matching commits.", branch.FriendlyName)); continue; } Logger.WriteInfo(string.Format("The branch '{0}' has a matching commit.", branch.FriendlyName)); yield return(branch); } } }
public static Commit FindCommitBranchWasBranchedFrom([NotNull] this Branch branch, IRepository repository, params Branch[] excludedBranches) { const string missingTipFormat = "{0} has no tip. Please see http://example.com/docs for information on how to fix this."; if (branch == null) { throw new ArgumentNullException("branch"); } using (Logger.IndentLog("Finding branch source")) { if (branch.Tip == null) { Logger.WriteWarning(string.Format(missingTipFormat, branch.FriendlyName)); return(null); } var otherBranches = repository.Branches .Except(excludedBranches) .Where(b => IsSameBranch(branch, b)) .ToList(); var mergeBases = otherBranches.Select(otherBranch => { if (otherBranch.Tip == null) { Logger.WriteWarning(string.Format(missingTipFormat, otherBranch.FriendlyName)); return(null); } var findMergeBase = FindMergeBase(branch, otherBranch, repository); return(new { mergeBaseCommit = findMergeBase, branch = otherBranch }); }).Where(b => b.mergeBaseCommit != null).OrderByDescending(b => b.mergeBaseCommit.Committer.When).ToList(); var firstOrDefault = mergeBases.FirstOrDefault(); if (firstOrDefault != null) { return(firstOrDefault.mergeBaseCommit); } return(null); } }
public IEnumerable <SemanticVersion> GetVersionTagsOnBranch(Branch branch, string tagPrefixRegex) { if (semanticVersionTagsOnBranchCache.ContainsKey(branch)) { Logger.WriteDebug($"Cache hit for version tags on branch '{branch.CanonicalName}"); return(semanticVersionTagsOnBranchCache[branch]); } using (Logger.IndentLog($"Getting version tags from branch '{branch.CanonicalName}'.")) { var tags = GetValidVersionTags(Repository, tagPrefixRegex); var versionTags = branch.Commits.SelectMany(c => tags.Where(t => c.Sha == t.Item1.Target.Sha).Select(t => t.Item2)).ToList(); semanticVersionTagsOnBranchCache.Add(branch, versionTags); return(versionTags); } }
public static Commit FindCommitBranchWasBranchedFrom(this Branch branch, IRepository repository, params Branch[] excludedBranches) { using (Logger.IndentLog("Finding branch source")) { var otherBranches = repository.Branches.Except(excludedBranches).Where(b => IsSameBranch(branch, b)).ToList(); var mergeBases = otherBranches.Select(b => { var otherCommit = b.Tip; if (b.Tip.Parents.Contains(branch.Tip)) { otherCommit = b.Tip.Parents.First(); } var mergeBase = repository.Commits.FindMergeBase(otherCommit, branch.Tip); return(mergeBase); }).Where(b => b != null).ToList(); return(mergeBases.OrderByDescending(b => b.Committer.When).FirstOrDefault()); } }
public void Initialise(bool normaliseGitDirectory, string currentBranch) { if (string.IsNullOrWhiteSpace(targetUrl)) { if (normaliseGitDirectory) { using (Logger.IndentLog(string.Format("Normalizing git directory for branch '{0}'", currentBranch))) { GitRepositoryHelper.NormalizeGitDirectory(GetDotGitDirectory(), authentication, noFetch, currentBranch); } } return; } var tempRepositoryPath = CalculateTemporaryRepositoryPath(targetUrl, dynamicRepositoryLocation); DynamicGitRepositoryPath = CreateDynamicRepository(tempRepositoryPath, authentication, targetUrl, currentBranch, noFetch); }
/// <summary> /// Find the commit where the given branch was branched from another branch. /// If there are multiple such commits and branches, returns the newest commit. /// </summary> public BranchCommit FindCommitBranchWasBranchedFrom([NotNull] Branch branch, params Branch[] excludedBranches) { if (branch == null) { throw new ArgumentNullException("branch"); } using (Logger.IndentLog(string.Format("Finding branch source of '{0}'", branch.FriendlyName))) { if (branch.Tip == null) { Logger.WriteWarning(string.Format(missingTipFormat, branch.FriendlyName)); return(BranchCommit.Empty); } return(GetMergeCommitsForBranch(branch).ExcludingBranches(excludedBranches).FirstOrDefault(b => !branch.IsSameBranch(b.Branch))); } }
public static Commit FindMergeBase(this Branch branch, Branch otherBranch, IRepository repository) { using (Logger.IndentLog(string.Format("Finding merge base between '{0}' and {1}.", branch.FriendlyName, otherBranch.FriendlyName))) { // Otherbranch tip is a forward merge var commitToFindCommonBase = otherBranch.Tip; var commit = branch.Tip; if (otherBranch.Tip.Parents.Contains(commit)) { commitToFindCommonBase = otherBranch.Tip.Parents.First(); } var findMergeBase = repository.ObjectDatabase.FindMergeBase(commit, commitToFindCommonBase); if (findMergeBase != null) { Logger.WriteInfo(string.Format("Found merge base of {0}", findMergeBase.Sha)); // We do not want to include merge base commits which got forward merged into the other branch bool mergeBaseWasFowardMerge; do { // Now make sure that the merge base is not a forward merge mergeBaseWasFowardMerge = otherBranch.Commits .SkipWhile(c => c != commitToFindCommonBase) .TakeWhile(c => c != findMergeBase) .Any(c => c.Parents.Contains(findMergeBase)); if (mergeBaseWasFowardMerge) { var second = commitToFindCommonBase.Parents.First(); var mergeBase = repository.ObjectDatabase.FindMergeBase(commit, second); if (mergeBase == findMergeBase) { break; } findMergeBase = mergeBase; Logger.WriteInfo(string.Format("Merge base was due to a forward merge, next merge base is {0}", findMergeBase)); } } while (mergeBaseWasFowardMerge); } return(findMergeBase); } }
public VersionVariables LoadVersionVariablesFromDiskCache(IRepository repo, string gitDir) { using (Logger.IndentLog("Loading version variables from disk cache")) { // If the cacheDir already exists, CreateDirectory just won't do anything (it won't fail). @asbjornu var cacheDir = GetCacheDir(gitDir); fileSystem.CreateDirectory(cacheDir); var cacheFileName = GetCacheFileName(GetKey(repo, gitDir), cacheDir); VersionVariables vv = null; if (fileSystem.Exists(cacheFileName)) { using (Logger.IndentLog("Deserializing version variables from cache file " + cacheFileName)) { try { vv = VersionVariables.FromFile(cacheFileName, fileSystem); } catch (Exception ex) { Logger.WriteWarning("Unable to read cache file " + cacheFileName + ", deleting it."); Logger.WriteInfo(ex.ToString()); try { fileSystem.Delete(cacheFileName); } catch (Exception deleteEx) { Logger.WriteWarning(string.Format("Unable to delete corrupted version cache file {0}. Got {1} exception.", cacheFileName, deleteEx.GetType().FullName)); } } } } else { Logger.WriteInfo("Cache file " + cacheFileName + " not found."); } return(vv); } }
public void Initialise(bool normaliseGitDirectory, string currentBranch, bool shouldCleanUpRemotes = false) { if (string.IsNullOrWhiteSpace(targetUrl)) { if (normaliseGitDirectory) { using (Logger.IndentLog($"Normalizing git directory for branch '{currentBranch}'")) { if (shouldCleanUpRemotes) { CleanupDuplicateOrigin(); } GitRepositoryHelper.NormalizeGitDirectory(GetDotGitDirectory(), authentication, noFetch, currentBranch, IsDynamicGitRepository); } } return; } var tempRepositoryPath = CalculateTemporaryRepositoryPath(targetUrl, dynamicRepositoryLocation); DynamicGitRepositoryPath = CreateDynamicRepository(tempRepositoryPath, authentication, targetUrl, currentBranch, noFetch); }
public VersionVariables LoadVersionVariablesFromDiskCache(GitPreparer gitPreparer, GitVersionCacheKey key) { using (Logger.IndentLog("Loading version variables from disk cache")) { var cacheDir = PrepareCacheDirectory(gitPreparer); var cacheFileName = GetCacheFileName(key, cacheDir); if (!fileSystem.Exists(cacheFileName)) { Logger.WriteInfo("Cache file " + cacheFileName + " not found."); return(null); } using (Logger.IndentLog("Deserializing version variables from cache file " + cacheFileName)) { try { var loadedVariables = VersionVariables.FromFile(cacheFileName, fileSystem); return(loadedVariables); } catch (Exception ex) { Logger.WriteWarning("Unable to read cache file " + cacheFileName + ", deleting it."); Logger.WriteInfo(ex.ToString()); try { fileSystem.Delete(cacheFileName); } catch (Exception deleteEx) { Logger.WriteWarning($"Unable to delete corrupted version cache file {cacheFileName}. Got {deleteEx.GetType().FullName} exception."); } return(null); } } } }
public static Commit FindCommitBranchWasBranchedFrom([NotNull] this Branch branch, IRepository repository, params Branch[] excludedBranches) { const string missingTipFormat = "{0} has no tip. Please see http://example.com/docs for information on how to fix this."; if (branch == null) { throw new ArgumentNullException("branch"); } using (Logger.IndentLog("Finding branch source")) { if (branch.Tip == null) { Logger.WriteWarning(String.Format(missingTipFormat, branch.Name)); return(null); } var otherBranches = repository.Branches.Except(excludedBranches).Where(b => IsSameBranch(branch, b)).ToList(); var mergeBases = otherBranches.Select(b => { if (b.Tip == null) { Logger.WriteWarning(String.Format(missingTipFormat, b.Name)); return(null); } var otherCommit = b.Tip; if (b.Tip.Parents.Contains(branch.Tip)) { otherCommit = b.Tip.Parents.First(); } var mergeBase = repository.Commits.FindMergeBase(otherCommit, branch.Tip); return(mergeBase); }).Where(b => b != null).ToList(); return(mergeBases.OrderByDescending(b => b.Committer.When).FirstOrDefault()); } }
public void WriteVariablesToDiskCache(IRepository repo, string gitDir, VersionVariables variablesFromCache) { var cacheFileName = GetCacheFileName(GetKey(repo, gitDir), GetCacheDir(gitDir)); variablesFromCache.FileName = cacheFileName; using (var stream = fileSystem.OpenWrite(cacheFileName)) { using (var sw = new StreamWriter(stream)) { Dictionary <string, string> dictionary; using (Logger.IndentLog("Creating dictionary")) { dictionary = variablesFromCache.ToDictionary(x => x.Key, x => x.Value); } using (Logger.IndentLog("Storing version variables to cache file " + cacheFileName)) { var serializer = new Serializer(); serializer.Serialize(sw, dictionary); } } } }
static BranchConfig InheritBranchConfiguration(GitVersionContext context, Branch targetBranch, BranchConfig branchConfiguration, IList <Branch> excludedInheritBranches) { var repository = context.Repository; var config = context.FullConfiguration; using (Logger.IndentLog("Attempting to inherit branch configuration from parent branch")) { var excludedBranches = new[] { targetBranch }; // Check if we are a merge commit. If so likely we are a pull request var parentCount = context.CurrentCommit.Parents.Count(); if (parentCount == 2) { excludedBranches = CalculateWhenMultipleParents(repository, context.CurrentCommit, ref targetBranch, excludedBranches); } if (excludedInheritBranches == null) { excludedInheritBranches = repository.Branches.Where(b => { var branchConfig = config.GetConfigForBranch(b.FriendlyName); return(branchConfig != null && branchConfig.Increment == IncrementStrategy.Inherit); }).ToList(); } // Add new excluded branches. foreach (var excludedBranch in excludedBranches.ExcludingBranches(excludedInheritBranches)) { excludedInheritBranches.Add(excludedBranch); } var branchesToEvaluate = repository.Branches.Except(excludedInheritBranches).ToList(); var branchPoint = context.RepositoryMetadataProvider .FindCommitBranchWasBranchedFrom(targetBranch, excludedInheritBranches.ToArray()); List <Branch> possibleParents; if (branchPoint == BranchCommit.Empty) { possibleParents = context.RepositoryMetadataProvider.GetBranchesContainingCommit(context.CurrentCommit, branchesToEvaluate, true) // It fails to inherit Increment branch configuration if more than 1 parent; // therefore no point to get more than 2 parents .Take(2) .ToList(); } else { var branches = context.RepositoryMetadataProvider .GetBranchesContainingCommit(branchPoint.Commit, branchesToEvaluate, true).ToList(); if (branches.Count > 1) { var currentTipBranches = context.RepositoryMetadataProvider .GetBranchesContainingCommit(context.CurrentCommit, branchesToEvaluate, true).ToList(); possibleParents = branches.Except(currentTipBranches).ToList(); } else { possibleParents = branches; } } Logger.WriteInfo("Found possible parent branches: " + string.Join(", ", possibleParents.Select(p => p.FriendlyName))); if (possibleParents.Count == 1) { var branchConfig = GetBranchConfiguration(context, possibleParents[0], excludedInheritBranches); return(new BranchConfig(branchConfiguration) { Increment = branchConfig.Increment, PreventIncrementOfMergedBranchVersion = branchConfig.PreventIncrementOfMergedBranchVersion, // If we are inheriting from develop then we should behave like develop TracksReleaseBranches = branchConfig.TracksReleaseBranches }); } // If we fail to inherit it is probably because the branch has been merged and we can't do much. So we will fall back to develop's config // if develop exists and master if not string errorMessage; if (possibleParents.Count == 0) { errorMessage = "Failed to inherit Increment branch configuration, no branches found."; } else { errorMessage = "Failed to inherit Increment branch configuration, ended up with: " + string.Join(", ", possibleParents.Select(p => p.FriendlyName)); } var developBranchRegex = config.Branches[ConfigurationProvider.DevelopBranchKey].Regex; var masterBranchRegex = config.Branches[ConfigurationProvider.MasterBranchKey].Regex; var chosenBranch = repository.Branches.FirstOrDefault(b => Regex.IsMatch(b.FriendlyName, developBranchRegex, RegexOptions.IgnoreCase) || Regex.IsMatch(b.FriendlyName, masterBranchRegex, RegexOptions.IgnoreCase)); if (chosenBranch == null) { // TODO We should call the build server to generate this exception, each build server works differently // for fetch issues and we could give better warnings. throw new InvalidOperationException("Could not find a 'develop' or 'master' branch, neither locally nor remotely."); } var branchName = chosenBranch.FriendlyName; Logger.WriteWarning(errorMessage + Environment.NewLine + Environment.NewLine + "Falling back to " + branchName + " branch config"); // To prevent infinite loops, make sure that a new branch was chosen. if (targetBranch.IsSameBranch(chosenBranch)) { Logger.WriteWarning("Fallback branch wants to inherit Increment branch configuration from itself. Using patch increment instead."); return(new BranchConfig(branchConfiguration) { Increment = IncrementStrategy.Patch }); } var inheritingBranchConfig = GetBranchConfiguration(context, chosenBranch, excludedInheritBranches); return(new BranchConfig(branchConfiguration) { Increment = inheritingBranchConfig.Increment, PreventIncrementOfMergedBranchVersion = inheritingBranchConfig.PreventIncrementOfMergedBranchVersion, // If we are inheriting from develop then we should behave like develop TracksReleaseBranches = inheritingBranchConfig.TracksReleaseBranches }); } }
static KeyValuePair <string, BranchConfig> InheritBranchConfiguration(bool onlyEvaluateTrackedBranches, IRepository repository, Commit currentCommit, Branch currentBranch, KeyValuePair <string, BranchConfig> keyValuePair, BranchConfig branchConfiguration, Config config, IList <Branch> excludedInheritBranches) { using (Logger.IndentLog("Attempting to inherit branch configuration from parent branch")) { var excludedBranches = new[] { currentBranch }; // Check if we are a merge commit. If so likely we are a pull request var parentCount = currentCommit.Parents.Count(); if (parentCount == 2) { var parents = currentCommit.Parents.ToArray(); var branch = repository.Branches.SingleOrDefault(b => !b.IsRemote && b.Tip == parents[1]); if (branch != null) { excludedBranches = new[] { currentBranch, branch }; currentBranch = branch; } else { var possibleTargetBranches = repository.Branches.Where(b => !b.IsRemote && b.Tip == parents[0]).ToList(); if (possibleTargetBranches.Count() > 1) { currentBranch = possibleTargetBranches.FirstOrDefault(b => b.Name == "master") ?? possibleTargetBranches.First(); } else { currentBranch = possibleTargetBranches.FirstOrDefault() ?? currentBranch; } } Logger.WriteInfo("HEAD is merge commit, this is likely a pull request using " + currentBranch.Name + " as base"); } if (excludedInheritBranches == null) { excludedInheritBranches = repository.Branches.Where(b => { var branchConfig = LookupBranchConfiguration(config, b); return(branchConfig.Length == 1 && branchConfig[0].Value.Increment == IncrementStrategy.Inherit); }).ToList(); } excludedBranches.ToList().ForEach(excludedInheritBranches.Add); var branchPoint = currentBranch.FindCommitBranchWasBranchedFrom(repository, excludedInheritBranches.ToArray()); List <Branch> possibleParents; if (branchPoint == null) { possibleParents = currentCommit.GetBranchesContainingCommit(repository, true).Except(excludedInheritBranches).ToList(); } else { var branches = branchPoint.GetBranchesContainingCommit(repository, true).Except(excludedInheritBranches).ToList(); if (branches.Count > 1) { var currentTipBranches = currentCommit.GetBranchesContainingCommit(repository, true).Except(excludedInheritBranches).ToList(); possibleParents = branches.Except(currentTipBranches).ToList(); } else { possibleParents = branches; } } Logger.WriteInfo("Found possible parent branches: " + string.Join(", ", possibleParents.Select(p => p.Name))); if (possibleParents.Count == 1) { var branchConfig = GetBranchConfiguration(currentCommit, repository, onlyEvaluateTrackedBranches, config, possibleParents[0], excludedInheritBranches).Value; return(new KeyValuePair <string, BranchConfig>( keyValuePair.Key, new BranchConfig(branchConfiguration) { Increment = branchConfig.Increment, PreventIncrementOfMergedBranchVersion = branchConfig.PreventIncrementOfMergedBranchVersion })); } // If we fail to inherit it is probably because the branch has been merged and we can't do much. So we will fall back to develop's config // if develop exists and master if not string errorMessage; if (possibleParents.Count == 0) { errorMessage = "Failed to inherit Increment branch configuration, no branches found."; } else { errorMessage = "Failed to inherit Increment branch configuration, ended up with: " + string.Join(", ", possibleParents.Select(p => p.Name)); } var developBranch = repository.Branches.FirstOrDefault(b => Regex.IsMatch(b.Name, "^develop", RegexOptions.IgnoreCase)); var branchName = developBranch != null ? developBranch.Name : "master"; Logger.WriteWarning(errorMessage + Environment.NewLine + Environment.NewLine + "Falling back to " + branchName + " branch config"); var value = GetBranchConfiguration(currentCommit, repository, onlyEvaluateTrackedBranches, config, repository.Branches[branchName]).Value; return(new KeyValuePair <string, BranchConfig>( keyValuePair.Key, new BranchConfig(branchConfiguration) { Increment = value.Increment, PreventIncrementOfMergedBranchVersion = value.PreventIncrementOfMergedBranchVersion })); } }
/// <summary> /// Find the merge base of the two branches, i.e. the best common ancestor of the two branches' tips. /// </summary> public Commit FindMergeBase(Branch branch, Branch otherBranch) { var key = Tuple.Create(branch, otherBranch); if (mergeBaseCache.ContainsKey(key)) { Logger.WriteDebug($"Cache hit for merge base between '{branch.FriendlyName}' and '{otherBranch.FriendlyName}'."); return(mergeBaseCache[key].MergeBase); } using (Logger.IndentLog($"Finding merge base between '{branch.FriendlyName}' and '{otherBranch.FriendlyName}'.")) { // Otherbranch tip is a forward merge var commitToFindCommonBase = otherBranch.Tip; var commit = branch.Tip; if (otherBranch.Tip.Parents.Contains(commit)) { commitToFindCommonBase = otherBranch.Tip.Parents.First(); } var findMergeBase = Repository.ObjectDatabase.FindMergeBase(commit, commitToFindCommonBase); if (findMergeBase != null) { Logger.WriteInfo($"Found merge base of {findMergeBase.Sha}"); // We do not want to include merge base commits which got forward merged into the other branch Commit forwardMerge; do { // Now make sure that the merge base is not a forward merge forwardMerge = Repository.Commits .QueryBy(new CommitFilter { IncludeReachableFrom = commitToFindCommonBase, ExcludeReachableFrom = findMergeBase }) .FirstOrDefault(c => c.Parents.Contains(findMergeBase)); if (forwardMerge != null) { // TODO Fix the logging up in this section var second = forwardMerge.Parents.First(); Logger.WriteDebug("Second " + second.Sha); var mergeBase = Repository.ObjectDatabase.FindMergeBase(commit, second); if (mergeBase == null) { Logger.WriteWarning("Could not find mergbase for " + commit); } else { Logger.WriteDebug("New Merge base " + mergeBase.Sha); } if (mergeBase == findMergeBase) { Logger.WriteDebug("Breaking"); break; } findMergeBase = mergeBase; commitToFindCommonBase = second; Logger.WriteInfo($"Merge base was due to a forward merge, next merge base is {findMergeBase}"); } } while (forwardMerge != null); } // Store in cache. mergeBaseCache.Add(key, new MergeBaseData(branch, otherBranch, Repository, findMergeBase)); Logger.WriteInfo($"Merge base of {branch.FriendlyName}' and '{otherBranch.FriendlyName} is {findMergeBase}"); return(findMergeBase); } }
static KeyValuePair <string, BranchConfig> InheritBranchConfiguration(bool onlyEvaluateTrackedBranches, IRepository repository, Commit currentCommit, Branch currentBranch, KeyValuePair <string, BranchConfig> keyValuePair, BranchConfig branchConfiguration, Config config, IList <Branch> excludedInheritBranches) { using (Logger.IndentLog("Attempting to inherit branch configuration from parent branch")) { var excludedBranches = new[] { currentBranch }; // Check if we are a merge commit. If so likely we are a pull request var parentCount = currentCommit.Parents.Count(); if (parentCount == 2) { excludedBranches = CalculateWhenMultipleParents(repository, currentCommit, ref currentBranch, excludedBranches); } if (excludedInheritBranches == null) { excludedInheritBranches = repository.Branches.Where(b => { var branchConfig = LookupBranchConfiguration(config, b); // NOTE: if length is 0 we couldn't find the configuration for the branch e.g. "origin/master" // NOTE: if the length is greater than 1 we cannot decide which merge strategy to pick return((branchConfig.Length != 1) || (branchConfig.Length == 1 && branchConfig[0].Value.Increment == IncrementStrategy.Inherit)); }).ToList(); } excludedBranches.ToList().ForEach(excludedInheritBranches.Add); var branchesToEvaluate = repository.Branches.Except(excludedInheritBranches).ToList(); var branchPoint = currentBranch.FindCommitBranchWasBranchedFrom(repository, excludedInheritBranches.ToArray()); List <Branch> possibleParents; if (branchPoint == null) { possibleParents = currentCommit.GetBranchesContainingCommit(repository, branchesToEvaluate, true) // It fails to inherit Increment branch configuration if more than 1 parent; // therefore no point to get more than 2 parents .Take(2) .ToList(); } else { var branches = branchPoint.GetBranchesContainingCommit(repository, branchesToEvaluate, true).ToList(); if (branches.Count > 1) { var currentTipBranches = currentCommit.GetBranchesContainingCommit(repository, branchesToEvaluate, true).ToList(); possibleParents = branches.Except(currentTipBranches).ToList(); } else { possibleParents = branches; } } Logger.WriteInfo("Found possible parent branches: " + string.Join(", ", possibleParents.Select(p => p.FriendlyName))); if (possibleParents.Count == 1) { var branchConfig = GetBranchConfiguration(currentCommit, repository, onlyEvaluateTrackedBranches, config, possibleParents[0], excludedInheritBranches).Value; return(new KeyValuePair <string, BranchConfig>( keyValuePair.Key, new BranchConfig(branchConfiguration) { Increment = branchConfig.Increment, PreventIncrementOfMergedBranchVersion = branchConfig.PreventIncrementOfMergedBranchVersion })); } // If we fail to inherit it is probably because the branch has been merged and we can't do much. So we will fall back to develop's config // if develop exists and master if not string errorMessage; if (possibleParents.Count == 0) { errorMessage = "Failed to inherit Increment branch configuration, no branches found."; } else { errorMessage = "Failed to inherit Increment branch configuration, ended up with: " + string.Join(", ", possibleParents.Select(p => p.FriendlyName)); } var chosenBranch = repository.Branches.FirstOrDefault(b => Regex.IsMatch(b.FriendlyName, "^develop", RegexOptions.IgnoreCase) || Regex.IsMatch(b.FriendlyName, "master$", RegexOptions.IgnoreCase)); if (chosenBranch == null) { throw new InvalidOperationException("Could not find a 'develop' or 'master' branch, neither locally nor remotely."); } var branchName = chosenBranch.FriendlyName; Logger.WriteWarning(errorMessage + Environment.NewLine + Environment.NewLine + "Falling back to " + branchName + " branch config"); var value = GetBranchConfiguration(currentCommit, repository, onlyEvaluateTrackedBranches, config, chosenBranch).Value; return(new KeyValuePair <string, BranchConfig>( keyValuePair.Key, new BranchConfig(branchConfiguration) { Increment = value.Increment, PreventIncrementOfMergedBranchVersion = value.PreventIncrementOfMergedBranchVersion })); } }
public static Commit FindCommitBranchWasBranchedFrom([NotNull] this Branch branch, IRepository repository, params Branch[] excludedBranches) { const string missingTipFormat = "{0} has no tip. Please see http://example.com/docs for information on how to fix this."; if (branch == null) { throw new ArgumentNullException("branch"); } using (Logger.IndentLog("Finding branch source")) { if (branch.Tip == null) { Logger.WriteWarning(string.Format(missingTipFormat, branch.Name)); return(null); } var otherBranches = repository.Branches .Except(excludedBranches) .Where(b => IsSameBranch(branch, b)) .ToList(); var mergeBases = otherBranches.Select(otherBranch => { if (otherBranch.Tip == null) { Logger.WriteWarning(string.Format(missingTipFormat, otherBranch.Name)); return(null); } // Otherbranch tip is a forward merge var commitToFindCommonBase = otherBranch.Tip; if (otherBranch.Tip.Parents.Contains(branch.Tip)) { commitToFindCommonBase = otherBranch.Tip.Parents.First(); } var findMergeBase = repository.Commits.FindMergeBase(branch.Tip, commitToFindCommonBase); if (findMergeBase != null) { using (Logger.IndentLog(string.Format("Found merge base of {0} against {1}", findMergeBase.Sha, otherBranch.Name))) { // We do not want to include merge base commits which got forward merged into the other branch bool mergeBaseWasFowardMerge; do { // Now make sure that the merge base is not a forward merge mergeBaseWasFowardMerge = otherBranch.Commits .SkipWhile(c => c != commitToFindCommonBase) .TakeWhile(c => c != findMergeBase) .Any(c => c.Parents.Contains(findMergeBase)); if (mergeBaseWasFowardMerge) { Logger.WriteInfo("Merge base was due to a forward merge, moving to next merge base"); var second = commitToFindCommonBase.Parents.First(); var mergeBase = repository.Commits.FindMergeBase(branch.Tip, second); if (mergeBase == findMergeBase) { break; } findMergeBase = mergeBase; } } while (mergeBaseWasFowardMerge); } } return(new { mergeBaseCommit = findMergeBase, branch = otherBranch }); }).Where(b => b != null).OrderByDescending(b => b.mergeBaseCommit.Committer.When).ToList(); var firstOrDefault = mergeBases.FirstOrDefault(); if (firstOrDefault != null) { return(firstOrDefault.mergeBaseCommit); } return(null); } }