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: Finding Out What You (And Others) Did -- update And diff,  Next: CVS And Implied Arguments,  Prev: Making A Change,  Up: A Day With CVS

Finding Out What You (And Others) Did - update And diff
-------------------------------------------------------

Previously, I've talked about updating as a way of bringing changes down
from the repository into your working copy - that is, as a way of
getting other people's changes.  However, update is really a bit more
complex; it compares the overall state of the working copy with the
state of the project in the repository.  Even if nothing in the
repository has changed since checkout, something in the working copy may
have, and update will show that, too:

     floss$ cvs update
     cvs update: Updating .
     M hello.c
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir

The M next to hello.c means the file has been modified since it was last
checked out, and the modifications have not yet been committed to the
repository.

Sometimes, merely knowing which files you've edited is all you need.
However, if you want a more detailed look at the changes, you can get a
full report in diff format.  The diff command compares the possibly
modified files in the working copy to their counterparts in the
repository and displays any differences:

     floss$ cvs diff
     cvs diff: Diffing .
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.1.1.1
     diff -r1.1.1.1 hello.c
     6a7
     >   printf ("Goodbye, world!\n");
     cvs diff: Diffing a-subdir
     cvs diff: Diffing a-subdir/subsubdir
     cvs diff: Diffing b-subdir

That's helpful, if a bit obscure, but there's still a lot of cruft in
the output.  For starters, you can ignore most of the first few lines.
They just name the repository file and give the number of the last
checked-in revision.  These are useful pieces of information under other
circumstances (we'll look more closely at them later), but you don't
need them when you're just trying to get a sense of what changes have
been made in the working copy.

A more serious impediment to reading the diff is that CVS is announcing
its entry as it goes into each directory during the update.  This can be
useful during long updates on large projects, as it gives you a sense of
how much longer the command will take, but right now it's just getting
in the way of reading the diff.  Let's tell CVS to be quiet about where
it's working, with the -Q global option:

     floss$ cvs -Q diff
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.1.1.1
     diff -r1.1.1.1 hello.c
     6a7
     >   printf ("Goodbye, world!\n");

Better - at least some of the cruft is gone.  However, the diff is
still hard to read.  It's telling you that at line 6, a new line was
added (that is, what became line 7), whose contents were:

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

The preceding ">" in the diff tells you that this line is present in the
newer version of the file but not in the older one.

The format could be made even more readable, however.  Most people find
"context" diff format easier to read because it displays a few lines of
context on either side of a change.  Context diffs are generated by
passing the -c flag to diff:

     floss$ cvs -Q diff -c
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.1.1.1
     diff -c -r1.1.1.1 hello.c
     *** hello.c     1999/04/18 18:18:22     1.1.1.1
     --- hello.c     1999/04/19 02:17:07
     ***************
     *** 4,7 ****
     ---4,8 --
       main ()
       {
         printf ("Hello, world!\n");
     +   printf ("Goodbye, world!\n");
       }

Now that's clarity! Even if you're not used to reading context diffs, a
glance at the preceding output will probably make it obvious what
happened: a new line was added (the + in the first column signifies an
added line) between the line that prints Hello, world! and the final
curly brace.

We don't need to be able to read context diffs perfectly (that's patch's
job), but it's worth taking the time to acquire at least a passing
familiarity with the format.  The first two lines (after the
introductory cruft) are

     *** hello.c     1999/04/18 18:18:22     1.1.1.1
     --- hello.c     1999/04/19 02:17:07

and they tell you what is being diffed against what.  In this case,
revision 1.1.1.1 of hello.c is being compared against a modified version
of the same file (thus, there's no revision number for the second line,
because only the working copy's changes haven't been committed to the
repository yet).  The lines of asterisks and dashes identify sections
farther down in the diff.  Later on, a line of asterisks, with a line
number range embedded, precedes a section from the original file.  Then
a line of dashes, with a new and potentially different line number range
embedded, precedes a section from the modified file.  These sections are
organized into contrasting pairs (known as "hunks"), one side from the
old file and the other side from the new.

Our diff has one hunk:

     ***************
     *** 4,7 ****
     --- 4,8 --
       main ()
       {
         printf ("Hello, world!\n");
     +   printf ("Goodbye, world!\n");
       }

The first section of the hunk is empty, meaning that no material was
removed from the original file.  The second section shows that, in the
corresponding place in the new file, one line has been added; it's
marked with a "+". (When diff quotes excerpts from files, it reserves
the first two columns on the left for special codes, such as "+" so the
entire excerpt appears to be indented by two spaces.  This extra
indentation is stripped off when the diff is applied, of course.)

The line number ranges show the hunk's coverage, including context
lines.  In the original file, the hunk was in lines 4 through 7; in the
new file, it's lines 4 through 8 (because a line has been added).  Note
that the diff didn't need to show any material from the original file
because nothing was removed; it just showed the range and moved on to
the second half of the hunk.

