Project-system: Merging unsaved changes and project file changes is broken

Created on 4 Sep 2019  路  7Comments  路  Source: dotnet/project-system

This is most likely the underlying cause of #5181 and #4940, and reproduces with:

  1. Open the project file editor for any project
  2. Make some changes, but _don't_ save
  3. Externally, make changes to the project file and save
  4. Repeat step 3

After step 4 the unsaved changes from step 2 are removed.

The problem is with the MergeBuffer method which intends to do a 3-way merge between the last known state of the editor, the incoming file changes, and the current state of the text document.

Bug Feature-Project-File-Editing Needs-CPS-work Triage-Approved

All 7 comments

There is no real built-in support for doing a 3-way merge and most of the examples of using diff are deeply buried in other code.

For doing a merge -- which is really what you are doing here -- it might be best to talk to the TFS people to see what algorithms they are using. You can fudge things to an extent:

  1. Given a common version V1
  2. A set of user edits that turn V1 -> V1e and
  3. A set of project changes that turn V1 -> v1p

IIRC, CPS is using diff to compute the changes from V1 -> V1e and V1 -> v1p and then use a LiveShare-like algorithm to resolve the conflicts. I can help you with improving that but I suspect you may end up doing better with either some customized merging logic from TFS or by looking at the user edits and translating them into some action that CPS understands (e.g. add an attribute).

Spent some time digging in to this. For posterity:

The current algorithm works like this:

Project File: Foo Bar
User Edit : Frob Bar

File change 1, Adding Baz:
Base snapshot: Foo Bar
Project file : Foo Bar Baz
Text buffer : Frob Bar

Diff between base and file: Added Baz
Result after applying diff to text buffer: Frob Bar Baz

File change 2, Adding Yolo:
Base snapshot: Frob Bar Baz
Project file : Foo Bar Baz Yolo
Text buffer : Frob Bar Baz

Diff between base and file: Change Frob to Foo, Added Yolo
Result after applying diff to text buffer: Foo Bar Baz Yolo

As can be seen, because the algorithm prioritises the project file, the change to Foo is actually reversed because the algorithm thinks the change has been applied to the project file.

The logical thing to do here is not update the base snapshot after every change, but if we update the code to only update the baseline when the file is saved, leaving it more similar to the project file:

Project File: Foo Bar
User Edit : Frob Bar

File change 1, Adding Baz:
Base snapshot: Foo Bar
Project file : Foo Bar Baz
Text buffer : Frob Bar

Diff between base and file: Added Baz
Result after applying diff to text buffer: Frob Bar Baz

File change 2, Adding Yolo:
Base snapshot: Foo Bar
Project file : Foo Bar Baz Yolo
Text buffer : Frob Bar Baz

Diff between base and file: Added Baz Yolo
Result after applying diff to text buffer: Frob Bar Baz Baz Yolo

As we can see, now the Foo change is left alone, but each individual addition is seen as adding the whole set.

The strategy I'm working on that looks promising is essentially:

Given a common base V1, a user edited version of that V1e, and the project file contents V1p:

  • Replace contents of the editor with V1p
  • Replace user edits from V1 to V1e
  • Reset snapshot V1 to V1p

The above works fine if user edits are made above any changes to the project file. If they're below then it fails because there is no way to know where the user edits should be after inserting the project changes. :(

PR complete.

Was this page helpful?
0 / 5 - 0 ratings