This is ossdev.info, produced by makeinfo version 4.3 from
../book/ossdev/main.texi.

START-INFO-DIR-ENTRY
* CVS Book: (cvsbook).          A CVS tutorial and reference.
END-INFO-DIR-ENTRY

Copyright (C) 1999, 2000 Karl Fogel <kfogel@red-bean.com>

This document is free software; you can redistribute and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

This document is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

This manual describes how to use and administer CVS (Concurrent Versions
System).  It is part of a larger work entitled `Open Source Development
With CVS'; please see the introduction for details.

This is version 1.21 of this manual.


File: ossdev.info,  Node: Annotations -- A Detailed View Of Project Activity,  Next: Annotations And Branches,  Prev: History -- A Summary Of Repository Activity,  Up: Advanced CVS

Annotations - A Detailed View Of Project Activity
=================================================

The annotate Command
====================

If the history command gives an overview of project activity, the
"annotate" command is a way of attaching a zoom lens to the view.  With
`annotate', you can see who was the last person to touch each line of a
file, and at what revision they touched it:

     floss$ cvs annotate
     Annotations for README.txt
     ***************
     1.14         (jrandom  25-Jul-99): blah
     1.13         (jrandom  25-Jul-99): test 3 for history
     1.12         (qsmith   19-Jul-99): test 2
     1.11         (qsmith   19-Jul-99): test
     1.10         (jrandom  12-Jul-99): blah
     1.1          (jrandom  20-Jun-99): Just a test project.
     1.4          (jrandom  21-Jun-99): yeah.
     1.5          (jrandom  21-Jun-99): nope.
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.15         (jrandom  25-Jul-99):   /* another test for history */
     1.13         (qsmith   19-Jul-99):   /* random change number two */
     1.10         (jrandom  12-Jul-99):   /* test */
     1.21         (jrandom  25-Jul-99):   printf ("Hellooo, world!\n");
     1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.11         (qsmith   18-Jul-99):   /* added this comment */
     1.16         (qsmith   25-Jul-99):   /* will merge these changes */
     1.18         (jrandom  25-Jul-99):   /* will merge these changes too */
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     Annotations for a-subdir/whatever.c
     ***************
     1.3          (jrandom  25-Jul-99): /* A completely non-empty C file. */
     Annotations for a-subdir/subsubdir/fish.c
     ***************
     1.2          (jrandom  25-Jul-99): /* An almost completely empty C file. */
     Annotations for b-subdir/random.c
     ***************
     1.1          (jrandom  20-Jun-99): /* A completely empty C file. */
     floss$

The output of annotate is pretty intuitive.  On the left are the
revision number, developer, and date on which the line in question was
added or last modified.  On the right is the line itself, as of the
current revision.  Because every line is annotated, you can actually see
the entire contents of the file, pushed over to the right by the
annotation information.

If you specify a revision number or tag, the annotations are given as of
that revision, meaning that it shows the most recent modification to
each line at or before that revision.  This is probably the most common
way to use annotations - examining a particular revision of a single
file to determine which developers were active in which parts of the
file.

For example, in the output of the previous example, you can see that the
most recent revision of hello.c is 1.21, in which jrandom did something
to the line:

     printf ("Hellooo, world!\n");

One way to find out what she did is to diff that revision against the
previous one:

     floss$ cvs diff -r 1.20 -r 1.21 hello.c
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/newrepos/myproj/hello.c,v
     retrieving revision 1.20
     retrieving revision 1.21
     diff -r1.20 -r1.21
     9c9
     <   printf ("Hello, world!\n");
     --
     >   printf ("Hellooo, world!\n");
     floss$

Another way to find out, while still retaining a file-wide view of
everyone's activity, is to compare the current annotations with the
annotations from a previous revision:

     floss$ cvs annotate -r 1.20 hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.15         (jrandom  25-Jul-99):   /* another test for history */
     1.13         (qsmith   19-Jul-99):   /* random change number two */
     1.10         (jrandom  12-Jul-99):   /* test */
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.11         (qsmith   18-Jul-99):   /* added this comment */
     1.16         (qsmith   25-Jul-99):   /* will merge these changes */
     1.18         (jrandom  25-Jul-99):   /* will merge these changes too */
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$