Here's another context diff, from an actual project of mine:

     floss$ cvs -Q diff -c
     Index: cvs2cl.pl
     ===================================================================
     RCS file: /usr/local/cvs/kfogel/code/cvs2cl/cvs2cl.pl,v
     retrieving revision 1.76
     diff -c -r1.76 cvs2cl.pl
     *** cvs2cl.pl   1999/04/13 22:29:44     1.76
     --- cvs2cl.pl   1999/04/19 05:41:37
     ***************
     *** 212,218 ****
               # can contain uppercase and lowercase letters, digits, '-',
               # and '_'. However, it's not our place to enforce that, so
               # we'll allow anything CVS hands us to be a tag:
     !         /^\s([^:]+): ([0-9.]+)$/;
               push (@{$symbolic_names{$2}}, $1);
             }
           }
     -- 212,218 --
               # can contain uppercase and lowercase letters, digits, '-',
               # and '_'. However, it's not our place to enforce that, so
               # we'll allow anything CVS hands us to be a tag:
     !         /^\s([^:]+): ([\d.]+)$/;
               push (@{$symbolic_names{$2}}, $1);
             }
           }

The exclamation point shows that the marked line differs between the old
and new files.  Since there are no "+" or "-" signs, we know that the
total number of lines in the file has remained the same.

Here's one more context diff from the same project, slightly more
complex this time:

     floss$ cvs -Q diff -c
     Index: cvs2cl.pl
     ===================================================================
     RCS file: /usr/local/cvs/kfogel/code/cvs2cl/cvs2cl.pl,v
     retrieving revision 1.76
     diff -c -r1.76 cvs2cl.pl
     *** cvs2cl.pl   1999/04/13 22:29:44     1.76
     --- cvs2cl.pl   1999/04/19 05:58:51
     ***************
     *** 207,217 ****
     }
             else    # we're looking at a tag name, so parse & store it
             {
     -         # According to the Cederqvist manual, in node "Tags", "Tag
     -         # names must start with an uppercase or lowercase letter and
     -         # can contain uppercase and lowercase letters, digits, '-',
     -         # and '_'. However, it's not our place to enforce that, so
     -         # we'll allow anything CVS hands us to be a tag:
               /^\s([^:]+): ([0-9.]+)$/;
               push (@{$symbolic_names{$2}}, $1);
             }
     - 207,212 --
     ***************
     *** 223,228 ****
     --- 218,225 --
           if (/^revision (\d\.[0-9.]+)$/) {
             $revision = "$1";
           }
     +
     +     # This line was added, I admit, solely for the sake of a diff example.
     
           # If have file name but not time and author, and see date or
           # author, then grab them:

