I have two streams, Source
and Target
. A file was at some point created on Source
, called FileA
. Then, Target stream was created branching from Source
. FileA
on Source
was then renamed to FileB
.
Now I need to merge this change into Target
. The result should be FileA
on Target
being renamed to FileB
.
I can generally merge a single file using p4 integrate
and specifying fromFiles
and toFiles
.
This workflow breaks for files marked with move/delete
. I get an error message that says the following:
move/delete(s) must be integrated along with matching move/add(s)
Okay then, I'll see what the move/add
target was and add that to the command. But here's the twist. p4 integrate
allows targeting multiple files only by using their filespec system. I cannot specify two separate files, unless they share some part of the path, which doesn't always have to be the case in the situation above.
But not all hope is lost yet, since, instead of specifying fromFiles
and toFiles
in the p4 integrate
explicitly, you can use branch mapping
instead. There you can specify any number of mappings, so I can explicitly add both the move/delete
pair and move/add
pair. Problem solved, right?
No, because, that also doesn't work because perforce seems to process the branch mapping
line by line, so the as soon as it reaches the first line, it throws the same error. Even if I swap the ordering, putting move/add
pair before move/delete
pair, it still complains.
Is it really impossible to integrate a single rename from one stream to the other if the files are renamed to something completely different? How do I handle this?
In most cases you can actually just integrate the move/add; as long as the original branch mapping is simple enough (and for streams it usually is), the corresponding move/delete will be discovered automatically. Specifying the full branch mapping is only strictly needed if it's something tricky (like if individual paths in the source are mapped to different paths in the target and the file was moved between those paths).
Here's the error trying to merge FileA
on its own:
C:\Perforce\karl>p4 merge --from Source FileA
Move/delete(s) must be integrated along with matching move/add(s).
and here we are just merging FileB
on its own:
C:\Perforce\karl>p4 merge --from Source FileB
//stream/Target/FileA#1 - integrate from //stream/Source/FileB#1 (remapped from //stream/Target/FileB)
... must resolve content from //stream/Source/FileB#1
... must resolve move to //stream/Target/FileB
Note that when we said we wanted to merge FileB
, p4 was able to figure out that it's linked to FileA
, and so we open FileA
for integrate while scheduling a filename resolve. Then we resolve:
C:\Perforce\karl>p4 resolve
c:\Perforce\karl\FileA - merging //stream/Source/FileB#1
Diff chunks: 0 yours + 0 theirs + 0 both + 0 conflicting
Accept(a) Edit(e) Diff(d) Merge (m) Skip(s) Help(?) at: a
//bob-dvcs-1709920089/FileA - copy from //stream/Source/FileB
c:\Perforce\karl\FileA - resolving move to //stream/Target/FileB
Filename resolve:
at: //stream/Target/FileB
ay: //stream/Target/FileA
Accept(a) Skip(s) Help(?) at: a
//stream/Target/FileB - moved from //stream/Target/FileA
(I could also have just done p4 resolve -as
here to do it automatically, but then we wouldn't get the preview of the individual resolve actions.)
Now the file is open for move in the Target
stream:
C:\Perforce\karl>p4 opened
//stream/Target/FileA#1 - move/delete default change (text)
//stream/Target/FileB#1 - move/add default change (text)
C:\Perforce\karl>p4 resolved
c:\Perforce\karl\FileB - copy from //stream/Source/FileB#1
c:\Perforce\karl\FileB - moved from //stream/Target/FileA#1
c:\Perforce\karl\FileB - resolved move (copy from //stream/Target/FileB)
Another option would have been to merge by changelist (assuming that there wasn't a bunch of other stuff in that changelist that we specifically wanted to avoid):
C:\Perforce\karl>p4 revert //...
//stream/Target/FileA#1 - was move/delete, reverted
//stream/Target/FileB#none - was move/add, deleted
C:\Perforce\karl>p4 filelog //stream/Source/fileB
//stream/Source/FileB
... #1 change 4 move/add on 2024/03/08 by bob@bob-dvcs-1709920089 (text) 'moveB'
... ... moved from //stream/Source/FileA#1
//stream/Source/FileA
... #1 change 2 add on 2024/03/08 by bob@bob-dvcs-1709920089 (text) 'addA'
... ... moved into //stream/Source/FileB#1
... ... branch into //stream/Target/FileA#1
C:\Perforce\karl>p4 merge --from Source @4,4
//stream/Target/FileA#1 - integrate from //stream/Source/FileB#1 (remapped from //stream/Target/FileB)
... must resolve content from //stream/Source/FileB#1
... must resolve move to //stream/Target/FileB
Changelist 4 will of course include both the move/delete and move/add (because moves are guaranteed to be atomic), so that's another way to make sure that you're selecting both revisions. If there were other edits made to the file, either before or after the move, integrating by changelist ("cherry-picking" the changelist) will specifically avoid merging those edits.