Although the diff reveals the textual facts of the change more
concisely, the annotation may be preferable because it places them in
their historical context by showing how long the previous incarnation of
the line had been present (in this case, all the way since revision
1.1).  That knowledge can help you decide whether to look at the logs to
find out the motivation for the change:

     floss$ cvs log -r 1.21 hello.c
     RCS file: /usr/local/newrepos/myproj/hello.c,v
     Working file: hello.c
     head: 1.21
     branch:
     locks: strict
     access list:
     symbolic names:
            random-tag: 1.20
            start: 1.1.1.1
            jrandom: 1.1.1
     keyword substitution: kv
     total revisions: 22;    selected revisions: 1
     description:
     ----------------------------
     revision 1.21
     date: 1999/07/25 20:17:42;  author: jrandom;  state: Exp;  lines: +1 -1
     say hello with renewed enthusiasm
     ============================================================================
     floss$

In addition to -r, you can also filter annotations using the -D DATE
option:

     floss$ cvs annotate -D "5 weeks ago" hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$ cvs annotate -D "3 weeks ago" hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$


File: ossdev.info,  Node: Annotations And Branches,  Next: Using Keyword Expansion,  Prev: Annotations -- A Detailed View Of Project Activity,  Up: Advanced CVS

Annotations And Branches
========================

By default, annotation always shows activity on the main trunk of
development.  Even when invoked from a branch working copy, it shows
annotations for the trunk unless you specify otherwise.  (This tendency
to favor the trunk is either a bug or a feature, depending on your point
of view.)  You can force CVS to annotate a branch by passing the branch
tag as an argument to -r.  Here is an example from a working copy in
which hello.c is on a branch named `Brancho_Gratuito', with at least
one change committed on that branch:

     floss$ cvs status hello.c
     ===================================================================
     File: hello.c           Status: Up-to-date
     
       Working revision:    1.10.2.2        Sun Jul 25 21:29:05 1999
       Repository revision: 1.10.2.2        /usr/local/newrepos/myproj/hello.c,v
       Sticky Tag:          Brancho_Gratuito (branch: 1.10.2)
       Sticky Date:         (none)
       Sticky Options:      (none)
     
     floss$ cvs annotate hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.10         (jrandom  12-Jul-99):   /* test */
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$ cvs annotate -r Brancho_Gratuito hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.10         (jrandom  12-Jul-99):   /* test */
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.10.2.2     (jrandom  25-Jul-99):   printf ("hmmmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.10.2.1     (jrandom  25-Jul-99):   printf ("added this line");
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$

You can also pass the branch number itself:

     floss$ cvs annotate -r 1.10.2 hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.10         (jrandom  12-Jul-99):   /* test */
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.10.2.2     (jrandom  25-Jul-99):   printf ("hmmmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.10.2.1     (jrandom  25-Jul-99):   printf ("added this line");
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$

or a full revision number from the branch:

     floss$ cvs annotate -r 1.10.2.1 hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.10         (jrandom  12-Jul-99):   /* test */
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.10.2.1     (jrandom  25-Jul-99):   printf ("added this line");
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$

If you do this, remember that the numbers are only valid for that
particular file.  In general, it's probably better to use the branch
name wherever possible.


File: ossdev.info,  Node: Using Keyword Expansion,  Next: Going Out On A Limb (How To Work With Branches And Survive),  Prev: Annotations And Branches,  Up: Advanced CVS

Using Keyword Expansion
=======================

You may recall a brief mention of `keyword expansion' in *Note An
Overview of CVS::.  RCS keywords are special words, surrounded by dollar
signs, that CVS looks for in text files and expands into
revision-control information.  For example, if a file contains

     $Author: root $

then when updating the file to a given revision, CVS will expand it to
the username of the person who committed that revision:

     $Author: root $

CVS is also sensitive to keywords in their expanded form, so that once
expanded, they continue to be updated as appropriate.

Although keywords don't actually offer any information that's not
available by other means, they give people a convenient way to see
revision control facts embedded in the text of the file itself, rather
than by invoking some arcane CVS operation.

Here are a few other commonly used keywords:

     $Date: 2003/02/21 15:41:12 $       ==>  date of last commit, expands to ==>
     $Date: 2003/02/21 15:41:12 $
     
     $Id: chapter-6.texi,v 1.1.2.1 2003/02/21 15:41:12 root Exp $         ==>  filename, revision, date, and author; expands to ==>
     $Id: chapter-6.texi,v 1.1.2.1 2003/02/21 15:41:12 root Exp $
     
     $Revision: 1.1.2.1 $   ==>  exactly what you think it is, expands to ==>
     $Revision: 1.1.2.1 $
     
     $Source: d:/data/reposit/gnu/cvs/emx-new/book/ossdev/Attic/chapter-6.texi,v $     ==> path to corresponding repository file, expands to ==>
     $Source: d:/data/reposit/gnu/cvs/emx-new/book/ossdev/Attic/chapter-6.texi,v $
     
     $Log: chapter-6.texi,v $
     Revision 1.1.2.1  2003/02/21 15:41:12  root
     Added the 'Open Source Development with CVS' book to the distribution
             ==>  accumulating log messages for the file, expands to ==>
     $Log: chapter-6.texi,v $
     Revision 1.1.2.1  2003/02/21 15:41:12  root
     Added the 'Open Source Development with CVS' book to the distribution
     
     Revision 1.2  1999/07/26 06:47:52  jrandom
     ...and this is the second log message.
     
     Revision 1.1  1999/07/26 06:39:46  jrandom
     This is the first log message...