This diff has two hunks.  In the first, five lines were removed (these
lines are only shown in the first section of the hunk, and the second
section's line count shows that it has five fewer lines).  An unbroken
line of asterisks forms the boundary between hunks, and in the second
hunk we see that two lines have been added: a blank line and a pointless
comment.  Note how the line numbers compensate for the effect of the
previous hunk.  In the original file, the second hunk's range of the
area was lines 223 through 228; in the new file, because of the deletion
that took place in the first hunk, the range is in lines 218 through
225.

Congratulations, you are probably now as expert as you'll ever need to
be at reading diffs.


File: ossdev.info,  Node: CVS And Implied Arguments,  Next: Committing,  Prev: Finding Out What You (And Others) Did -- update And diff,  Up: A Day With CVS

CVS And Implied Arguments
-------------------------

In each of the CVS commands so far, you may have noticed that no files
were specified on the command line.  We ran

     floss$ cvs diff

instead of

     floss$ cvs diff hello.c

and

     floss$ cvs update

instead of

     floss$ cvs update hello.c

The principle at work here is that if you don't name any files, CVS acts
on all files for which the command could possibly be appropriate.  This
even includes files in subdirectories beneath the current directory; CVS
automatically descends from the current directory through every
subdirectory in the tree.  For example, if you modified
b-subdir/random.c and a-subdir/subsubdir/fish.c, running update may
result in this:

     floss$ cvs update
     cvs update: Updating .
     M hello.c
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     M a-subdir/subsubdir/fish.c
     cvs update: Updating b-subdir
     M b-subdir/random.c
     floss$

or better yet:

     floss$ cvs -q update
     M hello.c
     M a-subdir/subsubdir/fish.c
     M b-subdir/random.c
     floss$

Note: The -q flag is a less emphatic version of -Q.  Had we used -Q, the
command would have printed out nothing at all, because the modification
notices are considered nonessential informational messages.  Using the
lowercase -q is less strict; it suppresses the messages we probably
don't want, while allowing certain, more useful messages to pass
through.

You can also name specific files for the update:

     floss$ cvs update hello.c b-subdir/random.c
     M hello.c
     M b-subdir/random.c
     floss$

and CVS will only examine those files, ignoring all others.

In truth, it's more common to run update without restricting it to
certain files.  In most situations, you'll want to update the entire
directory tree at once.  Remember, the updates we're doing here only
show that some files have been locally modified, because nothing has
changed yet in the repository.  When other people are working on the
project with you, there's always the chance that running update will
pull some new changes down from the repository and incorporate them into
your local files.  In that case, you may find it slightly more useful to
name which files you want updated.

The same principle can be applied to other CVS commands.  For example,
with diff, you can choose to view the changes one file at a time

     floss$ cvs diff -c b-subdir/random.c
     Index: b-subdir/random.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     retrieving revision 1.1.1.1
     diff -c -r1.1.1.1 random.c
     *** b-subdir/random.c   1999/04/18 18:18:22     1.1.1.1
     --- b-subdir/random.c   1999/04/19 06:09:48
     ***************
     *** 1 ****
     ! /* A completely empty C file. */
     --- 1,8 --
     ! /* Print out a random number. */
     !
     ! #include <stdio.h>
     !
     ! void main ()
     ! {
     !   printf ("a random number\n");
     ! }

or see all the changes at once (hang on to your seat, this is going to
be a big diff):

     floss$ cvs -Q diff -c
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.1.1.1
     diff -c -r1.1.1.1 hello.c
     *** hello.c     1999/04/18 18:18:22     1.1.1.1
     --- hello.c     1999/04/19 02:17:07
     ***************
     *** 4,7 ****
     --- 4,8 --
       main ()
       {
         printf ("Hello, world!\n");
     +   printf ("Goodbye, world!\n");
       }
     Index: a-subdir/subsubdir/fish.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v
     retrieving revision 1.1.1.1
     diff -c -r1.1.1.1 fish.c
     *** a-subdir/subsubdir/fish.c   1999/04/18 18:18:22     1.1.1.1
     --- a-subdir/subsubdir/fish.c   1999/04/19 06:08:50
     ***************
     *** 1 ****
     ! /* A completely empty C file. */
     --- 1,8 --
     ! #include <stdio.h>
     !
     ! void main ()
     ! {
     !   while (1) {
     !     printf ("fish\n");
     !   }
     ! }
     Index: b-subdir/random.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     retrieving revision 1.1.1.1
     diff -c -r1.1.1.1 random.c
     *** b-subdir/random.c   1999/04/18 18:18:22     1.1.1.1
     --- b-subdir/random.c   1999/04/19 06:09:48
     ***************
     *** 1 ****
     ! /* A completely empty C file. */
     --- 1,8 --
     ! /* Print out a random number. */
     !
     ! #include <stdio.h>
     !
     ! void main ()
     ! {
     !   printf ("a random number\n");
     ! }

Anyway, as you can see from these diffs, this project is clearly ready
for prime time.  Let's commit the changes to the repository.


File: ossdev.info,  Node: Committing,  Next: Revision Numbers,  Prev: CVS And Implied Arguments,  Up: A Day With CVS

Committing
----------

The "commit" command sends modifications to the repository.  If you
don't name any files, a commit will send all changes to the repository;
otherwise, you can pass the names of one or more files to be committed
(other files would be ignored, in that case).

Here, we commit one file by name and two by inference:

     floss$ cvs commit -m "print goodbye too" hello.c
     Checking in hello.c;
     /usr/local/cvs/myproj/hello.c,v  <--  hello.c
     new revision: 1.2; previous revision: 1.1
     done
     floss$ cvs commit -m "filled out C code"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in a-subdir/subsubdir/fish.c;
     /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v  <--  fish.c
     new revision: 1.2; previous revision: 1.1
     done
     Checking in b-subdir/random.c;
     /usr/local/cvs/myproj/b-subdir/random.c,v  <--  random.c
     new revision: 1.2; previous revision: 1.1
     done
     floss$

Take a moment to read over the output carefully.  Most of what it says
is pretty self-explanatory.  One thing you may notice is that revision
numbers have been incremented (as expected), but the original revisions
are listed as 1.1 instead of 1.1.1.1 as we saw in the Entries file
earlier.

There is an explanation for this discrepancy, but it's not very
important.  It concerns a special meaning that CVS attaches to revision
1.1.1.1.  For most purposes, we can just say that files receive a
revision number of 1.1 when imported, but the number is displayed - for
reasons known only to CVS - as 1.1.1.1 in the Entries file, until the
first commit.


File: ossdev.info,  Node: Revision Numbers,  Next: Detecting And Resolving Conflicts,  Prev: Committing,  Up: A Day With CVS

Revision Numbers
----------------

Each file in a project has its own revision number.  When a file is
committed, the last portion of the revision number is incremented by
one.  Thus, at any given time, the various files comprising a project
may have very different revision numbers.  This just means that some
files have been changed (committed) more often than others.

(You may be wondering, what's the point of the part to the left of the
decimal point, if only the part on the right ever changes? Actually,
although CVS never automatically increments the number on the left, that
number can be incremented on request by a user.  This is a rarely used
feature, and we won't cover it in this tour.)

In the example project that we've been using, we just committed changes
to three files.  Each of those files is now revision 1.2, but the
remaining files in the project are still revision 1.1.  When you check
out a project, you get each file at its highest revision so far.  Here
is what qsmith would see if he checked out myproj right now and looked
at the revision numbers for the top-level directory:

     paste$ cvs -q -d :pserver:qsmith@cvs.foobar.com:/usr/local/cvs co myproj
     U myproj/README.txt
     U myproj/hello.c
     U myproj/a-subdir/whatever.c
     U myproj/a-subdir/subsubdir/fish.c
     U myproj/b-subdir/random.c
     paste$ cd myproj/CVS
     paste$ cat Entries
     /README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//
     /hello.c/1.2/Mon Apr 19 06:35:15 1999//
     D/a-subdir////
     D/b-subdir////
     paste$

The file hello.c (among others) is now at revision 1.2, while README.txt
is still at the initial revision (revision 1.1.1.1, also known as 1.1).

If he adds the line

     printf ("between hello and goodbye\n");

to hello.c and commit it, the file's revision number will be incremented
once more:

     paste$ cvs ci -m "added new middle line"
     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.3; previous revision: 1.2
     done
     paste$

Now hello.c is revision 1.3, fish.c and random.c still are revision 1.2,
and every other file is revision 1.1.

Note: that the command was given as cvs ci instead of cvs commit.  Most
CVS commands have short forms, to make typing easier.  For checkout,
update, and commit, the abbreviated versions are co, up, and ci,
respectively.  You can get a list of all of the short forms by running
the command `cvs --help-synonyms'.

You can usually ignore a file's revision number.  In most situations,
the numbers are just internal bookkeeping that CVS handles
automatically.  However, being able to find and compare revision numbers
is extremely handy when you have to retrieve (or diff against) an
earlier copy of a file.

Examining the Entries file isn't the only way to discover a revision
number.  You can also use the status command

     paste$ cvs status hello.c
     ===================================================================
     File: hello.c           Status: Up-to-date
     
        Working revision:    1.3     Tue Apr 20 02:34:42 1999
        Repository revision: 1.3     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)

which, if invoked without any files being named, shows the status of
every file in the project:

     paste$ cvs status
     cvs status: Examining.
     ===================================================================
     File: README.txt        Status: Up-to-date
     
        Working revision:    1.1.1.1 Sun Apr 18 18:18:22 1999
        Repository revision: 1.1.1.1 /usr/local/cvs/myproj/README.txt,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     ===================================================================
     File: hello.c           Status: Up-to-date
     
        Working revision:    1.3     Tue Apr 20 02:34:42 1999
        Repository revision: 1.3     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     cvs status: Examining a-subdir
     ===================================================================
     File: whatever.c        Status: Up-to-date
     
        Working revision:    1.1.1.1 Sun Apr 18 18:18:22 1999
        Repository revision: 1.1.1.1 /usr/local/cvs/myproj/a-subdir/whatever.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     cvs status: Examining a-subdir/subsubdir
     ===================================================================
     File: fish.c            Status: Up-to-date
     
        Working revision:    1.2     Mon Apr 19 06:35:27 1999
        Repository revision: 1.2     /usr/local/cvs/myproj/
                                     a-subdir/subsubdir/fish.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     cvs status: Examining b-subdir
     ===================================================================
     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:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     paste$

Just ignore the parts of that output that you don't understand.  In
fact, that's generally good advice with CVS.  Often, the one little bit
of information you're looking for will be accompanied by reams of
information that you don't care about at all, and maybe don't even
understand.  This situation is normal.  Just pick out what you need, and
don't worry about the rest.

In the previous example, the parts we care about are the first three
lines (not counting the blank line) of each file's status output.  The
first line is the most important; it tells you the file's name, and its
status in the working copy.  All of the files are currently in sync with
the repository, so they all say `Up-to-date'.  However, if random.c has
been modified but not committed, it might read like this:

     ===================================================================
     File: random.c          Status: Locally Modified
     
        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:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)

The Working revision and Repository revision tell you whether the file
is out of sync with the repository.  Returning to our original working
copy (jrandom's copy, which hasn't seen the new change to hello.c yet),
we see:

     floss$ cvs status hello.c
     ===================================================================
     File: hello.c           Status: Needs Patch
     
        Working revision:    1.2     Mon Apr 19 02:17:07 1999
        Repository revision: 1.3     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     floss$

This tells us that someone has committed a change to hello.c, bringing
the repository copy to revision 1.3, but that this working copy is still
on revision 1.2.  The line Status: Needs Patch means that the next
update will retrieve those changes from the repository and "patch" them
into the working copy's file.

Let's pretend for the moment that we don't know anything about qsmith's
change to hello.c, so we don't run status or update.  Instead, we just
start editing the file, making a slightly different change at the same
location.  This brings us to our first conflict.


File: ossdev.info,  Node: Detecting And Resolving Conflicts,  Next: Finding Out Who Did What (Browsing Log Messages),  Prev: Revision Numbers,  Up: A Day With CVS

Detecting And Resolving Conflicts
---------------------------------

Detecting a conflict is easy enough.  When you run update, CVS tells
you, in no uncertain terms, that there's a conflict.  But first, let's
create the conflict.  We edit hello.c to insert the line

     printf ("this change will conflict\n");

right where qsmith committed this:

     printf ("between hello and goodbye\n");

At this point, the status of our copy of hello.c is

     floss$ cvs status hello.c
     ===================================================================
     File: hello.c           Status: Needs Merge
     
        Working revision:    1.2     Mon Apr 19 02:17:07 1999
        Repository revision: 1.3     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     floss$

meaning that there are changes both in the repository and the working
copy, and these changes need to be merged. (CVS isn't aware that the
changes will conflict, because we haven't run update yet.) When we do
the update, we see this:

     floss$ cvs update hello.c
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.2
     retrieving revision 1.3
     Merging differences between 1.2 and 1.3 into hello.c
     rcsmerge: warning: conflicts during merge
     cvs update: conflicts found in hello.c
     C hello.c
     floss$

The last line of output is the giveaway.  The C in the left margin next
to the filename indicates that changes have been merged, but that they
conflict.  The contents of hello.c now shows both changes:

     #include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
     <<<<<<< hello.c
       printf ("this change will conflict\n");
     =======
       printf ("between hello and goodbye\n");
     >>>>>>> 1.3
       printf ("Goodbye, world!\n");
     }

Conflicts are always shown delimited by conflict markers, in the
following format:

     <<<<<<< (filename)
       the uncommitted changes in the working copy
       blah blah blah
     =======
       the new changes that came from the repository
       blah blah blah
       and so on
     >>>>>>> (latest revision number in the repository)

The Entries file also shows that the file is in a halfway state at the
moment:

     floss$ cat CVS/Entries
     /README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//
     D/a-subdir////
     D/b-subdir////
     /hello.c/1.3/Result of merge+Tue Apr 20 03:59:09 1999//
     floss$

The way to resolve the conflict is to edit the file so that it contains
whatever text is appropriate, removing the conflict markers in the
process, and then to commit.  This doesn't necessarily mean choosing one
change over another; you could decide neither change is sufficient and
rewrite the conflicting section (or indeed the whole file) completely.
In this case, we'll adjust in favor of the first change, but with
capitalization and punctuation slightly different from qsmith's:

     floss$ emacs hello.c
       (make the edits...)
     floss$ cat hello.c
     #include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
       printf ("BETWEEN HELLO AND GOODBYE.\n");
       printf ("Goodbye, world!\n");
     }
     floss$ cvs ci -m "adjusted middle line"
     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.4; previous revision: 1.3
     done
     floss$


