Search code examples
perforceperforce-integrateperforce-streamperforce-branch-spec

Integrating a single file that was renamed in a source stream to the target stream


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?


Solution

  • 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.