GNU Info

Info Node: (cvsbook.info)Branching Basics

(cvsbook.info)Branching Basics


Next: Merging Changes From Branch To Trunk Up: Branches
Enter node , (file) or (file)node

Branching Basics
----------------

Why are branches useful?

Let's return for a moment to the scenario of the developer who, in the
midst of working on a new version of the program, receives a bug report
about an older released version.  Assuming the developer fixes the
problem, she still needs a way to deliver the fix to the customer.  It
won't help to just find an old copy of the program somewhere, patch it
up without CVS's knowledge, and ship it off.  There would be no record
of what was done; CVS would be unaware of the fix; and later if
something was discovered to be wrong with the patch, no one would have a
starting point for reproducing the problem.

It's even more ill-advised to fix the bug in the current, unstable
version of the sources and ship that to the customer.  Sure, the
reported bug may be solved, but the rest of the code is in a
half-implemented, untested state.  It may run, but it's certainly not
ready for prime time.

Because the last released version is thought to be stable, aside from
this one bug, the ideal solution is to go back and correct the bug in
the old release - that is, to create an alternate universe in which the
last public release includes this bug fix.

That's where branches come in.  The developer splits off a branch,
rooted in the main line of development (the trunk) not at its most
recent revisions, but back at the point of the last release.  Then she
checks out a working copy of this branch, makes whatever changes are
necessary to fix the bug, and commits them on that branch, so there's a
record of the bug fix.  Now she can package up an interim release based
on the branch and ship it to the customer.

Her change won't have affected the code on the trunk, nor would she want
it to without first finding out whether the trunk needs the same bug fix
or not.  If it does, she can merge the branch changes into the trunk.
In a merge, CVS calculates the changes made on the branch between the
point where it diverged from the trunk and the branch's tip (its most
recent state), then applies those differences to the project at the tip
of the trunk.  The difference between the branch's root and its tip
works out, of course, to be precisely the bug fix.

Another good way to think of a merge is as a special case of updating.
The difference is that in a merge, the changes to be incorporated are
derived by comparing the branch's root and tip, instead of by comparing
the working copy against the repository.