File: ossdev.info,  Node: Finding Out Who Did What (Browsing Log Messages),  Next: Examining And Reverting Changes,  Prev: Detecting And Resolving Conflicts,  Up: A Day With CVS

Finding Out Who Did What (Browsing Log Messages)
------------------------------------------------

By now, the project has undergone several changes.  If you're trying to
get an overview of what has happened so far, you don't necessarily want
to examine every diff in detail.  Browsing the log messages would be
ideal, and you can accomplish this with the log command:

     floss$ cvs log
     (pages upon pages of output omitted)

The log output tends to be a bit verbose.  Let's look at the log
messages for just one file:

     floss$ cvs log hello.c
     RCS file: /usr/local/cvs/myproj/hello.c,v
     Working file: hello.c
     head: 1.4
     branch:
     locks: strict
     access list:
     symbolic names:
             start: 1.1.1.1
             jrandom: 1.1.1
     keyword substitution: kv
     total revisions: 5;     selected revisions: 5
     description:
     --------------
     revision 1.4
     date: 1999/04/20 04:14:37;  author: jrandom;  state: Exp;  lines: +1 -1
     adjusted middle line
     --------------
     revision 1.3
     date: 1999/04/20 02:30:05;  author: qsmith;  state: Exp;  lines: +1 -0
     added new middle line
     --------------
     revision 1.2
     date: 1999/04/19 06:35:15;  author: jrandom;  state: Exp;  lines: +1 -0
     print goodbye too
     --------------
     revision 1.1
     date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;
     branches:  1.1.1;
     Initial revision
     --------------
     revision 1.1.1.1
     date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;  lines: +0 -0
     initial import into CVS
     =========================================================================
     floss$