The $Log: chapter-6.texi,v $ The Revision 1.1.2.1  2003/02/21 15:41:12
root The Added the 'Open Source Development with CVS' book to the
distribution The keyword is the only one of these that expands to cover
multiple lines, so its behavior is unique.  Unlike the others, it does
not replace the old expansion with the new one, but instead inserts the
latest expansion, plus an additional blank line, right after the keyword
(thereby pushing any previous expansions downward).  Furthermore, any
text between the beginning of the line and $Log is used as a prefix for
the expansions (this is done to ensure that the log messages stay
commented in program code).  For example, if you put this into the file

     // $Log: chapter-6.texi,v $
     // Revision 1.1.2.1  2003/02/21 15:41:12  root
     // Added the 'Open Source Development with CVS' book to the distribution
     //

it will expand to something like this on the first commit:

     // $Log: chapter-6.texi,v $
     // Revision 1.1.2.1  2003/02/21 15:41:12  root
     // Added the 'Open Source Development with CVS' book to the distribution
     //
     // Revision 1.14  1999/07/26 07:03:20  jrandom
     // this is the first log message...
     //

this on the second:

     // $Log: chapter-6.texi,v $
     // Revision 1.1.2.1  2003/02/21 15:41:12  root
     // Added the 'Open Source Development with CVS' book to the distribution
     //
     // Revision 1.15  1999/07/26 07:04:40  jrandom
     // ...and this is the second log message...
     //
     // Revision 1.14  1999/07/26 07:03:20  jrandom
     // this is the first log message...
     //

and so on:

     // $Log: chapter-6.texi,v $
     // Revision 1.1.2.1  2003/02/21 15:41:12  root
     // Added the 'Open Source Development with CVS' book to the distribution
     //
     // Revision 1.16  1999/07/26 07:05:34  jrandom
     // ...and this is the third!
     //
     // Revision 1.15  1999/07/26 07:04:40  jrandom
     // ...and this is the second log message...
     //
     // Revision 1.14  1999/07/26 07:03:20  jrandom
     // this is the first log message...
     //

You may not want to keep your entire log history in the file all the
time; if you do, you can always remove the older sections when it starts
to get too lengthy.  It's certainly more convenient than running cvs
log, and it may be worthwhile in projects where people must constantly
read over the logs.

A more common technique may be to include $Revision: 1.1.2.1 $ in a
file and use it as the version number for the program.  This can work
if the project consists of essentially one file or undergoes frequent
releases and has at least one file that is guaranteed to be modified
between every release.  You can even use an RCS keyword as a value in
program code:

     VERSION = "$Revision: 1.1.2.1 $";

CVS expands that keyword just like any other; it has no concept of the
programming language's semantics and does not assume that the double
quotes protect the string in any way.

A complete list of keywords (there are a few more, rather obscure ones)
is given in *Note CVS Reference::.


File: ossdev.info,  Node: Going Out On A Limb (How To Work With Branches And Survive),  Next: Tracking Third-Party Sources (Vendor Branches),  Prev: Using Keyword Expansion,  Up: Advanced CVS

Going Out On A Limb (How To Work With Branches And Survive)
===========================================================

Branches are simultaneously one of the most important and most easily
misused features of CVS.  Isolating risky or disruptive changes onto a
separate line of development until they stabilize can be immensely
helpful.  If not properly managed, however, branches can quickly propel
a project into confusion and cascading chaos, as people lose track of
what changes have been merged when.

* Menu:

* Some Principles For Working With Branches::
* Merging Repeatedly Into The Trunk::
* The Dovetail Approach -- Merging In And Out Of The Trunk::
* The Flying Fish Approach -- A Simpler Way To Do It::
* Branches And Keyword Expansion -- Natural Enemies::


File: ossdev.info,  Node: Some Principles For Working With Branches,  Next: Merging Repeatedly Into The Trunk,  Up: Going Out On A Limb (How To Work With Branches And Survive)

Some Principles For Working With Branches
-----------------------------------------