The act of updating is itself similar to receiving patches directly from
their authors and applying them by hand.  In fact, to do an update, CVS
calculates the difference (that's "difference" as in the diff program)
between the working copy and the repository and then applies that diff
to the working copy just as the patch program would.  This mirrors the
way in which a developer takes changes from the outside world, by
manually applying patch files sent in by contributors.

Thus, merging the bug fix branch into the trunk is just like accepting
some outside contributor's patch to fix the bug.  The contributor would
have made the patch against the last released version, just as the
branch's changes are against that version.  If that area of code in the
current sources hasn't changed much since the last release, the merge
will succeed with no problems.  If the code is now substantially
different, however, the merge will fail with conflict (that is, the
patch will be rejected), and some manual fiddling will be necessary.
Usually this is accomplished by reading the conflicting area, making the
necessary changes by hand, and committing.  Figure 2.3 shows a picture
of what happens in a branch and merge.


                  (branch on which bug was fixed)
                .---------------->---------------.
               /                                 |
              /                                  |
             /                                   |
            /                                    |
           /                                     V (<------ point of merge)
      ====*===================================================================>
                     (main line of development)
     
     
     [Figure 2.3: A branch and then a merge.  Time flows left to right.]

We'll now walk through the steps necessary to make this picture happen.
Remember that it's not really time that's flowing from left to right in
the diagram, but rather the revision history.  The branch will not have
been made at the time of the release, but is created later, rooted back
at the release's revisions.

In our case, let's assume the files in the project have gone through
many revisions since they were tagged as `Release-1999_05_01', and
perhaps files have been added as well.  When the bug report regarding
the old release comes in, the first thing we'll want to do is create a
branch rooted at the old release, which we conveniently tagged
`Release-1999_05_01'.

One way to do this is to first check out a working copy based on that
tag, then create the branch by re-tagging with the -b (branch) option:

     floss$ cd ..
     floss$ ls
     myproj/
     floss$ cvs -q checkout -d myproj_old_release -r Release-1999_05_01 myproj
     U myproj_old_release/README.txt
     U myproj_old_release/hello.c
     U myproj_old_release/a-subdir/whatever.c
     U myproj_old_release/a-subdir/subsubdir/fish.c
     U myproj_old_release/b-subdir/random.c
     floss$ ls
     myproj/      myproj_old_release/
     floss$ cd myproj_old_release
     floss$ ls
     CVS/      README.txt  a-subdir/   b-subdir/   hello.c
     floss$ cvs -q tag -b Release-1999_05_01-bugfixes
     T README.txt
     T hello.c
     T a-subdir/whatever.c
     T a-subdir/subsubdir/fish.c
     T b-subdir/random.c
     floss$

Take a good look at that last command.  It may seem somewhat arbitrary
that tag is used to create branches, but there's actually a reason for
it: The tag name will serve as a label by which the branch can be
retrieved later.  Branch tags do not look any different from non-branch
tags, and are subject to the same naming restrictions.  Some people like
to always include the word branch in the tag name itself (for example,
`Release-1999_05_01-bugfix-branch'), so they can distinguish branch
tags from other kinds of tags.  You may want to do this if you find
yourself often retrieving the wrong tag.

(And while we're at it, note the -d myproj_old_release option to
checkout in the first CVS command.  This tells checkout to put the
working copy in a directory called myproj_old_release, so we won't
confuse it with the current version in myproj.  Be careful not to
confuse this use of -d with the global option of the same name, or with
the -d option to update.)

Of course, merely running the tag command does not switch this working
copy over to the branch.  Tagging never affects the working copy; it
just records some extra information in the repository to allow you to
retrieve that working copy's revisions later on (as a static piece of
history or as a branch, as the case may be).

Retrieval can be done one of two ways (you're probably getting used to
this motif by now!).  You can check out a new working copy on the branch

     floss$ pwd
     /home/whatever
     floss$ cvs co -d myproj_branch -r Release-1999_05_01-bugfixes myproj

or switch an existing working copy over to it:

     floss$ pwd
     /home/whatever/myproj
     floss$ cvs update -r Release-1999_05_01-bugfixes

The end result is the same (well, the name of the new working copy's
top-level directory may be different, but that's not important for CVS's
purposes).  If your current working copy has uncommitted changes, you'll
probably want to use checkout instead of update to access the branch.
Otherwise, CVS attempts to merge your changes into the working copy as
it switches it over to the branch.  In that case, you might get
conflicts, and even if you didn't, you'd still have an impure branch.
It won't truly reflect the state of the program as of the designated
tag, because some files in the working copy will contain modifications
made by you.

Anyway, let's assume that by one method or another you get a working
copy on the desired branch:

     floss$ cvs -q status hello.c
     ===================================================================
     File: hello.c                 Status: Up-to-date
        Working revision:  1.5     Tue Apr 20 06:12:56 1999
        Repository revision:       1.5     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:                Release-1999_05_01-bugfixes
     (branch: 1.5.2)
        Sticky Date:               (none)
        Sticky Options:            (none)
     floss$ cvs -q status b-subdir/random.c
     ===================================================================
     File: random.c                Status: Up-to-date
        Working revision:  1.2     Mon Apr 19 06:35:27 1999
        Repository revision:       1.2 /usr/local/cvs/myproj/b-subdir/random.c,v
        Sticky Tag:                Release-1999_05_01-bugfixes (branch: 1.2.2)
        Sticky Date:               (none)
        Sticky Options:            (none)
     floss$

(The contents of those `Sticky Tag' lines will be explained shortly.)
If you modify hello.c and random.c, and commit

     floss$ cvs -q update
     M hello.c
     M b-subdir/random.c
     floss$ cvs ci -m "fixed old punctuation bugs"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in hello.c;
     /usr/local/cvs/myproj/hello.c,v  <-  hello.c
     new revision: 1.5.2.1; previous revision: 1.5
     done
     Checking in b-subdir/random.c;
     /usr/local/cvs/myproj/b-subdir/random.c,v  <-  random.c
     new revision: 1.2.2.1; previous revision: 1.2
     done
     floss$

you'll notice that there's something funny going on with the revision
numbers:

     floss$ cvs -q status hello.c b-subdir/random.c
     ===================================================================
     File: hello.c                 Status: Up-to-date
        Working revision:  1.5.2.1 Wed May  5 00:13:58 1999
        Repository revision:       1.5.2.1 /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:                Release-1999_05_01-bugfixes (branch: 1.5.2)
        Sticky Date:               (none)
        Sticky Options:            (none)
     ===================================================================
     File: random.c                Status: Up-to-date
        Working revision:  1.2.2.1 Wed May  5 00:14:25 1999
        Repository revision:       1.2.2.1 /usr/local/cvs/myproj/b-subdir/random.c,v
        Sticky Tag:                Release-1999_05_01-bugfixes (branch: 1.2.2)
        Sticky Date:               (none)
        Sticky Options:            (none)
     floss$

They now have four digits instead of two!

A closer look reveals that each file's revision number is just the
branch number (as shown on the `Sticky Tag' line) plus an extra digit
on the end.

What you're seeing is a little bit of CVS's inner workings.  Although
you almost always use a branch to mark a project-wide divergence, CVS
actually records the branch on a per-file basis.  This project had five
files in it at the point of the branch, so five individual branches were
made, all with the same tag name: `Release-1999_05_01-bugfixes'.

Most people consider this per-file scheme a rather inelegant
implementation on CVS's part.  It's a bit of the old RCS legacy showing
through-RCS didn't know how to group files into projects, and even
though CVS does, it still uses code inherited from RCS to handle
branches.

Ordinarily, you don't need to be too concerned with how CVS is keeping
track of things internally, but in this case, it helps to understand the
relationship between branch numbers and revision numbers.  Let's look at
the hello.c file; everything I'm about to say about hello.c applies to
the other files in the branch (with revision/branch numbers adjusted
accordingly).

The hello.c file was on revision 1.5 at the point where the branch was
rooted.  When we created the branch, a new number was tacked onto the
end to make a branch number (CVS chooses the first unused even, nonzero
integer).  Thus, the branch number in this case became `1.5.2'.  The
branch number by itself is not a revision number, but it is the root
(that is, the prefix) of all the revision numbers for hello.c along this
branch.

However, when we ran that first CVS status in a branched working copy,
hello.c's revision number showed up as only `1.5', not `1.5.2.0' or
something similar.  This is because the initial revision on a branch is
always the same as the trunk revision of the file, where the branch
sprouts off.  Therefore, CVS shows the trunk revision number in status
output, for as long as the file is the same on both branch and trunk.

Once we had committed a new revision, hello.c was no longer the same on
both trunk and branch - the branch incarnation of the file had changed,
while the trunk remained the same.  Accordingly, hello.c was assigned
its first branch revision number.  We saw this in the status output
after the commit, where its revision number is clearly `1.5.2.1'.

The same story applies to the random.c file. Its revision number at the
time of branching was `1.2', so its first branch is `1.2.2', and the
first new commit of random.c on that branch received the revision
number `1.2.2.1'.

There is no numeric relationship between `1.5.2.1' and `1.2.2.1' - no
reason to think that they are part of the same branch event, except
that both files are tagged with `Release-1999_05_01-bugfixes', and the
tag is attached to branch numbers `1.5.2' and `1.2.2' in the respective
files.  Therefore, the tag name is your only handle on the branch as a
project-wide entity.  Although it is perfectly possible to move a file
to a branch by using the revision number directly

     floss$ cvs update -r 1.5.2.1 hello.c
     U hello.c
     floss$

it is almost always ill-advised.  You would be mixing the branch
revision of one file with non-branch revisions of the others.  Who knows
what losses may result?  It is better to use the branch tag to refer to
the branch and do all files at once by not specifying any particular
file.  That way you don't have to know or care what the actual branch
revision number is for any particular file.

It is also possible to have branches that sprout off other branches, to
any level of absurdity.  For example, if a file has a revision number of
`1.5.4.37.2.3', its revision history can be diagrammed like this:

                       1.1
                        |
                       1.2
                        |
                       1.3
                        |
                       1.4
                        |
                       1.5
                      /   \
                     /     \
                    /       \
                (1.5.2)   (1.5.4)         <--- (these are branch numbers)
                  /           \
              1.5.2.1        1.5.4.1
                 |              |
              1.5.2.2        1.5.4.2
                 |              |
               (etc)          (...)       <--- (collapsed 34 revisions for brevity)
                                |
                             1.5.4.37
                               /
                              /
                        (1.5.4.37.2)      <--- (this is also a branch number)
                            /
                           /
                    1.5.4.37.2.1
                          |
                    1.5.4.37.2.2
                          |
                    1.5.4.37.2.3
     
     [Figure 2.4: An unusually high degree of branching.  Time flows downward.]

Admittedly, only the rarest circumstances make such a branching depth
necessary, but isn't it nice to know that CVS will go as far as you're
willing to take it?  Nested branches are created the same way as any
other branch: Check out a working copy on branch `N', run cvs tag -b
branchname in it, and you'll create branch `N.M' in the repository
(where `N' represents the appropriate branch revision number in each
file, such as `1.5.2.1', and `M' represents the next available branch
at the end of that number, such as `2').


automatically generated by info2www version 1.2.2.9