As usual, there's a lot of information at the top that you can just
ignore.  The good stuff comes after each line of dashes, in a format
that is self-explanatory.

When many files are sent in the same commit, they all share the same log
message; a fact that can be useful in tracing changes.  For example,
remember back when we committed fish.c and random.c simultaneously? It
was done like this:

     floss$ cvs commit -m "filled out C code"
     Checking in a-subdir/subsubdir/fish.c;
     /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v  <-  fish.c
     new revision: 1.2; previous revision: 1.1
     done
     Checking in b-subdir/random.c;
     /usr/local/cvs/myproj/b-subdir/random.c,v  <-  random.c
     new revision: 1.2; previous revision: 1.1
     done
     floss$

The effect of this was to commit both files with the same log message:
"Filled out C code."  (As it happened, both files started at revision
1.1 and went to 1.2, but that's just a coincidence.  If random.c had
been at revision 1.29, it would have moved to 1.30 with this commit, and
its revision 1.30 would have had the same log message as fish.c's
revision 1.2.)

When you run cvs log on them, you'll see the shared message:

     floss$ cvs log a-subdir/subsubdir/fish.c b-subdir/random.c
     
     RCS file: /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v
     Working file: a-subdir/subsubdir/fish.c
     head: 1.2
     branch:
     locks: strict
     access list:
     symbolic names:
             start: 1.1.1.1
             jrandom: 1.1.1
     keyword substitution: kv
     total revisions: 3;     selected revisions: 3
     description:
     --------------
     revision 1.2
     date: 1999/04/19 06:35:27;  author: jrandom;  state: Exp;  lines: +8 -1
     filled out C code
     --------------
     revision 1.1
     date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;
     branches:  1.1.1;
     Initial revision
     --------------
     revision 1.1.1.1
     date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;  lines: +0 -0
     initial import into CVS
     =========================================================================
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     Working file: b-subdir/random.c
     head: 1.2
     branch:
     locks: strict
     access list:
     symbolic names:
             start: 1.1.1.1
             jrandom: 1.1.1
     keyword substitution: kv
     total revisions: 3;     selected revisions: 3
     description:
     --------------
     revision 1.2
     date: 1999/04/19 06:35:27;  author: jrandom;  state: Exp;  lines: +8 -1
     filled out C code
     --------------
     revision 1.1
     date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;
     branches:  1.1.1;
     Initial revision
     --------------
     revision 1.1.1.1
     date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;  lines: +0 -0
     initial import into CVS
     =========================================================================
     floss$

