Next: , Previous: Some Principles For Working With Branches, Up: Going Out On A Limb (How To Work With Branches And Survive)


Merging Repeatedly Into The Trunk

Let's assume qsmith needs to do development on a branch for a while, to avoid destabilizing the trunk that he shares with jrandom. The first step is to create the branch. Notice how qsmith creates a regular (non-branch) tag at the branch point first, and then creates the branch:

     paste$ pwd
     /home/qsmith/myproj
     paste$ cvs tag Root-of-Exotic_Greetings
     cvs tag: Tagging .
     T README.txt
     T foo.gif
     T hello.c
     cvs tag: Tagging a-subdir
     T a-subdir/whatever.c
     cvs tag: Tagging a-subdir/subsubdir
     T a-subdir/subsubdir/fish.c
     cvs tag: Tagging b-subdir
     T b-subdir/random.c
     paste$ cvs tag -b Exotic_Greetings-branch
     cvs tag: Tagging .
     T README.txt
     T foo.gif
     T hello.c
     cvs tag: Tagging a-subdir
     T a-subdir/whatever.c
     cvs tag: Tagging a-subdir/subsubdir
     T a-subdir/subsubdir/fish.c
     cvs tag: Tagging b-subdir
     T b-subdir/random.c
     paste$

The point of tagging the trunk first is that it may be necessary someday to retrieve the trunk as it was the moment the branch was created. If you ever need to do that, you'll have to have a way of referring to the trunk snapshot without referring to the branch itself. Obviously, you can't use the branch tag because that would retrieve the branch, not the revisions in the trunk that form the root of the branch. The only way to do it is to make a regular tag at the same revisions the branch sprouts from. (Some people stick to this rule so faithfully that I considered listing it as "Branching Principle Number 4: Always create a non-branch tag at the branch point." However, many sites don't do it, and they generally seem to do okay, so it's really a matter of taste.) From here on, I will refer to this non-branch tag as the branch point tag.

Notice also that a naming convention is being adhered to: The branch point tag begins with Root-of-, then the actual branch name, which uses underscores instead of hyphens to separate words. When the actual branch is created, its tag ends with the suffix -branch so that you can identify it as a branch tag just by looking at the tag name. (The branch point tag Root-of-Exotic_Greetings does not include the -branch because it is not a branch tag.) You don't have to use this particular naming convention, of course, but you should use some convention.

Of course, I'm being extra pedantic here. In smallish projects, where everyone knows who's doing what and confusion is easy to recover from, these conventions don't have to be used. Whether you use a branch point tag or have a strict naming convention for your tags depends on the complexity of the project and the branching scheme. (Also, don't forget that you can always go back later and update old tags to use new conventions by retrieving an old tagged version, adding the new tag, and then deleting the old tag.)

Now, qsmith is ready to start working on the branch:

     paste$ cvs update -r Exotic_Greetings-branch
     cvs update: Updating .
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     paste$

He makes some changes to a couple of files and commits them on the branch:

     paste$ emacs README.txt a-subdir/whatever.c b-subdir/random.c
     ...
     paste$ cvs ci -m "print greeting backwards, etc"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in README.txt;
     /usr/local/newrepos/myproj/README.txt,v  <--  README.txt
     new revision: 1.14.2.1; previous revision: 1.14
     done
     Checking in a-subdir/whatever.c;
     /usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
     new revision: 1.3.2.1; previous revision: 1.3
     done
     Checking in b-subdir/random.c;
     /usr/local/newrepos/myproj/b-subdir/random.c,v  <--  random.c
     new revision: 1.1.1.1.2.1; previous revision: 1.1.1.1
     done
     paste$

Meanwhile, jrandom is continuing to work on the trunk. She modifies two of the three files that qsmith touched. Just for kicks, we'll have her make changes that conflict with qsmith's work:

     floss$ emacs README.txt whatever.c
      ...
     floss$ cvs ci -m "some very stable changes indeed"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in README.txt;
     /usr/local/newrepos/myproj/README.txt,v  <--  README.txt
     new revision: 1.15; previous revision: 1.14
     done
     Checking in a-subdir/whatever.c;
     /usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
     new revision: 1.4; previous revision: 1.3
     done
     floss$

