提交 4fffe945 编写于 作者: D dpoeschl

Inline Rename in Linked Files

Inline Rename performs a rename operation on an initial solution to produce a new solution with all references renamed, conflicts resolved, etc. This new solution may contain linked documents with un-merged (and potentially conflicting) contents. This change applies this linked file merging step to present properly merged changes and rename tags.

If a file on disk D.cs is linked in two Projects P1 and P2, then there are two documents D1 and D2, with their contents being equal. When a symbol is renamed, we have a new solution with documents D1' and D2'. We calculate the text changes between D1 & D1' (C1), and D2 & D2' (C2), and merge these changes C1 and C2 into a final set of changes which produces the new merged Documents, D1m and D2m, with their contents being equal. Each document D1' and D2' knows its rename information, such as the location of definitions, references, and resolved/unresolved conflicts. This information is mapped to D1m/D2m for display in the editor by tracking spans from D1' to D1m, and D2' to D2m. The final locations of these tracking spans is their location in the merged buffer contents. (changeset 1389424)
上级 27044db4
......@@ -2,23 +2,24 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis
{
internal abstract class AbstractLinkedFileMergeConflictCommentAdditionService : ILinkedFileMergeConflictCommentAdditionService
internal abstract class AbstractLinkedFileMergeConflictCommentAdditionService : IMergeConflictHandler, ILanguageService, ILinkedFileMergeConflictCommentAdditionService
{
internal abstract string GetConflictCommentText(string header, string beforeString, string afterString);
public IEnumerable<TextChange> CreateCommentsForUnmergedChanges(SourceText originalSourceText, IEnumerable<UnmergedDocumentChanges> unmergedChanges)
public IEnumerable<TextChange> CreateEdits(SourceText originalSourceText, IEnumerable<UnmergedDocumentChanges> unmergedChanges)
{
var commentChanges = new List<TextChange>();
foreach (var documentWithChanges in unmergedChanges)
{
var partitionedChanges = PartitionChangesForDocument(documentWithChanges.UnmergedChanges, originalSourceText);
var comments = GetCommentChangesForDocument(partitionedChanges, documentWithChanges.ProjectName, originalSourceText, documentWithChanges.Text);
var comments = GetCommentChangesForDocument(partitionedChanges, documentWithChanges.ProjectName, originalSourceText);
commentChanges.AddRange(comments);
}
......@@ -56,7 +57,7 @@ private IEnumerable<IEnumerable<TextChange>> PartitionChangesForDocument(IEnumer
return partitionedChanges;
}
private List<TextChange> GetCommentChangesForDocument(IEnumerable<IEnumerable<TextChange>> partitionedChanges, string projectName, SourceText oldDocumentText, SourceText newDocumentText)
private List<TextChange> GetCommentChangesForDocument(IEnumerable<IEnumerable<TextChange>> partitionedChanges, string projectName, SourceText oldDocumentText)
{
var commentChanges = new List<TextChange>();
......
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis
{
public partial class Solution
internal class DefaultDocumentTextDifferencingService : IDocumentTextDifferencingService
{
private sealed class LinkedFileMergeResult
public async Task<IEnumerable<TextChange>> GetTextChangesAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken)
{
public SourceText MergedSourceText { get; internal set; }
public bool HasMergeConflicts { get; private set; }
public LinkedFileMergeResult(SourceText mergedSourceText, bool hasMergeConflicts)
{
MergedSourceText = mergedSourceText;
HasMergeConflicts = hasMergeConflicts;
}
return await newDocument.GetTextChangesAsync(oldDocument, cancellationToken).ConfigureAwait(false);
}
}
}
\ No newline at end of file
}
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis
{
internal interface ILinkedFileMergeConflictCommentAdditionService : ILanguageService
internal interface ILinkedFileMergeConflictCommentAdditionService : ILanguageService, IMergeConflictHandler
{
IEnumerable<TextChange> CreateCommentsForUnmergedChanges(SourceText originalSourceText, IEnumerable<UnmergedDocumentChanges> unmergedChanges);
}
}
\ No newline at end of file
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis
{
internal interface IMergeConflictHandler
{
IEnumerable<TextChange> CreateEdits(SourceText originalSourceText, IEnumerable<UnmergedDocumentChanges> unmergedChanges);
}
}
\ No newline at end of file
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
internal sealed class LinkedFileDiffMergingSession
{
private readonly bool logSessionInfo;
private Solution oldSolution;
private Solution newSolution;
private SolutionChanges solutionChanges;
public LinkedFileDiffMergingSession(Solution oldSolution, Solution newSolution, SolutionChanges solutionChanges, bool logSessionInfo)
{
this.oldSolution = oldSolution;
this.newSolution = newSolution;
this.solutionChanges = solutionChanges;
this.logSessionInfo = logSessionInfo;
}
internal async Task<LinkedFileMergeSessionResult> MergeDiffsAsync(IMergeConflictHandler mergeConflictHandler, CancellationToken cancellationToken)
{
LinkedFileDiffMergingSessionInfo sessionInfo = new LinkedFileDiffMergingSessionInfo();
var linkedDocumentGroupsWithChanges = solutionChanges
.GetProjectChanges()
.SelectMany(p => p.GetChangedDocuments())
.GroupBy(d => oldSolution.GetDocument(d).FilePath, StringComparer.OrdinalIgnoreCase);
var linkedFileMergeResults = new List<LinkedFileMergeResult>();
var updatedSolution = newSolution;
foreach (var linkedDocumentsWithChanges in linkedDocumentGroupsWithChanges)
{
var documentInNewSolution = newSolution.GetDocument(linkedDocumentsWithChanges.First());
// Ensure the first document in the group is the first in the list of
var allLinkedDocuments = documentInNewSolution.GetLinkedDocumentIds().Add(documentInNewSolution.Id);
if (allLinkedDocuments.Length == 1)
{
continue;
}
SourceText mergedText;
if (linkedDocumentsWithChanges.Count() > 1)
{
var mergeGroupResult = await MergeLinkedDocumentGroupAsync(allLinkedDocuments, linkedDocumentsWithChanges, sessionInfo, mergeConflictHandler, cancellationToken).ConfigureAwait(false);
linkedFileMergeResults.Add(mergeGroupResult);
mergedText = mergeGroupResult.MergedSourceText;
}
else
{
mergedText = await newSolution.GetDocument(linkedDocumentsWithChanges.Single()).GetTextAsync(cancellationToken).ConfigureAwait(false);
}
foreach (var documentId in allLinkedDocuments)
{
updatedSolution = updatedSolution.WithDocumentText(documentId, mergedText);
}
}
LogLinkedFileDiffMergingSessionInfo(sessionInfo);
return new LinkedFileMergeSessionResult(updatedSolution, linkedFileMergeResults);
}
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().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 static async Task<IEnumerable<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 = new List<TextChange>();
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;
}
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 IEnumerable<TextChange> NormalizeChanges(IEnumerable<TextChange> changes)
{
if (changes.Count() <= 1)
{
return changes;
}
changes = changes.OrderBy(c => c.Span.Start);
var normalizedChanges = new List<TextChange>();
var currentChange = changes.First();
foreach (var nextChange in changes.Skip(1))
{
if (nextChange.Span.Start == currentChange.Span.End)
{
currentChange = new TextChange(TextSpan.FromBounds(currentChange.Span.Start, nextChange.Span.End), currentChange.NewText + nextChange.NewText);
}
else
{
normalizedChanges.Add(currentChange);
currentChange = nextChange;
}
}
normalizedChanges.Add(currentChange);
return normalizedChanges;
}
private void LogLinkedFileDiffMergingSessionInfo(LinkedFileDiffMergingSessionInfo sessionInfo)
{
// don't report telemetry
if (!this.logSessionInfo)
{
return;
}
var sessionId = SessionLogMessage.GetNextId();
Logger.Log(FunctionId.Workspace_Solution_LinkedFileDiffMergingSession, SessionLogMessage.Create(sessionId, sessionInfo));
foreach (var groupInfo in sessionInfo.LinkedFileGroups)
{
Logger.Log(FunctionId.Workspace_Solution_LinkedFileDiffMergingSession_LinkedFileGroup, SessionLogMessage.Create(sessionId, groupInfo));
}
}
internal static class SessionLogMessage
{
private const string SessionId = "SessionId";
private const string HasLinkedFile = "HasLinkedFile";
private const string LinkedDocuments = "LinkedDocuments";
private const string DocumentsWithChanges = "DocumentsWithChanges";
private const string IdenticalDiffs = "IdenticalDiffs";
private const string IsolatedDiffs = "IsolatedDiffs";
private const string OverlappingDistinctDiffs = "OverlappingDistinctDiffs";
private const string OverlappingDistinctDiffsWithSameSpan = "OverlappingDistinctDiffsWithSameSpan";
private const string OverlappingDistinctDiffsWithSameSpanAndSubstringRelation = "OverlappingDistinctDiffsWithSameSpanAndSubstringRelation";
private const string InsertedMergeConflictComments = "InsertedMergeConflictComments";
private const string InsertedMergeConflictCommentsAtAdjustedLocation = "InsertedMergeConflictCommentsAtAdjustedLocation";
public static KeyValueLogMessage Create(int sessionId, LinkedFileDiffMergingSessionInfo sessionInfo)
{
return KeyValueLogMessage.Create(m =>
{
m[SessionId] = sessionId.ToString();
m[HasLinkedFile] = (sessionInfo.LinkedFileGroups.Count > 0).ToString();
});
}
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();
});
}
public static int GetNextId()
{
return LogAggregator.GetNextId();
}
}
internal class LinkedFileDiffMergingSessionInfo
{
public readonly List<LinkedFileGroupSessionInfo> LinkedFileGroups = new List<LinkedFileGroupSessionInfo>();
public void LogLinkedFileResult(LinkedFileGroupSessionInfo info)
{
LinkedFileGroups.Add(info);
}
}
internal class LinkedFileGroupSessionInfo
{
public int LinkedDocuments;
public int DocumentsWithChanges;
public int IsolatedDiffs;
public int IdenticalDiffs;
public int OverlappingDistinctDiffs;
public int OverlappingDistinctDiffsWithSameSpan;
public int OverlappingDistinctDiffsWithSameSpanAndSubstringRelation;
public int InsertedMergeConflictComments;
public int InsertedMergeConflictCommentsAtAdjustedLocation;
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis
{
internal sealed class LinkedFileMergeResult
{
public IEnumerable<DocumentId> DocumentIds { get; internal set; }
public SourceText MergedSourceText { get; internal set; }
public IEnumerable<TextSpan> MergeConflictResolutionSpans { get; private set; }
public bool HasMergeConflicts { get { return MergeConflictResolutionSpans.Any(); } }
public LinkedFileMergeResult(IEnumerable<DocumentId> documentIds, SourceText mergedSourceText, IEnumerable<TextSpan> mergeConflictResolutionSpans)
{
DocumentIds = documentIds;
MergedSourceText = mergedSourceText;
MergeConflictResolutionSpans = mergeConflictResolutionSpans;
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis
{
internal sealed class LinkedFileMergeSessionResult
{
public Solution MergedSolution { get; private set; }
private readonly Dictionary<DocumentId, IEnumerable<TextSpan>> mergeConflictCommentSpans = new Dictionary<DocumentId, IEnumerable<TextSpan>>();
public Dictionary<DocumentId, IEnumerable<TextSpan>> MergeConflictCommentSpans { get { return mergeConflictCommentSpans; } }
public LinkedFileMergeSessionResult(Solution mergedSolution, IEnumerable<LinkedFileMergeResult> fileMergeResults)
{
this.MergedSolution = mergedSolution;
foreach (var fileMergeResult in fileMergeResults)
{
foreach (var documentId in fileMergeResult.DocumentIds)
{
mergeConflictCommentSpans.Add(documentId, fileMergeResult.MergeConflictResolutionSpans);
}
}
}
}
}
\ No newline at end of file
......@@ -8,14 +8,14 @@ namespace Microsoft.CodeAnalysis
internal sealed class UnmergedDocumentChanges
{
public IEnumerable<TextChange> UnmergedChanges { get; private set; }
public SourceText Text { get; private set; }
public string ProjectName { get; private set; }
public DocumentId DocumentId { get; private set; }
public UnmergedDocumentChanges(IEnumerable<TextChange> unmergedChanges, SourceText text, string projectName)
public UnmergedDocumentChanges(IEnumerable<TextChange> unmergedChanges, string projectName, DocumentId documentId)
{
UnmergedChanges = unmergedChanges;
Text = text;
ProjectName = projectName;
DocumentId = documentId;
}
}
}
......@@ -140,7 +140,8 @@ private static async Task<bool> ShouldIncludeSymbolAsync(ISymbol referencedSymbo
if (referencedSymbol.Kind == SymbolKind.Method ||
referencedSymbol.Kind == SymbolKind.Property ||
referencedSymbol.Kind == SymbolKind.Event ||
referencedSymbol.Kind == SymbolKind.TypeParameter)
referencedSymbol.Kind == SymbolKind.TypeParameter ||
referencedSymbol.Kind == SymbolKind.Field)
{
if (referencedSymbol.Kind == originalSymbol.Kind &&
string.Compare(TrimNameToAfterLastDot(referencedSymbol.Name), TrimNameToAfterLastDot(originalSymbol.Name), StringComparison.OrdinalIgnoreCase) == 0 &&
......
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis
{
internal interface IDocumentTextDifferencingService : IWorkspaceService
{
Task<IEnumerable<TextChange>> GetTextChangesAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken);
}
}
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
public partial class Solution
{
private sealed class LinkedFileDiffMergingSession
{
private readonly bool logSessionInfo;
private Solution oldSolution;
private Solution newSolution;
private SolutionChanges solutionChanges;
public LinkedFileDiffMergingSession(Solution oldSolution, Solution newSolution, SolutionChanges solutionChanges, bool logSessionInfo)
{
this.oldSolution = oldSolution;
this.newSolution = newSolution;
this.solutionChanges = solutionChanges;
this.logSessionInfo = logSessionInfo;
}
internal async Task<Solution> MergeDiffsAsync(CancellationToken cancellationToken)
{
LinkedFileDiffMergingSessionInfo sessionInfo = new LinkedFileDiffMergingSessionInfo();
var linkedDocumentGroupsWithChanges = solutionChanges
.GetProjectChanges()
.SelectMany(p => p.GetChangedDocuments())
.GroupBy(d => oldSolution.GetDocument(d).FilePath, StringComparer.OrdinalIgnoreCase);
var updatedSolution = newSolution;
foreach (var linkedDocumentGroup in linkedDocumentGroupsWithChanges)
{
var allLinkedDocuments = newSolution.GetDocumentIdsWithFilePath(newSolution.GetDocumentState(linkedDocumentGroup.First()).FilePath);
if (allLinkedDocuments.Length == 1)
{
continue;
}
SourceText mergedText;
if (linkedDocumentGroup.Count() > 1)
{
mergedText = (await MergeLinkedDocumentGroupAsync(linkedDocumentGroup, sessionInfo, cancellationToken).ConfigureAwait(false)).MergedSourceText;
}
else
{
mergedText = await newSolution.GetDocument(linkedDocumentGroup.Single()).GetTextAsync(cancellationToken).ConfigureAwait(false);
}
foreach (var documentId in allLinkedDocuments)
{
updatedSolution = updatedSolution.WithDocumentText(documentId, mergedText);
}
}
LogLinkedFileDiffMergingSessionInfo(sessionInfo);
return updatedSolution;
}
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());
}
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, 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 IEnumerable<TextChange> NormalizeChanges(IEnumerable<TextChange> changes)
{
if (changes.Count() <= 1)
{
return changes;
}
changes = changes.OrderBy(c => c.Span.Start);
var normalizedChanges = new List<TextChange>();
var currentChange = changes.First();
foreach (var nextChange in changes.Skip(1))
{
if (nextChange.Span.Start == currentChange.Span.End)
{
currentChange = new TextChange(TextSpan.FromBounds(currentChange.Span.Start, nextChange.Span.End), currentChange.NewText + nextChange.NewText);
}
else
{
normalizedChanges.Add(currentChange);
currentChange = nextChange;
}
}
normalizedChanges.Add(currentChange);
return normalizedChanges;
}
private void LogLinkedFileDiffMergingSessionInfo(LinkedFileDiffMergingSessionInfo sessionInfo)
{
// don't report telemetry
if (!this.logSessionInfo)
{
return;
}
var sessionId = SessionLogMessasge.GetNextId();
Logger.Log(FunctionId.Workspace_Solution_LinkedFileDiffMergingSession, SessionLogMessasge.Create(sessionId, sessionInfo));
foreach (var groupInfo in sessionInfo.LinkedFileGroups)
{
Logger.Log(FunctionId.Workspace_Solution_LinkedFileDiffMergingSession_LinkedFileGroup, SessionLogMessasge.Create(sessionId, groupInfo));
}
}
private static class SessionLogMessasge
{
private const string SessionId = "SessionId";
private const string HasLinkedFile = "HasLinkedFile";
private const string LinkedDocuments = "LinkedDocuments";
private const string DocumentsWithChanges = "DocumentsWithChanges";
private const string IdenticalDiffs = "IdenticalDiffs";
private const string IsolatedDiffs = "IsolatedDiffs";
private const string OverlappingDistinctDiffs = "OverlappingDistinctDiffs";
private const string OverlappingDistinctDiffsWithSameSpan = "OverlappingDistinctDiffsWithSameSpan";
private const string OverlappingDistinctDiffsWithSameSpanAndSubstringRelation = "OverlappingDistinctDiffsWithSameSpanAndSubstringRelation";
private const string InsertedMergeConflictComments = "InsertedMergeConflictComments";
private const string InsertedMergeConflictCommentsAtAdjustedLocation = "InsertedMergeConflictCommentsAtAdjustedLocation";
public static KeyValueLogMessage Create(int sessionId, LinkedFileDiffMergingSessionInfo sessionInfo)
{
return KeyValueLogMessage.Create(m =>
{
m[SessionId] = sessionId.ToString();
m[HasLinkedFile] = (sessionInfo.LinkedFileGroups.Count > 0).ToString();
});
}
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();
});
}
public static int GetNextId()
{
return LogAggregator.GetNextId();
}
}
private class LinkedFileDiffMergingSessionInfo
{
public readonly List<LinkedFileGroupSessionInfo> LinkedFileGroups = new List<LinkedFileGroupSessionInfo>();
public void LogLinkedFileResult(LinkedFileGroupSessionInfo info)
{
LinkedFileGroups.Add(info);
}
}
private class LinkedFileGroupSessionInfo
{
public int LinkedDocuments;
public int DocumentsWithChanges;
public int IsolatedDiffs;
public int IdenticalDiffs;
public int OverlappingDistinctDiffs;
public int OverlappingDistinctDiffsWithSameSpan;
public int OverlappingDistinctDiffsWithSameSpanAndSubstringRelation;
public int InsertedMergeConflictComments;
public int InsertedMergeConflictCommentsAtAdjustedLocation;
}
}
}
}
\ No newline at end of file
......@@ -1487,11 +1487,16 @@ public Solution WithAdditionalDocumentText(DocumentId documentId, SourceText tex
return newSolution;
}
internal async Task<Solution> WithMergedLinkedFileChangesAsync(Solution oldSolution, SolutionChanges? solutionChanges = null, CancellationToken cancellationToken = default(CancellationToken))
internal async Task<Solution> WithMergedLinkedFileChangesAsync(
Solution oldSolution,
SolutionChanges? solutionChanges = null,
IMergeConflictHandler mergeConflictHandler = null,
CancellationToken cancellationToken = default(CancellationToken))
{
// we only log sessioninfo for actual changes committed to workspace which should exclude ones from preview
var session = new LinkedFileDiffMergingSession(oldSolution, this, solutionChanges ?? this.GetChanges(oldSolution), logSessionInfo: solutionChanges != null);
return await session.MergeDiffsAsync(cancellationToken).ConfigureAwait(false);
return (await session.MergeDiffsAsync(mergeConflictHandler, cancellationToken).ConfigureAwait(false)).MergedSolution;
}
private SolutionBranch firstBranch;
......
......@@ -852,7 +852,7 @@ public virtual bool TryApplyChanges(Solution newSolution)
var solutionChanges = newSolution.GetChanges(oldSolution);
this.CheckAllowedSolutionChanges(solutionChanges);
var solutionWithLinkedFileChangesMerged = newSolution.WithMergedLinkedFileChangesAsync(oldSolution, solutionChanges, CancellationToken.None).Result;
var solutionWithLinkedFileChangesMerged = newSolution.WithMergedLinkedFileChangesAsync(oldSolution, solutionChanges, cancellationToken: CancellationToken.None).Result;
solutionChanges = solutionWithLinkedFileChangesMerged.GetChanges(oldSolution);
// process all project changes
......
......@@ -370,6 +370,8 @@
<Compile Include="CodeRefactorings\CodeRefactoringContext.cs" />
<Compile Include="Differencing\LongestCommonImmutableArraySubsequence.cs" />
<Compile Include="Differencing\SequenceEdit.cs" />
<Compile Include="LinkedFileDiffMerging\LinkedFileMergeSessionResult.cs" />
<Compile Include="LinkedFileDiffMerging\DefaultDocumentTextDifferencingService.cs" />
<Compile Include="Log\AggregateLogger.cs" />
<Compile Include="Log\KeyValueLogMessage.cs" />
<Compile Include="Log\LogAggregator.cs" />
......@@ -842,8 +844,10 @@
<Compile Include="Workspace\Host\TextFactory\TextFactoryServiceFactory.cs" />
<Compile Include="Workspace\Host\TextFactory\TextFactoryServiceFactory.TextFactoryService.cs" />
<Compile Include="Workspace\PrimaryWorkspace.cs" />
<Compile Include="Workspace\Solution\AbstractLinkedFileMergeConflictCommentAdditionService.cs" />
<Compile Include="LinkedFileDiffMerging\AbstractLinkedFileMergeConflictCommentAdditionService.cs" />
<Compile Include="Workspace\Solution\AdditionalDocumentStream.cs" />
<Compile Include="LinkedFileDiffMerging\IMergeConflictHandler.cs" />
<Compile Include="Workspace\Solution\IDocumentTextDifferencingService.cs" />
<Compile Include="Workspace\Solution\MetadataReferenceManager.cs" />
<Compile Include="Workspace\Solution\TextDocumentState.cs">
<ExcludeFromStyleCop>true</ExcludeFromStyleCop>
......@@ -858,7 +862,7 @@
<Compile Include="Workspace\Solution\DocumentState.cs" />
<Compile Include="Workspace\Solution\DocumentState.EquivalenceResult.cs" />
<Compile Include="Workspace\Solution\DocumentState_SimpleProperties.cs" />
<Compile Include="Workspace\Solution\ILinkedFileMergeConflictCommentAdditionService.cs" />
<Compile Include="LinkedFileDiffMerging\ILinkedFileMergeConflictCommentAdditionService.cs" />
<Compile Include="Workspace\Solution\MetadataOnlyImage.cs" />
<Compile Include="Workspace\Solution\MetadataOnlyReference.cs" />
<Compile Include="Workspace\Solution\PreservationMode.cs" />
......@@ -883,8 +887,10 @@
<Compile Include="Workspace\Solution\Solution.cs">
<ExcludeFromStyleCop>true</ExcludeFromStyleCop>
</Compile>
<Compile Include="Workspace\Solution\Solution.LinkedFileDiffMergingSession.cs" />
<Compile Include="Workspace\Solution\Solution.LinkedFileMergeResult.cs" />
<Compile Include="LinkedFileDiffMerging\LinkedFileDiffMergingSession.cs">
<ExcludeFromStyleCop>true</ExcludeFromStyleCop>
</Compile>
<Compile Include="LinkedFileDiffMerging\LinkedFileMergeResult.cs" />
<Compile Include="Workspace\Solution\SolutionChanges.cs" />
<Compile Include="Workspace\Solution\SolutionId.cs" />
<Compile Include="Workspace\Solution\SolutionInfo.cs" />
......@@ -893,7 +899,7 @@
<Compile Include="Workspace\Solution\TextAndVersion.cs" />
<Compile Include="Workspace\Solution\TextLoader.cs" />
<Compile Include="Workspace\Solution\TreeAndVersion.cs" />
<Compile Include="Workspace\Solution\UnmergedDocumentChanges.cs" />
<Compile Include="LinkedFileDiffMerging\UnmergedDocumentChanges.cs" />
<Compile Include="Workspace\Solution\VersionStamp.cs" />
<Compile Include="Workspace\TextExtensions.cs" />
<Compile Include="Workspace\Workspace.cs" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册