From this output, you'll know that the two revisions were part of the
same commit (the fact that the timestamps on the two revisions are the
same, or very close, is further evidence).

Browsing log messages is a good way to get a quick overview of what's
been going on in a project or to find out what happened to a specific
file at a certain time.  There are also free tools available to convert
raw cvs log output to more concise and readable formats (such as GNU
ChangeLog style); we won't cover those tools in this tour, but they'll
be introduced in *Note Third-Party Tools::.


File: ossdev.info,  Node: Examining And Reverting Changes,  Next: The Slow Method Of Reverting,  Prev: Finding Out Who Did What (Browsing Log Messages),  Up: A Day With CVS

Examining And Reverting Changes
-------------------------------

Suppose that, in the course of browsing the logs, qsmith sees that
jrandom made the most recent change to hello.c:

     revision 1.4
     date: 1999/04/20 04:14:37;  author: jrandom;  state: Exp;  lines: +1 -1
     adjusted middle line

and wonders what jrandom did? In formal terms, the question that qsmith
is asking is, "What's the difference between my revision (1.3) of
hello.c, and jrandom's revision right after it (1.4)?" The way to find
out is with the diff command, but this time by comparing two past
revisions using the -r command option to specify both of them:

     paste$ cvs diff -c -r 1.3 -r 1.4 hello.c
     Index: hello.c
     ===========================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.3
     retrieving revision 1.4
     diff -c -r1.3 -r1.4
     *** hello.c     1999/04/20 02:30:05     1.3
     --- hello.c     1999/04/20 04:14:37     1.4
     ***************
     *** 4,9 ****
       main ()
       {
         printf ("Hello, world!\n");
     !   printf ("between hello and goodbye\n");
         printf ("Goodbye, world!\n");
       }
     --- 4,9 --
       main ()
       {
         printf ("Hello, world!\n");
     !   printf ("BETWEEN HELLO AND GOODBYE.\n");
         printf ("Goodbye, world!\n");
       }
     paste$

The change is pretty clear, when viewed this way.  Because the revision
numbers are given in chronological order (usually a good idea), the diff
shows them in order.  If only one revision number is given, CVS uses the
revision of the current working copy for the other.

When qsmith sees this change, he instantly decides he likes his way
better and resolves to "undo"-that is, to step back by one revision.

However, this doesn't mean that he wants to lose his revision 1.4.
Although, in an absolute technical sense, it's probably possible to
achieve that effect in CVS, there's almost never any reason to do so.
It's much preferable to keep revision 1.4 in the history and make a new
revision 1.5 that looks exactly like 1.3.  That way the undo event
itself is part of the file's history.

The only question is, how can you retrieve the contents of revision 1.3
and put them into 1.5?

In this particular case, because the change is a very simple one, qsmith
can probably just edit the file by hand to mirror revision 1.3 and then
commit.  However, if the changes are more complex (as they usually are
in a real-life project), trying to re-create the old revision manually
will be hopelessly error-prone.  Therefore, we'll have qsmith use CVS to
retrieve and recommit the older revision's contents.

There are two equally good ways to do this: the slow, plodding way and
the fast, fancy way.  We'll examine the slow, plodding way first.


File: ossdev.info,  Node: The Slow Method Of Reverting,  Next: The Fast Method Of Reverting,  Prev: Examining And Reverting Changes,  Up: A Day With CVS

The Slow Method Of Reverting
----------------------------

This method involves passing the -p flag to update, in conjunction with
-r.  The -p option sends the contents of the named revision to standard
output.  By itself, this isn't terribly helpful; the contents of the
file fly by on the display, leaving the working copy unchanged.
However, by redirecting the standard output into the file, the file will
now hold the contents of the older revision.  It's just as though the
file had been hand-edited into that state.

