public async Task <ImmutableList <string> > DetectShallowUpstream(string branchName, bool asGroup) { var remoteBranchesTask = repositoryState.RemoteBranches().FirstOrDefaultAsync().ToTask(); var configuredBranchesTask = branchSettings.GetConfiguredBranches().FirstOrDefaultAsync().ToTask(); await Task.WhenAll(remoteBranchesTask, configuredBranchesTask); var allRemotes = remoteBranchesTask.Result; var configured = configuredBranchesTask.Result; var actualBranchName = asGroup ? branchIteration.GetLatestBranchNameIteration(branchName, allRemotes.Select(b => b.Name)) : branchName; var allUpstream = await repositoryState.DetectUpstream(actualBranchName, false); return(await PruneUpstream(allRemotes.Find(remote => remote.Name == branchName), allUpstream, configured, allRemotes)); }
private async Task <IEnumerable <NeededMerge> > ToUpstreamBranchNames(ImmutableList <BranchGroup> directUpstreamBranchGroups) { var hierarchy = (await repository.AllBranchesHierarchy().Take(1)).ToDictionary(branch => branch.GroupName, branch => branch.HierarchyDepth); var remotes = await repository.GetAllBranchRefs().FirstOrDefaultAsync(); // Put integration branches first; they might be needed for conflict resolution in other branches! // FIXME - this is using the GroupName rather than the BranchName! return(from branch in directUpstreamBranchGroups orderby branch.BranchType == BranchGroupType.Integration ? 0 : 1, -hierarchy[branch.GroupName], branch.GroupName select new NeededMerge { GroupName = branch.GroupName, BranchName = branchIteration.GetLatestBranchNameIteration(branch.GroupName, remotes.Select(r => r.Name)) }); }
public async Task <IntegrationBranchResult> FindConflicts(string targetBranch, IEnumerable <string> _, AttemptMergeDelegate doMerge) { // When finding branches that conflict, we should go to the earliest point of conflict... so we need to know full ancestry. // Except... we can start at the direct upstream, and if those conflict, then move up; a double breadth-first search. // // A & B -> C // D & E & F -> G // H & I -> J // C and G do not conflict. // C and J conflict. // G and J do not conflict. // A and J conflict. // B and J conflict. // A and H conflict. // B and H do not conflict. // A and I do not conflict. // B and I do not conflict. // // Two integration branches are needed: A-H and B-J. A-H is already found, so only B-J is created. // A-H and B-J are added to the downstream branch, if A-H was not already added to the downstream branch. var remoteBranches = await repository.GetAllBranchRefs().Select(branches => branches.Select(branch => branch.Name).ToImmutableList()).FirstOrDefaultAsync(); Func <string, LatestBranchGroup> groupToLatest = group => new LatestBranchGroup { GroupName = group, LatestBranchName = branchIteration.GetLatestBranchNameIteration(group, remoteBranches) }; var upstreamBranchListings = new Dictionary <string, ImmutableList <string> >(); var initialUpstreamBranchGroups = await GetUpstreamBranches(targetBranch, upstreamBranchListings); await GetUpstreamBranches(targetBranch, upstreamBranchListings); var leafConflicts = new HashSet <ConflictingBranches>(); var unflippedConflicts = new HashSet <ConflictingBranches>(); // Remove from `middleConflicts` if we find a deeper one that conflicts var middleConflicts = new HashSet <ConflictingBranches>(); var target = groupToLatest(targetBranch); var possibleConflicts = new Stack <PossibleConflictingBranches>( ( from branchA in initialUpstreamBranchGroups.Select(groupToLatest) from branchB in initialUpstreamBranchGroups.Select(groupToLatest) where branchA.CompareTo(branchB) < 0 select new PossibleConflictingBranches { BranchA = branchA, BranchB = branchB, ConflictWhenSuccess = null } ).Concat( from branch in initialUpstreamBranchGroups.Select(groupToLatest) where target.LatestBranchName != null select new PossibleConflictingBranches { BranchA = branch, BranchB = target, ConflictWhenSuccess = null } ) ); Func <PossibleConflictingBranches, Task <bool> > digDeeper = async(possibleConflict) => { var upstreamBranches = (await GetUpstreamBranches(possibleConflict.BranchA.GroupName, upstreamBranchListings)).Select(groupToLatest).ToImmutableList(); if (upstreamBranches.Count > 0) { // go deeper on the left side foreach (var possible in upstreamBranches.Where(b => b.GroupName != possibleConflict.BranchB.GroupName)) { possibleConflicts.Push(new PossibleConflictingBranches { BranchA = possible, BranchB = possibleConflict.BranchB, ConflictWhenSuccess = new ConflictingBranches { BranchA = possibleConflict.BranchA, BranchB = possibleConflict.BranchB } }); } return(true); } return(false); }; while (possibleConflicts.Count > 0) { if (possibleConflicts.Any(p => p.BranchA.LatestBranchName == null || p.BranchB.LatestBranchName == null)) { return(new IntegrationBranchResult { PendingUpdates = true, }); } var possibleConflict = possibleConflicts.Pop(); if (await repository.IsBadBranch(possibleConflict.BranchA.LatestBranchName) || await repository.IsBadBranch(possibleConflict.BranchB.LatestBranchName)) { // At least one bad branch was found return(new IntegrationBranchResult { PendingUpdates = true, }); } if (leafConflicts.Contains(new ConflictingBranches { BranchA = possibleConflict.BranchA, BranchB = possibleConflict.BranchB })) { continue; } var isSuccessfulMerge = await repository.CanMerge(possibleConflict.BranchA.LatestBranchName, possibleConflict.BranchB.LatestBranchName) ?? await doMerge(possibleConflict.BranchA.LatestBranchName, possibleConflict.BranchB.LatestBranchName, "CONFLICT TEST; DO NOT PUSH"); if (isSuccessfulMerge) { // successful, not a conflict await repository.MarkCanMerge(possibleConflict.BranchA.LatestBranchName, possibleConflict.BranchB.LatestBranchName, true); } else { await repository.MarkCanMerge(possibleConflict.BranchA.LatestBranchName, possibleConflict.BranchB.LatestBranchName, false); // there was a conflict if (possibleConflict.ConflictWhenSuccess.HasValue && unflippedConflicts.Contains(possibleConflict.ConflictWhenSuccess.Value)) { // so remove the intermediary unflippedConflicts.Remove(possibleConflict.ConflictWhenSuccess.Value); } // ...and check deeper var conflict = new ConflictingBranches { BranchA = possibleConflict.BranchA, BranchB = possibleConflict.BranchB }; unflippedConflicts.Add(conflict); if (await digDeeper(possibleConflict)) { // succeeded to dig deeper on branchA } } } foreach (var conflict in unflippedConflicts) { if (await digDeeper(new PossibleConflictingBranches { BranchA = conflict.BranchB, BranchB = conflict.BranchA, ConflictWhenSuccess = null, })) { middleConflicts.Add(new ConflictingBranches { BranchA = conflict.BranchB, BranchB = conflict.BranchA }); } else { // Nothing deeper; this is our conflict leafConflicts.Add(conflict); } } while (possibleConflicts.Count > 0) { var possibleConflict = possibleConflicts.Pop(); if (await repository.IsBadBranch(possibleConflict.BranchA.LatestBranchName) || await repository.IsBadBranch(possibleConflict.BranchB.LatestBranchName)) { // At least one bad branch was found return(new IntegrationBranchResult { PendingUpdates = true, }); } if (leafConflicts.Contains(new ConflictingBranches { BranchA = possibleConflict.BranchA, BranchB = possibleConflict.BranchB })) { continue; } var isSuccessfulMerge = await doMerge(possibleConflict.BranchA.LatestBranchName, possibleConflict.BranchB.LatestBranchName, "CONFLICT TEST; DO NOT PUSH"); if (isSuccessfulMerge) { // successful, not a conflict } else { // there was a conflict if (possibleConflict.ConflictWhenSuccess.HasValue && middleConflicts.Contains(possibleConflict.ConflictWhenSuccess.Value)) { // so remove the intermediary middleConflicts.Remove(possibleConflict.ConflictWhenSuccess.Value); } // ...and check deeper var conflict = new ConflictingBranches { BranchA = possibleConflict.BranchA, BranchB = possibleConflict.BranchB }; if (await digDeeper(possibleConflict)) { // succeeded to dig deeper on branchA middleConflicts.Add(conflict); } else { // Nothing deeper; this is our conflict leafConflicts.Add(conflict); } } } return(new IntegrationBranchResult { Conflicts = leafConflicts.Concat(middleConflicts).Select(c => c.Normalize()).Distinct(), }); }