private async Task<LinkedFileMergeResult> MergeLinkedDocumentGroupAsync( IEnumerable<DocumentId> allLinkedDocuments, IEnumerable<DocumentId> linkedDocumentGroup, LinkedFileDiffMergingSessionInfo sessionInfo, IMergeConflictHandler mergeConflictHandler, CancellationToken cancellationToken) { var groupSessionInfo = new LinkedFileGroupSessionInfo(); // Automatically merge non-conflicting diffs while collecting the conflicting diffs var textDifferencingService = _oldSolution.Workspace.Services.GetService<IDocumentTextDifferencingService>() ?? new DefaultDocumentTextDifferencingService(); var appliedChanges = await textDifferencingService.GetTextChangesAsync(_oldSolution.GetDocument(linkedDocumentGroup.First()), _newSolution.GetDocument(linkedDocumentGroup.First()), cancellationToken).ConfigureAwait(false); var unmergedChanges = new List<UnmergedDocumentChanges>(); foreach (var documentId in linkedDocumentGroup.Skip(1)) { appliedChanges = await AddDocumentMergeChangesAsync( _oldSolution.GetDocument(documentId), _newSolution.GetDocument(documentId), appliedChanges.ToList(), unmergedChanges, groupSessionInfo, textDifferencingService, cancellationToken).ConfigureAwait(false); } var originalDocument = _oldSolution.GetDocument(linkedDocumentGroup.First()); var originalSourceText = await originalDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); // Add comments in source explaining diffs that could not be merged IEnumerable<TextChange> allChanges; IList<TextSpan> mergeConflictResolutionSpan = new List<TextSpan>(); if (unmergedChanges.Any()) { mergeConflictHandler = mergeConflictHandler ?? _oldSolution.GetDocument(linkedDocumentGroup.First()).GetLanguageService<ILinkedFileMergeConflictCommentAdditionService>(); var mergeConflictTextEdits = mergeConflictHandler.CreateEdits(originalSourceText, unmergedChanges); allChanges = MergeChangesWithMergeFailComments(appliedChanges, mergeConflictTextEdits, mergeConflictResolutionSpan, groupSessionInfo); } else { allChanges = appliedChanges; } groupSessionInfo.LinkedDocuments = _newSolution.GetDocumentIdsWithFilePath(originalDocument.FilePath).Length; groupSessionInfo.DocumentsWithChanges = linkedDocumentGroup.Count(); sessionInfo.LogLinkedFileResult(groupSessionInfo); return new LinkedFileMergeResult(allLinkedDocuments, originalSourceText.WithChanges(allChanges), mergeConflictResolutionSpan); }
private async Task<LinkedFileMergeResult> MergeLinkedDocumentGroupAsync( IEnumerable<DocumentId> linkedDocumentGroup, LinkedFileDiffMergingSessionInfo sessionInfo, CancellationToken cancellationToken) { var groupSessionInfo = new LinkedFileGroupSessionInfo(); // Automatically merge non-conflicting diffs while collecting the conflicting diffs var appliedChanges = await newSolution.GetDocument(linkedDocumentGroup.First()).GetTextChangesAsync(oldSolution.GetDocument(linkedDocumentGroup.First())).ConfigureAwait(false); var unmergedChanges = new List<UnmergedDocumentChanges>(); foreach (var documentId in linkedDocumentGroup.Skip(1)) { appliedChanges = await AddDocumentMergeChangesAsync( oldSolution.GetDocument(documentId), newSolution.GetDocument(documentId), appliedChanges.ToList(), unmergedChanges, groupSessionInfo, cancellationToken).ConfigureAwait(false); } var originalDocument = oldSolution.GetDocument(linkedDocumentGroup.First()); var originalSourceText = await originalDocument.GetTextAsync().ConfigureAwait(false); // Add comments in source explaining diffs that could not be merged IEnumerable<TextChange> allChanges; if (unmergedChanges.Any()) { var mergeConflictCommentAdder = originalDocument.GetLanguageService<ILinkedFileMergeConflictCommentAdditionService>(); var commentChanges = mergeConflictCommentAdder.CreateCommentsForUnmergedChanges(originalSourceText, unmergedChanges); allChanges = MergeChangesWithMergeFailComments(appliedChanges, commentChanges, groupSessionInfo); } else { allChanges = appliedChanges; } groupSessionInfo.LinkedDocuments = newSolution.GetDocumentIdsWithFilePath(originalDocument.FilePath).Length; groupSessionInfo.DocumentsWithChanges = linkedDocumentGroup.Count(); sessionInfo.LogLinkedFileResult(groupSessionInfo); return new LinkedFileMergeResult(originalSourceText.WithChanges(allChanges), hasMergeConflicts: unmergedChanges.Any()); }
public void LogLinkedFileResult(LinkedFileGroupSessionInfo info) { LinkedFileGroups.Add(info); }
public static KeyValueLogMessage Create(int sessionId, LinkedFileGroupSessionInfo groupInfo) { return KeyValueLogMessage.Create(m => { m[SessionId] = sessionId.ToString(); m[LinkedDocuments] = groupInfo.LinkedDocuments.ToString(); m[DocumentsWithChanges] = groupInfo.DocumentsWithChanges.ToString(); m[IdenticalDiffs] = groupInfo.IdenticalDiffs.ToString(); m[IsolatedDiffs] = groupInfo.IsolatedDiffs.ToString(); m[OverlappingDistinctDiffs] = groupInfo.OverlappingDistinctDiffs.ToString(); m[OverlappingDistinctDiffsWithSameSpan] = groupInfo.OverlappingDistinctDiffsWithSameSpan.ToString(); m[OverlappingDistinctDiffsWithSameSpanAndSubstringRelation] = groupInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation.ToString(); m[InsertedMergeConflictComments] = groupInfo.InsertedMergeConflictComments.ToString(); m[InsertedMergeConflictCommentsAtAdjustedLocation] = groupInfo.InsertedMergeConflictCommentsAtAdjustedLocation.ToString(); }); }
private IEnumerable<TextChange> MergeChangesWithMergeFailComments(IEnumerable<TextChange> mergedChanges, IEnumerable<TextChange> commentChanges, LinkedFileGroupSessionInfo groupSessionInfo) { var mergedChangesList = NormalizeChanges(mergedChanges).ToList(); var commentChangesList = NormalizeChanges(commentChanges).ToList(); var combinedChanges = new List<TextChange>(); var insertedMergeConflictCommentsAtAdjustedLocation = 0; var commentChangeIndex = 0; foreach (var mergedChange in mergedChangesList) { while (commentChangeIndex < commentChangesList.Count && commentChangesList[commentChangeIndex].Span.End <= mergedChange.Span.Start) { // Add a comment change that does not conflict with any merge change combinedChanges.Add(commentChangesList[commentChangeIndex]); commentChangeIndex++; } if (commentChangeIndex >= commentChangesList.Count || mergedChange.Span.End <= commentChangesList[commentChangeIndex].Span.Start) { // Add a merge change that does not conflict with any comment change combinedChanges.Add(mergedChange); continue; } // The current comment insertion location conflicts with a merge diff location. Add the comment before the diff. var conflictingCommentInsertionLocation = new TextSpan(mergedChange.Span.Start, 0); while (commentChangeIndex < commentChangesList.Count && commentChangesList[commentChangeIndex].Span.Start < mergedChange.Span.End) { combinedChanges.Add(new TextChange(conflictingCommentInsertionLocation, commentChangesList[commentChangeIndex].NewText)); commentChangeIndex++; insertedMergeConflictCommentsAtAdjustedLocation++; } combinedChanges.Add(mergedChange); } while (commentChangeIndex < commentChangesList.Count) { // Add a comment change that does not conflict with any merge change combinedChanges.Add(commentChangesList[commentChangeIndex]); commentChangeIndex++; } groupSessionInfo.InsertedMergeConflictComments = commentChanges.Count(); groupSessionInfo.InsertedMergeConflictCommentsAtAdjustedLocation = insertedMergeConflictCommentsAtAdjustedLocation; return NormalizeChanges(combinedChanges); }
private static async Task<List<TextChange>> AddDocumentMergeChangesAsync( Document oldDocument, Document newDocument, List<TextChange> cumulativeChanges, List<UnmergedDocumentChanges> unmergedChanges, LinkedFileGroupSessionInfo groupSessionInfo, CancellationToken cancellationToken) { var unmergedDocumentChanges = new List<TextChange>(); var successfullyMergedChanges = new List<TextChange>(); int cumulativeChangeIndex = 0; foreach (var change in await newDocument.GetTextChangesAsync(oldDocument).ConfigureAwait(false)) { while (cumulativeChangeIndex < cumulativeChanges.Count && cumulativeChanges[cumulativeChangeIndex].Span.End < change.Span.Start) { // Existing change that does not overlap with the current change in consideration successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]); cumulativeChangeIndex++; groupSessionInfo.IsolatedDiffs++; } if (cumulativeChangeIndex < cumulativeChanges.Count) { var cumulativeChange = cumulativeChanges[cumulativeChangeIndex]; if (!cumulativeChange.Span.IntersectsWith(change.Span)) { // The current change in consideration does not intersect with any existing change successfullyMergedChanges.Add(change); groupSessionInfo.IsolatedDiffs++; } else { if (change.Span != cumulativeChange.Span || change.NewText != cumulativeChange.NewText) { // The current change in consideration overlaps an existing change but // the changes are not identical. unmergedDocumentChanges.Add(change); groupSessionInfo.OverlappingDistinctDiffs++; if (change.Span == cumulativeChange.Span) { groupSessionInfo.OverlappingDistinctDiffsWithSameSpan++; if (change.NewText.Contains(cumulativeChange.NewText) || cumulativeChange.NewText.Contains(change.NewText)) { groupSessionInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation++; } } } else { // The current change in consideration is identical to an existing change successfullyMergedChanges.Add(change); cumulativeChangeIndex++; groupSessionInfo.IdenticalDiffs++; } } } else { // The current change in consideration does not intersect with any existing change successfullyMergedChanges.Add(change); groupSessionInfo.IsolatedDiffs++; } } while (cumulativeChangeIndex < cumulativeChanges.Count) { // Existing change that does not overlap with the current change in consideration successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]); cumulativeChangeIndex++; groupSessionInfo.IsolatedDiffs++; } if (unmergedDocumentChanges.Any()) { unmergedChanges.Add(new UnmergedDocumentChanges( unmergedDocumentChanges.AsEnumerable(), await oldDocument.GetTextAsync(cancellationToken).ConfigureAwait(false), oldDocument.Project.Name)); } return successfullyMergedChanges; }
private IEnumerable <TextChange> MergeChangesWithMergeFailComments( IEnumerable <TextChange> mergedChanges, IEnumerable <TextChange> commentChanges, IList <TextSpan> mergeConflictResolutionSpans, LinkedFileGroupSessionInfo groupSessionInfo) { var mergedChangesList = NormalizeChanges(mergedChanges).ToList(); var commentChangesList = NormalizeChanges(commentChanges).ToList(); var combinedChanges = new List <TextChange>(); var insertedMergeConflictCommentsAtAdjustedLocation = 0; var commentChangeIndex = 0; var currentPositionDelta = 0; foreach (var mergedChange in mergedChangesList) { while (commentChangeIndex < commentChangesList.Count && commentChangesList[commentChangeIndex].Span.End <= mergedChange.Span.Start) { // Add a comment change that does not conflict with any merge change combinedChanges.Add(commentChangesList[commentChangeIndex]); mergeConflictResolutionSpans.Add(new TextSpan(commentChangesList[commentChangeIndex].Span.Start + currentPositionDelta, commentChangesList[commentChangeIndex].NewText.Length)); currentPositionDelta += (commentChangesList[commentChangeIndex].NewText.Length - commentChangesList[commentChangeIndex].Span.Length); commentChangeIndex++; } if (commentChangeIndex >= commentChangesList.Count || mergedChange.Span.End <= commentChangesList[commentChangeIndex].Span.Start) { // Add a merge change that does not conflict with any comment change combinedChanges.Add(mergedChange); currentPositionDelta += (mergedChange.NewText.Length - mergedChange.Span.Length); continue; } // The current comment insertion location conflicts with a merge diff location. Add the comment before the diff. var conflictingCommentInsertionLocation = new TextSpan(mergedChange.Span.Start, 0); while (commentChangeIndex < commentChangesList.Count && commentChangesList[commentChangeIndex].Span.Start < mergedChange.Span.End) { combinedChanges.Add(new TextChange(conflictingCommentInsertionLocation, commentChangesList[commentChangeIndex].NewText)); mergeConflictResolutionSpans.Add(new TextSpan(commentChangesList[commentChangeIndex].Span.Start + currentPositionDelta, commentChangesList[commentChangeIndex].NewText.Length)); currentPositionDelta += commentChangesList[commentChangeIndex].NewText.Length; commentChangeIndex++; insertedMergeConflictCommentsAtAdjustedLocation++; } combinedChanges.Add(mergedChange); currentPositionDelta += (mergedChange.NewText.Length - mergedChange.Span.Length); } while (commentChangeIndex < commentChangesList.Count) { // Add a comment change that does not conflict with any merge change combinedChanges.Add(commentChangesList[commentChangeIndex]); mergeConflictResolutionSpans.Add(new TextSpan(commentChangesList[commentChangeIndex].Span.Start + currentPositionDelta, commentChangesList[commentChangeIndex].NewText.Length)); currentPositionDelta += (commentChangesList[commentChangeIndex].NewText.Length - commentChangesList[commentChangeIndex].Span.Length); commentChangeIndex++; } groupSessionInfo.InsertedMergeConflictComments = commentChanges.Count(); groupSessionInfo.InsertedMergeConflictCommentsAtAdjustedLocation = insertedMergeConflictCommentsAtAdjustedLocation; return(NormalizeChanges(combinedChanges)); }
private static async Task <ImmutableArray <TextChange> > AddDocumentMergeChangesAsync( Document oldDocument, Document newDocument, List <TextChange> cumulativeChanges, List <UnmergedDocumentChanges> unmergedChanges, LinkedFileGroupSessionInfo groupSessionInfo, IDocumentTextDifferencingService textDiffService, CancellationToken cancellationToken) { var unmergedDocumentChanges = new List <TextChange>(); var successfullyMergedChanges = ArrayBuilder <TextChange> .GetInstance(); int cumulativeChangeIndex = 0; var textchanges = await textDiffService.GetTextChangesAsync(oldDocument, newDocument, cancellationToken).ConfigureAwait(false); foreach (var change in textchanges) { while (cumulativeChangeIndex < cumulativeChanges.Count && cumulativeChanges[cumulativeChangeIndex].Span.End < change.Span.Start) { // Existing change that does not overlap with the current change in consideration successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]); cumulativeChangeIndex++; groupSessionInfo.IsolatedDiffs++; } if (cumulativeChangeIndex < cumulativeChanges.Count) { var cumulativeChange = cumulativeChanges[cumulativeChangeIndex]; if (!cumulativeChange.Span.IntersectsWith(change.Span)) { // The current change in consideration does not intersect with any existing change successfullyMergedChanges.Add(change); groupSessionInfo.IsolatedDiffs++; } else { if (change.Span != cumulativeChange.Span || change.NewText != cumulativeChange.NewText) { // The current change in consideration overlaps an existing change but // the changes are not identical. unmergedDocumentChanges.Add(change); groupSessionInfo.OverlappingDistinctDiffs++; if (change.Span == cumulativeChange.Span) { groupSessionInfo.OverlappingDistinctDiffsWithSameSpan++; if (change.NewText.Contains(cumulativeChange.NewText) || cumulativeChange.NewText.Contains(change.NewText)) { groupSessionInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation++; } } } else { // The current change in consideration is identical to an existing change successfullyMergedChanges.Add(change); cumulativeChangeIndex++; groupSessionInfo.IdenticalDiffs++; } } } else { // The current change in consideration does not intersect with any existing change successfullyMergedChanges.Add(change); groupSessionInfo.IsolatedDiffs++; } } while (cumulativeChangeIndex < cumulativeChanges.Count) { // Existing change that does not overlap with the current change in consideration successfullyMergedChanges.Add(cumulativeChanges[cumulativeChangeIndex]); cumulativeChangeIndex++; groupSessionInfo.IsolatedDiffs++; } if (unmergedDocumentChanges.Any()) { unmergedChanges.Add(new UnmergedDocumentChanges( unmergedDocumentChanges.AsEnumerable(), oldDocument.Project.Name, oldDocument.Id)); } return(successfullyMergedChanges.ToImmutableAndFree()); }