First, though, qsmith needs to get up to date with respect to the
repository:

     paste$ cvs update
     cvs update: Updating .
     U hello.c
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     paste$ cat hello.c
     #include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
       printf ("BETWEEN HELLO AND GOODBYE.\n");
       printf ("Goodbye, world!\n");
     }
     paste$

Next, he runs update -p to make sure that the revision 1.3 is the one he
wants:

     paste$ cvs update -p -r 1.3 hello.c
     ===================================================================
     Checking out hello.c
     RCS:  /usr/local/cvs/myproj/hello.c,v
     VERS: 1.3
     ***************
     #include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
       printf ("between hello and goodbye\n");
       printf ("Goodbye, world!\n");
     }

Oops, there are a few lines of cruft at the beginning.  They aren't
actually being sent to standard output, but rather to standard error, so
they're harmless.  Nevertheless, they make reading the output more
difficult and can be suppressed with -Q:

     paste$ cvs -Q update -p -r 1.3 hello.c
     #include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
       printf ("between hello and goodbye\n");
       printf ("Goodbye, world!\n");
     }
     paste$

There - that's exactly what qsmith was hoping to retrieve.  The next
step is to put that content into the working copy's file, using a Unix
redirect (that's what the ">" does):

     paste$ cvs -Q update -p -r 1.3 hello.c > hello.c
     paste$ cvs update
     cvs update: Updating .
     M hello.c
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     paste$

Now when update is run, the file is listed as modified, which makes
sense because its contents have changed.  Specifically, it has the same
content as the old revision 1.3 (not that CVS is aware of its being
identical to a previous revision - it just knows the file has been
modified).  If qsmith wants to make extra sure, he can do a diff to
check:

     paste$ cvs -Q diff -c
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.4
     diff -c -r1.4 hello.c
     *** hello.c     1999/04/20 04:14:37     1.4
     --- hello.c     1999/04/20 06:02:25
     ***************
     *** 4,9 ****
       main ()
       {
         printf ("Hello, world!\n");
     !   printf ("BETWEEN HELLO AND GOODBYE.\n");
         printf ("Goodbye, world!\n");
       }
     --- 4,9 --
       main ()
       {
         printf ("Hello, world!\n");
     !   printf ("between hello and goodbye\n");
         printf ("Goodbye, world!\n");
       }
     paste$

Yes, that's exactly what he wanted: a pure reversion - in fact, it is
the reverse of the diff he previously obtained.  Satisfied, he commits:

     paste$ cvs ci -m "reverted to 1.3 code"
     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; previous revision: 1.4
     done
     paste$


File: ossdev.info,  Node: The Fast Method Of Reverting,  Prev: The Slow Method Of Reverting,  Up: A Day With CVS

The Fast Method Of Reverting
----------------------------

The fast, fancy way of reverting is to use the -j (for "join") flag to
the update command.  This flag is like -r in that it takes a revision
number, and you can use up to two -j's at once.  CVS calculates the
difference between the two named revisions and applies that difference
as a patch to the file in question (so the order in which you give the
revisions is important).

Thus, assuming qsmith's copy is up to date, he can just do this:

     paste$ cvs update -j 1.4 -j 1.3 hello.c
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.4
     retrieving revision 1.3
     Merging differences between 1.4 and 1.3 into hello.c
     paste$ cvs update
     cvs update: Updating .
     M hello.c
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     paste$ cvs ci -m "reverted to 1.3 code" hello.c
     Checking in hello.c;
     /usr/local/cvs/myproj/hello.c,v  <--  hello.c
     new revision: 1.5; previous revision: 1.4
     done
     paste$

When you only need to revert one file, there's not really much
difference between the plodding and fast methods.  Later in the book,
you'll see how the fast method is much better for reverting multiple
files at once.  In the meantime, use whichever way you're more
comfortable with.

Reverting Is Not A Substitute For Communication
===============================================

In all likelihood, what qsmith did in our example was quite rude.  When
you're working on a real project with other people and you think that
someone has committed a bad change, the first thing you should do is
talk to him or her about it.  Maybe there's a good reason for the
change, or maybe he or she just didn't think things through.  Either
way, there's no reason to rush and revert.  A full record of everything
that happens is stored permanently in CVS, so you can always revert to
a previous revision after consulting with whoever made the changes.

If you're a project maintainer facing a deadline or you feel you have
the right and the need to revert the change unconditionally, then do so
- but follow it immediately with an email to the author whose change
was reverted, explaining why you did it and what needs to be fixed to
recommit the change.


File: ossdev.info,  Node: Other Useful CVS Commands,  Next: Branches,  Prev: A Day With CVS,  Up: An Overview of CVS

Other Useful CVS Commands
=========================

At this point, you should be pretty comfortable with basic CVS usage.
I'll abandon the tour narrative and introduce a few more useful commands
in summarized form.

* Menu:

* Adding Files::
* Adding Directories::
* CVS And Binary Files::
* Removing Files::
* Removing Directories::
* Renaming Files And Directories::
* Avoiding Option Fatigue::
* Getting Snapshots (Dates And Tagging)::
* Acceptable Date Formats::
* Marking A Moment In Time (Tags)::


File: ossdev.info,  Node: Adding Files,  Next: Adding Directories,  Up: Other Useful CVS Commands

Adding Files
------------

Adding a file is a two-step process: First you run the add command on
it, then commit.  The file won't actually appear in the repository until
commit is run:

     floss$ cvs add newfile.c
     cvs add: scheduling file 'newfile.c' for addition
     cvs add: use 'cvs commit' to add this file permanently
     floss$ cvs ci -m "added newfile.c" newfile.c
     RCS file: /usr/local/cvs/myproj/newfile.c,v
     done
     Checking in newfile.c;
     /usr/local/cvs/myproj/newfile.c,v  <-  newfile.c
     initial revision: 1.1
     done
     floss$


File: ossdev.info,  Node: Adding Directories,  Next: CVS And Binary Files,  Prev: Adding Files,  Up: Other Useful CVS Commands

Adding Directories
------------------

Unlike adding a file, adding a new directory is done in one step;
there's no need to do a commit afterwards:

     floss$ mkdir c-subdir
     floss$ cvs add c-subdir
     Directory /usr/local/cvs/myproj/c-subdir added to the repository
     floss$

If you look inside the new directory in the working copy, you'll see
that a CVS subdirectory was created automatically by add:

     floss$ ls c-subdir
     CVS/
     floss$ ls c-subdir/CVS
     Entries     Repository  Root
     floss$

Now you can add files (or new directories) inside it, as with any other
working copy directory.


File: ossdev.info,  Node: CVS And Binary Files,  Next: Removing Files,  Prev: Adding Directories,  Up: Other Useful CVS Commands

CVS And Binary Files
--------------------

Until now, I've left unsaid the dirty little secret of CVS, which is
that it doesn't handle binary files very well (well, there are other
dirty little secrets, but this definitely counts as one of the
dirtiest).  It's not that CVS doesn't handle binaries at all; it does,
just not with any great panache.

