June 2, 2011

Merging moves

Streams & Branches

In an earlier blog post, I talked a bit about the new integrate engine and some of the new functionality available in 2010.2 with the undoc '-3' flag. Of particular interest to many users was the fact that you could integrate into a codeline where a bunch of files had been moved/renamed/refactored and your source changes would automatically get merged into the right files.

A few people who tried this out noticed that keen though this functionality is, on its own it's not of much help when the files have been moved within the source of the integrate rather than the target. Well, we're not banging rocks together over here, folks. In today's blog post I'll be talking about what's coming up in 2011.1.

One of the major features new to 2011.1 is the ability for the server to schedule resolves for decisions other than how to handle file content changes. This is a very important component of propagating moves between branches, since we need to account for the possibility that moves in different branches will conflict with each other, or that a move might need to be "ignored" in some situations. All of this requires that we solicit input from the user, and so we follow our standard model of having 'p4 integrate' figure out what questions need to be asked, and having 'p4 resolve' get the answers.

Merging moves
This file has been moved differently in different branches.

In this simple example, we are using a mapping that goes from A/... to B/... to integrate one file. In the A branch this file was moved from 1/foo to 1/bar, and in the B branch it was moved from 1/foo to 2/foo.

The branch mapping tells us that A/1/bar should go into B/1/bar. Since there is no common history between those files (B/1/bar does not exist), 'p4 integrate' backs up through the move history of A/1/bar to look for a better basis for finding a match. This brings it to A/1/foo, which maps to B/1/foo. These files do have common history. (Note that this logic would not work if foo lay outside the scope of the branch mapping; the moves to be merged must have happened within the source and/or target branches according to the branch mapping that is being used to perform the integrate.)

B/1/foo#1 is the starting point for finding the target file that we will integrate into. This is a simple matter of following move records in the other direction, which leads to B/2/foo. The output of 'p4 integrate' tells us what file we ended up opening, and if this doesn't agree with the originally specified mapping that is indicated as well:

//depot/itest/move/MergeMove/B/2/foo#1 - integrate from //depot/itest/move/MergeMove/A/1/bar#1 (remapped from //depot/itest/move/MergeMove/B/1/bar)

The "remapped from" also tells us that this integrate will require a 'filename resolve' that will determine what path the target file will be moved to, if any, as a result of the integrate. The 'theirs' (source) path will be the mapped source path, B/1/bar. The 'yours' (target) path is the client file, B/2/foo. The 'base' path is not necessarily the same as the path of the base revision that is used to resolve the content; it is determined by taking the path of the last credited file from the source and mapping it into the target namespace, giving us B/1/foo.

When we run 'p4 resolve', in addition to the normal content resolve we are asked to pick a filename:

c:\client\itest\move\MergeMove\B\2\foo - resolving move to //depot/itest/move/MergeMove/B/1/bar using base //depot/itest/move/MergeMove/B/1/foo
Filename resolve:
at: //depot/itest/move/MergeMove/B/1/bar
ay: //depot/itest/move/MergeMove/B/2/foo
am: //depot/itest/move/MergeMove/B/2/bar
Accept(a) Skip(s) Help(?) am:

The 'base' path is used to merge the 'theirs' and 'yours' paths, producing a suggested result path. In this case, the two moves (1/ -> 2/ and foo -> bar) are combined to produce 2/bar. If the file had only been moved in one of the branches, the suggested result would be either at or ay, and if the file had been moved in both branches and no merge was possible, no suggestion would be given (we would need to pick either at or ay, and possibly make further adjustments with 'p4 move').

Accepting an option other than ay reopens the file for 'move/add' and 'move/delete', similar to running the 'p4 move' command:

Accept(a) Skip(s) Help(?) am: am
//depot/itest/move/MergeMove/B/2/bar - moved from //depot/itest/move/MergeMove/B/2/foo

The contents of A/1/bar and B/2/foo are merged using the same content resolve process that we've always used, so I won't illustrate that here. After we submit, that merged content goes into B/2/bar:

The merged result
The merged result.


The previous screenshots have had everything split out to make all the different paths easily visible, but if you want things more tightly grouped up by branch, Revision Graph's 'File Renames Collapsed' feature is very handy:

File Renames Collapsed
'File Renames Collapsed'

With this new functionality, move actions are being treated more as revisions of a single file's history than they are places where one file ends and another begins. Like content edits, they can be propagated from branch to branch and combined gracefully with other changes when you run 'p4 integrate'.

Good job, integrate.