To work successfully with branches, your development group should adhere
to these principles:

   * Minimize the number of branches active at any one time.  The more
     branches under development at the same time, the more likely they
     are to conflict when merged into the trunk.  In practical terms,
     the way to accomplish this is to merge as frequently as you can
     (whenever a branch is at a stable point) and to move development
     back onto the trunk as soon as feasible.  By minimizing the amount
     of parallel development going on, everyone is better able to keep
     track of what's going on on each branch, and the possibility of
     conflicts on merge is reduced.

     This does not mean minimizing the absolute number of branches in
     the project, just the number being worked on at any given time.

   * Minimize the complexity - that is, the depth - of your branching
     scheme.  There are circumstances in which it's appropriate to have
     branches from branches, but they are very rare (you may get
     through your entire programming life without ever encountering
     one).  Just because CVS makes it technically possible to have
     arbitrary levels of nested branching, and to merge from any branch
     to any other branch, doesn't mean you actually want to do these
     things.  In most situations, it's best to have all your branches
     rooted at the trunk and to merge from branch to trunk and back out
     again.

   * Use consistently named tags to mark all branch and merge events.
     Ideally, the meaning of each tag and its relationship to other
     branches and tags should be apparent from the tag name.  (The
     point of this will become clearer as we go through the examples.)


With those principles in mind, let's take a look at a typical branch
development scenario.  We'll have jrandom on the trunk and qsmith on the
branch, but note that there could just as well be multiple developers on
the trunk and/or on the branch.  Regular development along either line
can involve any number of people; however, the tagging and merging are
best done by one person on each side, as you'll see.


File: ossdev.info,  Node: Merging Repeatedly Into The Trunk,  Next: The Dovetail Approach -- Merging In And Out Of The Trunk,  Prev: 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 *Note 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
*Note 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.


File: ossdev.info,  Node: The Dovetail Approach -- Merging In And Out Of The Trunk,  Next: The Flying Fish Approach -- A Simpler Way To Do It,  Prev: Merging Repeatedly Into The Trunk,  Up: Going Out On A Limb (How To Work With Branches And Survive)

The Dovetail Approach - Merging In And Out Of The Trunk
-------------------------------------------------------

Merging repeatedly from branch to trunk is good for the people on the
trunk, because they see all of their own changes and all the changes
from the branch.  However, the developer on the branch never gets to
incorporate any of the work being done on the trunk.

To allow that, the branch developer needs to add an extra step every now
and then (meaning whenever he feels like merging in recent trunk changes
and dealing with the inevitable conflicts):

     paste$ cvs update -j HEAD

The special reserved tag `HEAD' means the tip of the trunk.  The
preceding command merges in all of the trunk changes between the root of
the current branch (`Exotic_Greetings-branch') and the current highest
revisions of each file on the trunk.  Of course, qsmith should tag
again after doing this, so that the trunk developers can avoid
accidentally merging in their own changes when they're trying to get
qsmith's.

The branch developer can likewise use the trunk's merge tags as
boundaries, allowing the branch to merge exactly those trunk changes
between the last merge and the trunk's current state (the same way the
trunk does merges).  For example, supposing jrandom had made some
changes to hello.c after merging from the branch:

     floss$ emacs hello.c
      ...
     floss$ cvs ci -m "clarify algorithm" hello.c
     Checking in hello.c;
     /usr/local/newrepos/myproj/hello.c,v  <--  hello.c
     new revision: 1.22; previous revision: 1.21
     done
     floss$

Then, qsmith can merge those changes into his branch, commit, and, of
course, tag:

     paste$ cvs -q update -j merged-Exotic_Greetings-1 -j HEAD
     RCS file: /usr/local/newrepos/myproj/hello.c,v
     retrieving revision 1.21
     retrieving revision 1.22
     Merging differences between 1.21 and 1.22 into hello.c
     paste$ cvs -q update
     M hello.c
     paste$ cvs -q ci -m "merged trunk, from merged-Exotic_Greetings-1 to HEAD"
     Checking in hello.c;
     /usr/local/newrepos/myproj/hello.c,v  <--  hello.c
     new revision: 1.21.2.1; previous revision: 1.21
     done
     paste$ cvs -q tag merged-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
     paste$

Notice that jrandom did not bother to tag after committing the changes
to hello.c, but qsmith did.  The principle at work here is that although
you don't need to tag after every little change, you should always tag
after a merge or after committing your line of development up to a
mergeable state.  That way, other people - perhaps on other branches -
have a reference point against which to base their own merges.


File: ossdev.info,  Node: The Flying Fish Approach -- A Simpler Way To Do It,  Next: Branches And Keyword Expansion -- Natural Enemies,  Prev: The Dovetail Approach -- Merging In And Out Of The Trunk,  Up: Going Out On A Limb (How To Work With Branches And Survive)