All the files we've been working with until now have been plain text
files.  CVS has some special tricks for text files.  For example, when
it's working between a Unix repository and a Windows or Macintosh
working copy, it converts file line endings appropriately for each
platform.  For example, Unix convention is to use a linefeed (LF) only,
whereas Windows expects a carriage return/linefeed (CRLF) sequence at
the end of each line.  Thus, the files in a working copy on a Windows
machine will have CRLF endings, but a working copy of the same project
on a Unix machine will have LF endings (the repository itself is always
stored in LF format).

Another trick is that CVS detects special strings, known as RCS keyword
strings, in text files and replaces them with revision information and
other useful things.  For example, if your file contains this string

     $Revision: 1.1.2.1 $

CVS will expand on each commit to include the revision number.  For
example, it may get expanded to

     $Revision: 1.1.2.1 $

CVS will keep that string up to date as the file is developed.  (The
various keyword strings are documented in *Note Advanced CVS:: and
*Note Third-Party Tools::.)

This string expansion is a very useful feature in text files, as it
allows you to see the revision number or other information about a file
while you're editing it.  But what if the file is a JPG image?  Or a
compiled executable program?  In those kinds of files, CVS could do some
serious damage if it blundered around expanding any keyword string that
it encountered.  In a binary, such strings may even appear by
coincidence.

Therefore, when you add a binary file, you have to tell CVS to turn off
both keyword expansion and line-ending conversion.  To do so, use -kb:

     floss$ cvs add -kb filename
     floss$ cvs ci -m "added blah" filename
       (etc)

Also, in some cases (such as text files that are likely to contain
spurious keyword strings), you may wish to disable just the keyword
expansion.  That's done with -ko:

     floss$ cvs add -ko filename
     floss$ cvs ci -m "added blah" filename
       (etc)

(In fact, this chapter is one such document, because of the
`$Revision: 1.1.2.1 $' example shown here.)

Note that you can't meaningfully run `cvs diff' on two revisions of a
binary file.  Diff uses a text-based algorithm that can only report
whether two binary files differ, but not how they differ.  Future
versions of CVS may provide a way to diff binary files.


File: ossdev.info,  Node: Removing Files,  Next: Removing Directories,  Prev: CVS And Binary Files,  Up: Other Useful CVS Commands

Removing Files
--------------

Removing a file is similar to adding one, except there's an extra step:
You have to remove the file from the working copy first:

     floss$ rm newfile.c
     floss$ cvs remove newfile.c
     cvs remove: scheduling 'newfile.c' for removal
     cvs remove: use 'cvs commit' to remove this file permanently
     floss$ cvs ci -m "removed newfile.c" newfile.c
     Removing newfile.c;
     /usr/local/cvs/myproj/newfile.c,v  <-  newfile.c
     new revision: delete; previous revision: 1.1
     done
     floss$

Notice how, in the second and third commands, we name newfile.c
explicitly even though it doesn't exist in the working copy anymore.  Of
course, in the commit, you don't absolutely need to name the file, as
long as you don't mind the commit encompassing any other modifications
that may have taken place in the working copy.