The conflict is not apparent yet, of course, because neither developer has tried to merge branch and trunk. Now, jrandom does the merge:

     floss$ cvs update -j Exotic_Greetings-branch
     cvs update: Updating .
     RCS file: /usr/local/newrepos/myproj/README.txt,v
     retrieving revision 1.14
     retrieving revision 1.14.2.1
     Merging differences between 1.14 and 1.14.2.1 into README.txt
     rcsmerge: warning: conflicts during merge
     cvs update: Updating a-subdir
     RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
     retrieving revision 1.3
     retrieving revision 1.3.2.1
     Merging differences between 1.3 and 1.3.2.1 into whatever.c
     rcsmerge: warning: conflicts during merge
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     RCS file: /usr/local/newrepos/myproj/b-subdir/random.c,v
     retrieving revision 1.1.1.1
     retrieving revision 1.1.1.1.2.1
     Merging differences between 1.1.1.1 and 1.1.1.1.2.1 into random.c
     floss$ cvs update
     cvs update: Updating .
     C README.txt
     cvs update: Updating a-subdir
     C a-subdir/whatever.c
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     M b-subdir/random.c
     floss$

Two of the files conflict. No big deal; with her usual savoir-faire, jrandom resolves the conflicts, commits, and tags the trunk as successfully merged:

     floss$ emacs README.txt a-subdir/whatever.c
      ...
     floss$ cvs ci -m "merged from Exotic_Greetings-branch (conflicts resolved)"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in README.txt;
     /usr/local/newrepos/myproj/README.txt,v  <--  README.txt
     new revision: 1.16; previous revision: 1.15
     done
     Checking in a-subdir/whatever.c;
     /usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
     new revision: 1.5; previous revision: 1.4
     done
     Checking in b-subdir/random.c;
     /usr/local/newrepos/myproj/b-subdir/random.c,v  <--  random.c
     new revision: 1.2; previous revision: 1.1
     done
     floss$ cvs tag merged-Exotic_Greetings
     cvs tag: Tagging .
     T README.txt
     T foo.gif
     T hello.c
     cvs tag: Tagging a-subdir
     T a-subdir/whatever.c
     cvs tag: Tagging a-subdir/subsubdir
     T a-subdir/subsubdir/fish.c
     cvs tag: Tagging b-subdir
     T b-subdir/random.c
     floss$

Meanwhile, qsmith needn't wait for the merge to finish before continuing development, as long as he makes a tag for the batch of changes from which jrandom merged (later, jrandom will need to know this tag name; in general, branches depend on frequent and thorough developer communications):

     paste$ cvs tag Exotic_Greetings-1
     cvs tag: Tagging .
     T README.txt
     T foo.gif
     T hello.c
     cvs tag: Tagging a-subdir
     T a-subdir/whatever.c
     cvs tag: Tagging a-subdir/subsubdir
     T a-subdir/subsubdir/fish.c
     cvs tag: Tagging b-subdir
     T b-subdir/random.c
     paste$ emacs a-subdir/whatever.c
      ...
     paste$ cvs ci -m "print a randomly capitalized greeting"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in a-subdir/whatever.c;
     /usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
     new revision: 1.3.2.2; previous revision: 1.3.2.1
     done
     paste$

And of course, qsmith should tag those changes once he's done:

     paste$ cvs -q tag Exotic_Greetings-2
     T README.txt
     T foo.gif
     T hello.c
     T a-subdir/whatever.c
     T a-subdir/subsubdir/fish.c
     T b-subdir/random.c
     paste$

While all this is going on, jrandom makes a change in a different file, one that qsmith hasn't touched in his new batch of edits:

     floss$ emacs README.txt
      ...
     floss$ cvs ci -m "Mention new Exotic Greeting features" README.txt
     Checking in README.txt;
     /usr/local/newrepos/myproj/README.txt,v  <--  README.txt
     new revision: 1.17; previous revision: 1.16
     done
     floss$

