Next: , Previous: Merging Changes From Branch To Trunk, Up: Branches


Multiple Merges

Sometimes a branch will continue to be actively developed even after the trunk has undergone a merge from it. For example, this can happen if a second bug in the previous public release is discovered and has to be fixed on the branch. Maybe someone didn't get the joke in random.c, so on the branch, you have to add a line explaining it

     floss$ pwd
     /home/whatever/myproj_branch
     floss$ cat b-subdir/random.c
     /* Print out a random number. */
     #include <stdio.h>
     void main ()
     {
       printf ("A random number.\n");
       printf ("Get the joke?\n");
     }
     floss$

and commit. If that bug fix also needs to be merged into the trunk, you might be tempted to try the same update command as before in the trunk working copy to "re-merge":

     floss$ cvs -q update -d -j Release-1999_05_01-bugfixes
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.5
     retrieving revision 1.5.2.1
     Merging differences between 1.5 and 1.5.2.1 into hello.c
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     retrieving revision 1.2
     retrieving revision 1.2.2.2
     Merging differences between 1.2 and 1.2.2.2 into random.c
     rcsmerge: warning: conflicts during merge
     floss$

As you can see, that didn't have quite the desired effect-we got a conflict, even though the trunk copy hadn't been modified there and, therefore, no conflict was expected.

The trouble was that the update command behaved exactly as described: It tried to take all the changes between the branch's root and tip and merge them into the current working copy. The only problem is, some of those changes had already been merged into this working copy. That's why we got the conflict:

     floss$ pwd
     /home/whatever/myproj
     floss$ cat b-subdir/random.c
     /* Print out a random number. */
     #include <stdio.h
     void main ()
     {
     <<<<<<< random.c
       printf ("A random number.\n");
     =======
       printf ("A random number.\n");
       printf ("Get the joke?\n");
     >>>>>>> 1.2.2.2
     }
     floss$

You could go through resolving all such conflicts by hand-it's usually not hard to tell what you need to do in each file. Nevertheless, it is even better to avoid a conflict in the first place. By passing two -j flags instead of one, you'll get only those changes from where you last merged to the tip instead of all of the changes on the branch, from root to tip. The first -j gives the starting point on the branch, and the second is just the plain branch name (which implies the tip of the branch).

The question then is, how can you specify the point on the branch from which you last merged? One way is to qualify by using a date along with the branch tag name. CVS provides a special syntax for this:

     floss$ cvs -q update -d -j "Release-1999_05_01-bugfixes:2 days ago" \
                          -j Release-1999_05_01-bugfixes
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     retrieving revision 1.2.2.1
     retrieving revision 1.2.2.2
     Merging differences between 1.2.2.1 and 1.2.2.2 into random.c
     floss$

If the branch tag name is followed by a colon and then a date (in any of the usual CVS date syntaxes), CVS will include only changes later than that date. So if you knew that the original bug fix was committed on the branch three days ago, the preceding command would merge the second bug fix only.

A better way, if you plan ahead, is to tag the branch after each bug fix (just a regular tag – we're not starting a new branch here or anything like that). Suppose after fixing the bug in the branch and committing, you do this in the branch's working copy:

     floss$ cvs -q tag Release-1999_05_01-bugfixes-fix-number-1
     T README.txt
     T hello.c
     T a-subdir/whatever.c
     T a-subdir/subsubdir/fish.c
     T b-subdir/random.c
     floss$

Then, when it's time to merge the second change into the trunk, you can use that conveniently placed tag to delimit the earlier revision:

     floss$ cvs -q update -d -j Release-1999_05_01-bugfixes-fix-number-1 \
                          -j Release-1999_05_01-bugfixes
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     retrieving revision 1.2.2.1
     retrieving revision 1.2.2.2
     Merging differences between 1.2.2.1 and 1.2.2.2 into random.c
     floss$

This way, of course, is much better than trying to recall how long ago you made one change versus another, but it only works if you remember to tag the branch every time it is merged to the trunk. The lesson, therefore, is to tag early and tag often! It's better to err on the side of too many tags (as long as they all have descriptive names) than to have too few. In these last examples, for instance, there was no requirement that the new tag on the branch have a name similar to the branch tag itself. Although I named it Release-1999_05_01-bugfixes-fix-number-1, it could just as easily have been fix1. However, the former is preferable, because it contains the name of the branch and thus won't ever be confused with a tag on some other branch. (Remember that tag names are unique within files, not within branches. You can't have two tags named fix1 in the same file, even if they refer to revisions on different branches.)

Karl Fogel wrote this book. Buy a printed copy via his homepage at red-bean.com

copyright  ©  July 22 2019 sean dreilinger url: https://durak.org/sean/pubs/software/cvsbook/Multiple-Merges.html