Next: , Previous: Going Out On A Limb (How To Work With Branches And Survive), Up: Advanced CVS


Tracking Third-Party Sources (Vendor Branches)

Sometimes a site will make local changes to a piece of software received from an outside source. If the outside source does not incorporate the local changes (and there might be many legitimate reasons why it can't), the site has to maintain its changes in each received upgrade of the software.

CVS can help with this task, via a feature known as vendor branches. In fact, vendor branches are the explanation behind the puzzling (until now) final two arguments to cvs import: the vendor tag and release tag that I glossed over in An Overview of CVS.

Here's how it works. The initial import is just like any other initial import of a CVS project (except that you'll want to choose the vendor tag and release tag with a little care):

     floss$ pwd
     /home/jrandom/theirproj-1.0
     floss$ cvs import -m "Import of TheirProj 1.0" theirproj Them THEIRPROJ_1_0
     N theirproj/INSTALL
     N theirproj/README
     N theirproj/src/main.c
     N theirproj/src/parse.c
     N theirproj/src/digest.c
     N theirproj/doc/random.c
     N theirproj/doc/manual.txt
     
     No conflicts created by this import
     
     floss$

Then you check out a working copy somewhere, make your local modifications, and commit:

     floss$ cvs -q co theirproj
     U theirproj/INSTALL
     U theirproj/README
     U theirproj/doc/manual.txt
     U theirproj/doc/random.c
     U theirproj/src/digest.c
     U theirproj/src/main.c
     U theirproj/src/parse.c
     floss$ cd theirproj
     floss$ emacs src/main.c src/digest.c
      ...
     floss$ cvs -q update
     M src/digest.c
     M src/main.c
     floss$ cvs -q ci -m "changed digestion algorithm; added comment to main"
     Checking in src/digest.c;
     /usr/local/newrepos/theirproj/src/digest.c,v  <--  digest.c
     new revision: 1.2; previous revision: 1.1
     done
     Checking in src/main.c;
     /usr/local/newrepos/theirproj/src/main.c,v  <--  main.c
     new revision: 1.2; previous revision: 1.1
     done
     floss$

A year later, the next version of the software arrives from Them, Inc., and you must incorporate your local changes into it. Their changes and yours overlap slightly. They've added one new file, modified a couple of files that you didn't touch, but also modified two files that you modified.

First you must do another import, this time from the new sources. Almost everything is the same as it was in the initial import – you're importing to the same project in the repository, and on the same vendor branch. The only thing different is the release tag:

     floss$ pwd
     /home/jrandom/theirproj-2.0
     floss$ cvs -q import -m "Import of TheirProj 2.0" theirproj Them THEIRPROJ_2_0
     U theirproj/INSTALL
     N theirproj/TODO
     U theirproj/README
     cvs import: Importing /usr/local/newrepos/theirproj/src
     C theirproj/src/main.c
     U theirproj/src/parse.c
     C theirproj/src/digest.c
     cvs import: Importing /usr/local/newrepos/theirproj/doc
     U theirproj/doc/random.c
     U theirproj/doc/manual.txt
     
     2 conflicts created by this import.
     Use the following command to help the merge:
     
            cvs checkout -jThem:yesterday -jThem theirproj
     
     floss$

My goodness – we've never seen CVS try to be so helpful. It's actually telling us what command to run to merge the changes. And it's almost right, too! Actually, the command as given works (assuming that you adjust yesterday to be any time interval that definitely includes the first import but not the second), but I mildly prefer to do it by release tag instead:

     floss$ cvs checkout -j THEIRPROJ_1_0 -j THEIRPROJ_2_0 theirproj
     cvs checkout: Updating theirproj
     U theirproj/INSTALL
     U theirproj/README
     U theirproj/TODO
     cvs checkout: Updating theirproj/doc
     U theirproj/doc/manual.txt
     U theirproj/doc/random.c
     cvs checkout: Updating theirproj/src
     U theirproj/src/digest.c
     RCS file: /usr/local/newrepos/theirproj/src/digest.c,v
     retrieving revision 1.1.1.1
     retrieving revision 1.1.1.2
     Merging differences between 1.1.1.1 and 1.1.1.2 into digest.c
     rcsmerge: warning: conflicts during merge
     U theirproj/src/main.c
     RCS file: /usr/local/newrepos/theirproj/src/main.c,v
     retrieving revision 1.1.1.1
     retrieving revision 1.1.1.2
     Merging differences between 1.1.1.1 and 1.1.1.2 into main.c
     U theirproj/src/parse.c
     floss$

Notice how the import told us that there were two conflicts, but the merge only seems to claim one conflict. It seems that CVS's idea of a conflict is a little different when importing than at other times. Basically, import reports a conflict if both you and the vendor modified a file between the last import and this one. However, when it comes time to merge, update sticks with the usual definition of "conflict" – overlapping changes. Changes that don't overlap are merged in the usual way, and the file is simply marked as modified.

A quick diff verifies that only one of the files actually has conflict markers:

     floss$ cvs -q update
     C src/digest.c
     M src/main.c
     floss$ cvs diff -c
     Index: src/digest.c
     ===================================================================
     RCS file: /usr/local/newrepos/theirproj/src/digest.c,v
     retrieving revision 1.2
     diff -c -r1.2 digest.c
     *** src/digest.c        1999/07/26 08:02:18     1.2
     -- src/digest.c        1999/07/26 08:16:15
     ***************
     *** 3,7 ****
     -- 3,11 ----
      void
      digest ()
      {
     + <<<<<<< digest.c
        printf ("gurgle, slorp\n");
     + =======
     +   printf ("mild gurgle\n");
     + >>>>>>> 1.1.1.2
      }
     Index: src/main.c
     ===================================================================
     RCS file: /usr/local/newrepos/theirproj/src/main.c,v
     retrieving revision 1.2
     diff -c -r1.2 main.c
     *** src/main.c  1999/07/26 08:02:18     1.2
     -- src/main.c  1999/07/26 08:16:15
     ***************
     *** 7,9 ****
     -- 7,11 ----
      {
        printf ("Goodbye, world!\n");
      }
     +
     + /* I, the vendor, added this comment for no good reason. */
     floss$

From here, it's just a matter of resolving the conflicts as with any other merge:

     floss$ emacs  src/digest.c  src/main.c
      ...
     floss$ cvs -q update
     M src/digest.c
     M src/main.c
     floss$ cvs diff src/digest.c
     cvs diff src/digest.c
     Index: src/digest.c
     ===================================================================
     RCS file: /usr/local/newrepos/theirproj/src/digest.c,v
     retrieving revision 1.2
     diff -r1.2 digest.c
     6c6
     <   printf ("gurgle, slorp\n");
     --
     >   printf ("mild gurgle, slorp\n");
     floss$

Then commit the changes

     floss$ cvs -q ci -m "Resolved conflicts with import of 2.0"
     Checking in src/digest.c;
     /usr/local/newrepos/theirproj/src/digest.c,v  <--  digest.c
     new revision: 1.3; previous revision: 1.2
     done
     Checking in src/main.c;
     /usr/local/newrepos/theirproj/src/main.c,v  <--  main.c
     new revision: 1.3; previous revision: 1.2
     done
     floss$

and wait for the next release from the vendor. (Of course, you'll also want to test that your local modifications still work!)

——————————————————————————————–

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

copyright  ©  March 20 2019 sean dreilinger url: https://durak.org/sean/pubs/software/cvsbook/Tracking-Third_002dParty-Sources-_0028Vendor-Branches_0029.html