At this point, qsmith has committed a new change on the branch, and jrandom has committed a nonconflicting change in a different file on the trunk. Watch what happens when jrandom tries to merge from the branch again:

     floss$ cvs -q update -j Exotic_Greetings-branch
     RCS file: /usr/local/newrepos/myproj/README.txt,v
     retrieving revision 1.14
     retrieving revision 1.14.2.1
     Merging differences between 1.14 and 1.14.2.1 into README.txt
     rcsmerge: warning: conflicts during merge
     RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
     retrieving revision 1.3
     retrieving revision 1.3.2.2
     Merging differences between 1.3 and 1.3.2.2 into whatever.c
     rcsmerge: warning: conflicts during merge
     RCS file: /usr/local/newrepos/myproj/b-subdir/random.c,v
     retrieving revision 1.1
     retrieving revision 1.1.1.1.2.1
     Merging differences between 1.1 and 1.1.1.1.2.1 into random.c
     floss$ cvs -q update
     C README.txt
     C a-subdir/whatever.c
     floss$

There are conflicts! Is that what you expected?

The problem lies in the semantics of merging. Back in An Overview of CVS, I explained that when you run

     floss$ cvs update -j BRANCH

in a working copy, CVS merges into the working copy the differences between BRANCH's root and its tip. The trouble with that behavior, in this situation, is that most of those changes had already been incorporated into the trunk the first time that jrandom did a merge. When CVS tried to merge them in again (over themselves, as it were), it naturally registered a conflict.

What jrandom really wanted to do was merge into her working copy the changes between the branch's most recent merge and its current tip. You can do this by using two -j flags to update, as you may recall from An Overview of CVS, as long as you know what revision to specify with each flag. Fortunately, qsmith made a tag at exactly the last merge point (hurrah for planning ahead!), so this will be no problem. First, let's have jrandom restore her working copy to a clean state, from which she can redo the merge:

     floss$ rm README.txt a-subdir/whatever.c
     floss$ cvs -q update
     cvs update: warning: README.txt was lost
     U README.txt
     cvs update: warning: a-subdir/whatever.c was lost
     U a-subdir/whatever.c
     floss$

Now she's ready to do the merge, this time using qsmith's conveniently placed tag:

     floss$ cvs -q update -j Exotic_Greetings-1 -j Exotic_Greetings-branch
     RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
     retrieving revision 1.3.2.1
     retrieving revision 1.3.2.2
     Merging differences between 1.3.2.1 and 1.3.2.2 into whatever.c
     floss$ cvs -q update
     M a-subdir/whatever.c
     floss$

Much better. The change from qsmith has been incorporated into whatever.c; jrandom can now commit and tag:

     floss$ cvs -q ci -m "merged again from Exotic_Greetings (1)"
     Checking in a-subdir/whatever.c;
     /usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
     new revision: 1.6; previous revision: 1.5
     done
     floss$ cvs -q tag merged-Exotic_Greetings-1
     T README.txt
     T foo.gif
     T hello.c
     T a-subdir/whatever.c
     T a-subdir/subsubdir/fish.c
     T b-subdir/random.c
     floss$

Even if qsmith had forgotten to tag at the merge point, all hope would not be lost. If jrandom knew approximately when qsmith's first batch of changes had been committed, she could try filtering by date:

     floss$ cvs update -j Exotic_Greetings-branch:3pm -j Exotic_Greetings_branch

Although useful as a last resort, filtering by date is less than ideal because it selects the changes based on people's recollections rather than dependable developer designations. If qsmith's first mergeable set of changes had happened over several commits instead of in one commit, jrandom may mistakenly choose a date or time that would catch some of the changes, but not all of them.

There's no reason why each taggable point in qsmith's changes needs to be sent to the repository in a single commit – it just happens to have worked out that way in these examples. In real life, qsmith may make several commits between tags. He can work on the branch in isolation, as he pleases. The point of the tags is to record successive points on the branch where he considers the changes to be mergeable into the trunk. As long as jrandom always merges using two -j flags and is careful to use qsmith's merge tags in the right order and only once each, the trunk should never experience the double-merge problem. Conflicts may occur, but they will be the unavoidable kind that requires human resolution – situations in which both branch and trunk made changes to the same area of code.

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

copyright  ©  March 25 2019 sean dreilinger url: https://durak.org/sean/pubs/software/cvsbook/Merging-Repeatedly-Into-The-Trunk.html