The Flying Fish Approach - A Simpler Way To Do It
-------------------------------------------------

There is a simpler, albeit slightly limiting, variant of the preceding.
In it, the branch developers freeze while the trunk merges, and then the
trunk developers create an entirely new branch, which replaces the old
one.  The branch developers move onto that branch and continue working.
The cycle continues until there is no more need for branch development.
It goes something like this (in shorthand - we'll assume jrandom@floss
has the trunk and qsmith@paste has the branch, as usual):

     floss$ cvs tag -b BRANCH-1
     paste$ cvs checkout -r BRANCH-1 myproj

Trunk and branch both start working; eventually, the developers confer
and decide it's time to merge the branch into the trunk:

     paste$ cvs ci -m "committing all uncommitted changes"
     floss$ cvs update -j BRANCH-1

All the changes from the branch merge in; the branch developers stop
working while the trunk developers resolve any conflicts, commit, tag,
and create a new branch:

     floss$ cvs ci -m "merged from BRANCH-1"
     floss$ cvs tag merged-from-BRANCH-1
     floss$ cvs tag -b BRANCH-2

Now the branch developers switch their working copies over to the new
branch; they know they won't lose any uncommitted changes by doing so,
because they were up-to-date when the merge happened, and the new branch
is coming out of a trunk that has incorporated the changes from the old
branch:

     paste$ cvs update -r BRANCH-2

And the cycle continues in that way, indefinitely; just substitute
BRANCH-2 for BRANCH-1 and BRANCH-3 for BRANCH-2.

I call this the "Flying Fish" technique, because the branch is
constantly emerging from the trunk, traveling a short distance, then
rejoining it.  The advantages of this approach are that it's simple (the
trunk always merges in all the changes from a given branch) and the
branch developers never need to resolve conflicts (they're simply handed
a new, clean branch on which to work each time).  The disadvantage, of
course, is that the branch people must sit idle while the trunk is
undergoing merge (which can take an arbitrary amount of time, depending
on how many conflicts need to be resolved).  Another minor disadvantage
is that it results in many little, unused branches laying around instead
of many unused non-branch tags.  However, if having millions of tiny,
obsolete branches doesn't bother you, and you anticipate fairly
trouble-free merges, Flying Fish may be the easiest way to go in terms
of mental bookkeeping.

Whichever way you do it, you should try to keep the separations as short
as possible.  If the branch and the trunk go too long without merging,
they could easily begin to suffer not just from textual drift, but
semantic drift as well.  Changes that conflict textually are the easiest
ones to resolve.  Changes that conflict conceptually, but not textually,
often prove hardest to find and fix.  The isolation of a branch, so
freeing to the developers, is dangerous precisely because it shields
each side from the effects of others' changes...for a time.  When you
use branches, communication becomes more vital than ever: Everyone needs
to make extra sure to review each others' plans and code to ensure that
they're all staying on the same track.


File: ossdev.info,  Node: Branches And Keyword Expansion -- Natural Enemies,  Prev: The Flying Fish Approach -- A Simpler Way To Do It,  Up: Going Out On A Limb (How To Work With Branches And Survive)

Branches And Keyword Expansion - Natural Enemies
------------------------------------------------

If your files contain RCS keywords that expand differently on branch and
trunk, you're almost guaranteed to get spurious conflicts on every
merge.  Even if nothing else changed, the keywords are overlapping, and
their expansions won't match.  For example, if README.txt contains this
on the trunk

     $Revision: 1.1.2.1 $

and this on the branch

     $Revision: 1.1.2.1 $

then when the merge is performed, you'll get the following conflict:

     floss$ cvs 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
     floss$ cat README.txt
      ...
     <<<<<<< README.txt
     key $Revision: 1.1.2.1 $
     =======
     key $Revision: 1.1.2.1 $
     >>>>>>> 1.14.2.1
      ...
     floss$

To avoid this, you can temporarily disable expansion by passing the -kk
option (I don't know what it stands for; "kill keywords" maybe?) when
you do the merge:

     floss$ cvs update -kk -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
     floss$ cat README.txt
      ...
     $Revision: 1.1.2.1 $
      ...
     floss$

There is one thing to be careful of, however: If you use -kk, it
overrides whatever other keyword expansion mode you may have set for
that file.  Specifically, this is a problem for binary files, which are
normally -kb (which suppresses all keyword expansion and line-end
conversion).  So if you have to merge binary files in from a branch,
don't use -kk.  Just deal with the conflicts by hand instead.

