Work with the CVS repository using Git

This is a short memo on how to work with the CVS repository using Git.

Please note this is still very rough contents in terms of code testing.


Basic understanding of followings are prerequisites:

Work with the CVS repository using git

You may wish to use the convenience of your local git repository when you update an upstream source tree maintained in the CVS system.

Of course, you can set up the full repository history of CVS in the local repository and track the CVS HEAD in your local git repository as the remote branch, e.g., as cvs/master. In short, this is slow and cumbersome. I will explain this later in "Approach 2" which is also the usual case documented elsewhere.

For simplicity, I use following variables.


Approach 1: only future changes

Let's worry only future changes with following focuses for simplicity:

Here, you need to track CVS on "master" branch of git and work on "rebase" branch of git.

This can be realized as:

make simple CVS checkout and make it a git repository (1st run only)

  $ cvs -d :ext:${CVSROOT} checkout -P -kk $MODULE
  $ cd $MODULE
  $ git init; git add .; git commit -m "initial import"
  $ echo "CVS" > .git/info/exclude
  $ git checkout -b rebase

NOTE: All CVS directories are excluded from the git repository management.

update the git master branch to the latest CVS (non-1st run only)

  $ git reset --hard master && git clean -f
  $ cvs update -PdA -kk
  $ git add . ; git commit -m "new cvs HEAD"
  $ git checkout rebase
  $ git reset --hard rebase && git clean -f

start hacking the source on master

  $ vim foo.txt
  $ git commit -a -m "commit for foo"
  $ vim bar.txt
  $ git commit -a -m "commit for bar"
  ... (test code etc.)
  $ vim baz.txt
  $ git commit -a -m "commit for baz"

update to the latest CVS (to be sure)

  $ git checkout master
  $ git reset --hard master && git clean -f
  $ cvs update -PdA -kk
  $ git add . ; git commit -m "new cvs HEAD"
  $ git checkout rebase
  $ git reset --hard rebase && git clean -f 

rebase to the latest CVS and organize commit candidates

  $ git rebase -i master

commit as a series of commits

  $ git cherry master rebase | sed -n 's/^+ //p' | xargs -l1 \
    git cvsexportcommit -ckpvW

This commits each git change set as a single set of CVS commit.

Approach 2: synchronized full history of changes

This approach requires to set up 3 directories to be efficient with the CVS data transaction. Let's arrange them as follows under ${CWD}/:

Overview of the workflow is the following.

           1)            2)                           3)
Remote CVS -> Local CVS -+-> git-cvsimport (cvsps) -> hack ... +
        ^                |                                     |
        |                +-> cvs checkout -------------------> +
        |                                                      |
        +<------------------ git-cvsexportcommit <-------------+

Create a local copy of the CVS repository

 $ rsync -avS --delete --delay-updates --rsh=ssh ${CVSROOT}/. rsyncdir

You should see files under the following directories:

Create a local git repository and a local checkout from the CVS repository

 $ git cvsimport -akmRv -d ${CWD}/rsyncdir -C gitdir -r cvs $MODULE
 $ cvs -d ${CWD}/rsyncdir checkout -P -kk -d cvsdir $MODULE

The git-cvsimport command imports the full history of changes to the local git repository and allows to synchronize it continuously with the upstream CVS. The upstream CVS repository data without CVS/ directories is tracked on the remote branch called cvs/master if "-r cvs" option is used.

Hack code while using git

 $ cd ${CWD}/gitdir
  ... hack on master branch starting at remote branch cvs/master
 $ cd ${CWD}
 $ rsync -avS --delete --delay-updates --rsh=ssh ${CVSROOT}/. rsyncdir
 $ cd ${CWD}/cvsdir
 $ cvs -d ${CWD}/rsyncdir update -PdA -kk $MODULE
 $ cd ${CWD}/gitdir
 $ git rebase -i cvs/master
  ... resolve conflicts and clean up history

Merge pending patches into CVS automatically

 $ git cherry cvs/master master | sed -n 's/^+ //p' | xargs -l1 \
   git cvsexportcommit -ckpuv -d $CVSROOT -w ${CWD}/cvsdir/
 $ rm -rf ${CWD}/cvsdir

The "-d $CVSROOT" is required since CVS is used in an asymmetric fashion.


I am writing a git-cvs script to ease bidirectional operations between a single CVS tree and git.

Download git-cvs script and put it as git-cvs in your $PATH.

See "git cvs help" for what it does. This takes care lots of sanity checks and corner cases.

approach 1

 $ git cvs init :ext:${CVSROOT} $MODULE
 $ cd $MODULE
... hack hack
 $ git commit -a -m "change set 1"
 $ git cvs dcommit
... long time
 $ git cvs update

approach 2

 $ git cvs sync ${CVSROOT} $MODULE
 $ cd gitdir
... hack hack
 $ git commit -a -m "change set 1"
 $ git cvs dcommit
... long time
 $ git cvs update

See also: