cvsps — fast export the contents of a CVS repository (DEPRECATED)


cvsps [-h] [-z fuzz] [-s patchset] [-a author] [-f file] [-d date1 [-d date2]] [-l text] [-b branch] [-n] [-r tag [-r tag]] [-p directory] [-A authormap] [-R revmap] [-v] [-t] [--debuglvl bitmask] [-Z compression] [--root cvsroot] [--fast-export] [--convert-ignores] [--reposurgeon] [-i] [-k] [-T] [-V] [module-path]


This program has been declared end-of-life by its maintainer. Do not use it unless you have no alternative, and consider its results highly suspect on any CVS repository with branches - that is, anything other than a straight linear succession of commits. For a better alternative, see cvs-fast-export(1).


cvsps tries to group the per-file commits and tags in a CVS project repository into per-project changeset commits with common metadata, in the style of Subversion and later version-control systems.

The older, default reporting mode is designed for humans to look at and only includes commit metadata, not file contents. The newer --fast-export mode emits a git-style fast-import stream which can be consumed by the importers for git and other version-control systems.

There are several different ways to invoke cvsps:

  • From the top level of a CVS checkout directory, in which case no options or arguments are required. (This case runs if there is a CVS/Root beneath the current working directory.)
  • From a module subdirectory within a CVS repository, in which case no options or arguments are required. (This case runs if there is a CVSROOT directory in the parent of the current working directory.)
  • From the top level of a CVS repository directory, in which case a module-path argument is required. (This case runs if the program sees a CVSROOT directory in the current working directory.)
  • With a CVS URL of the form cvs://<hostname>/<path>#<module> as a module-path argument. This can be used to export the contents of a remote CVS repository.
  • With a root module specified via either the --root option or the CVSROOT environment variable, in which case a module-path argument is required. (This form is deprecated).

In fast-export mode:

  • Each patchset becomes a commit.
  • CVS tags become git lightweight tags.
  • The HEAD branch is renamed to master.
  • Other tag and branch names are sanitized to be legal for git; the characters ~^\*? are removed.
  • Since .cvsignore files have a syntax upward-compatible with that of .gitignore files, they may be renamed (if --convert-ignores is on).

cvsps does not parse the $HOME/.cvsrc file. If you have a $HOME/.cvspass file (such as is normally created by cvs login) it will be searched for a password.


display usage summary.
-z fuzz
set the timestamp fuzz factor for identifying patch sets.
generate diffs of the selected patch sets.
-s patchset[-[patchset]][,patchset…]
generate a diff for a given patchsets and patchset ranges.
-a author
restrict output to patchsets created by author.
-f regexp
restrict output to patchsets with filenames matching the specified regexp.
-d date1 -d date2
if just one date specified, show revisions newer than date1. If two dates specified, show revisions between two dates.
-l regex
restrict output to patchsets matching regex in log message. The regular expressions use POSIX syntax.
-b branch
restrict output to patchsets affecting history of branch. If you want to restrict to the main branch, use a branch of HEAD.
negate filter sense, print all patchsets not matching restrictions.
-r tag1 -r tag2
if just one tag specified, show revisions since tag1. If two tags specified, show revisions between the two tags.
-p dir
output individual patchsets as files in dir as dir/patchset.patch.
-A authormap

Apply an author-map file to the attribution lines. Each line must be of the form

ferd = Ferd J. Foonly <> America/Chicago

and will be applied to map the Unix username ferd to the DVCS-style user identity specified after the equals sign. The timezone field (after > and whitespace) is optional and (if present) is used to set the timezone offset to be attached to the date; acceptable formats for the timezone field are anything that can be in the TZ environment variable, including a [+-]hhmm offset. Whitespace around the equals sign is stripped. Lines beginning with a # or not containing an equals sign are silently ignored.

-R revmap
Write a revision map to the specified argument filename. Each line of the revision map consists of three whitespace-separated fields: a filename, an RCS revision number, and the mark of the commit to which that filename-revision pair was assigned.
show very verbose parsing messages.
show some brief memory usage statistics.
when multiple patchset diffs are being generated, put the patchset summary for all patchsets at the beginning of the output.
--diffs-opts option string
send a custom set of options to diff, for example to increase the number of context lines, or change the diff format.
--debuglvl bitmask
enable various debug output channels.
-Z compression
A value 1-9 which specifies amount of compression. A value of 0 disables compression.
--root cvsroot
Override the setting of CVSROOT (overrides working directory and environment).
Incremental export. Each commit with no ancestor gets a from pointer name. When importing to an existing repository, this will attach each such commit as a child of the last commit on $BRANCH in the existing repository.
Kill keywords: will extract files with -kk from the CVS archive to avoid noisy changesets.
Force deterministic dates for regression testing. Each patchset will have a monotonic-increasing attributed date computed from its patchset ID.
Emit the report as a git import stream.
Convert ..cvsignore files to .gitignore files.
Emit for each commit a list of the CVS file:revision pairs composing it as a bzr-style commit property named "cvs-revisions". From version 2.12 onward, reposurgeon can interpret these and use them as hints for reference-lifting.
Emit the program version and exit.
Operate on the specified module. If this option is not given, either the CVSROOT environment variable must be set to point directly at the module or cvsps must be run in a checkout directory or repository module subdirectory.


The old --cvs-direct option, and the -u and -x options having to do with local caching are gone; cvsps now always runs in direct mode and without caching. However, the -u command-line switch has been left in place and tied to an informative termination message so that users of older versions of calling scripts (such as git-cvsimport) will get an error message rather than silent misbehavior.

The ancestor-branch tracking enabled by the -A option in some previous versions of cvsps never worked properly and has been removed. The new -A option does author-name mapping, following the normal convention for CVS and SVN translation tools.

The -g and --summary-first options of older versions have also been removed. The following options are deprecated and may be removed in a future release: -s, -a, -f, -l, -n, -p, --root, --diff-opts, and -t. It is possible that the non-fast-export reporting mode will be removed, but if so the --fast-export option will be retained as a no-op.


Run within a checkout directory or a module subdirectory within a repository, dumps the history of its module in its repository in the old format.
cvsps --root :local:$PWD/foo --fast-export bar
Dump the history of module bar from a local repository directory foo in fast-export format.
cvsps cvs://
Dump the CVS history of the robotfindskitten project on Sourceforge using esr’s login credentials.
cvsps --root robotfindskitten
Same request using separate --root and module.
cvsps --root robotfindskitten
Same request, (redundantly) specifying the pserver method.


Tags are fundamentally file at a time in CVS, but like everything else, it would be nice to imagine that they are repository at a time. The approach cvsps takes is that a tag is assigned to a patchset. The meaning of this is that after this patchset, every revision of every file is after the tag (and conversely, before this patchset, at least one file is still before the tag).

However, there are two kinds of inconsistent (or funky) tags that can be created with older versions of CVS, even when following best practices for CVS. Newer versions do an up-to-dateness check that prevents these.

The first is what is called a funky tag. A funky tag is one where there are patchsets which are chronologically (and thus by patchset id) earlier than the tag, but are tagwise after. These tags will be marked as FUNKY in the Tag: section of the cvsps output. When a funky tag is specified as one of the -r arguments, there are some number of patchsets which need to be considered out of sequence. In this case, the patchsets themselves will be labeled FUNKY and will be processed correctly.

The second is called an invalid tag. An invalid tag is a tag where there are patchsets which are chronologically (and thus by patchset id) earlier than the tag, but which have members which are tagwise both before, and after the tag, in the same patchset. If an INVALID tag is specified as one of the -r arguments, cvsps will flag each member of the affected patchsets as before or after the tag and the patchset summary will indicate which members are which, and diffs will be generated accordingly.

These may be better explained by examples. The easiest test case for this is two developers, starting in a consistent state. (a):: developer 1 changes file A(sub1) to A(sub2) and commits, creating patchset P(sub1) chronologically earlier, thus with a lower patchset id. (b):: developer 2 changes file B(sub1) to B(sub2) and commits, creating patchset P(sub2) chronologically later, thus higher patchset id. (c):: developer 2 B does fInotfR do "cvs update", so does not get A(sub2) in working directory and creates a "tag" T(sub1)

A checkout of T(sub1) should have A(sub1) and B(sub2) and there is no "patchset" that represents this. In other words, if we label patchset P(sub2) with the tag there are earlier patchsets which need to be disregarded.

An "invalid" tag can be generated with a similar testcase, except:

  • In step (a) developer 1 also changes file C(sub1) to C(sub2)
  • developer 2 does a "selective" cvs update of only file C(sub2)
  • developer 1 does another change from C(sub2) to C(sub3), creating a new patchset in between the previous P(sub1) and P(sub2) "P(sub1(sub2))??"
  • Then we have step (b) and step (c). After this, a checkout of T(sub1) should have A(sub1), B(sub2) and C(sub2).

In other words, if we label patchset P(sub2) with the tag there are earlier patchsets which need to be partially disregarded.


Dates are reported in localtime, except that fast-export mode reports UTC. Three formats are accepted for the -d option:

  • RFC3339: yyyy-mm-ddThh:mm:ss.
  • A Unix-style timestamp (seconds since UTC epoch).
  • For backward compatibility with older versions, yyyy/mm/dd hh:mm:ss.

As a contrived example:

$ cvsps -d '2004-05-01T00:00:00' -d '2004/07/07 12:00:00'




Translating CVS repositories to the generic DVCS model expressed by import streams is not merely difficult and messy, there are weird CVS cases that cannot be correctly translated at all. cvsps will try to warn you about these cases rather than silently producing broken or incomplete translations.

CVS tags are per-file, not per revision. If developers are not careful in their use of tagging, it can be impossible to associate a tag with any of the changesets that cvsps resolves.

CVS-NT and versions of GNU CVS after 1.12 (2004) added a changeset commit-id to file metadata. Older sections of CVS history without these are vulnerable to various problems caused by clock skew between clients; this used to be relatively common for multiple reasons, including less pervasive use of NTP clock synchronization. cvsps will warn you when it sees such a section in your history. When it does, these caveats apply:

  • If timestamps of commits in the CVS repository were not stable enough to be used for ordering commits, changes may be reported in the wrong order.
  • If the timestamp order of different files crosses the revision order within the commit-matching time window, the order of commits reported may be wrong.

These problems cannot be fixed in cvsps; they are inherent to CVS.


This program has been declared end-of-life by its maintainer because it does a poor job of branch analysis and tag assignment on non-linear repositories. You should almost certainly be using cvs-fast-export(1) instead.

File execute bits are not correctly retrieved and passed through in --fast-export mode - all fileops will have 100664 permissions.

cvsps may be unable to communicate with some extremely ancient CVS server versions (this was a sacrifice for much faster performance). It is unlikely any of these are still in service; if you trip over one, mirror the repository locally with rsync or cvssuck and proceed.

CVS branches that were created but never got any file commits will not be reported in the translated import stream.

If any files were ever imported more than once (e.g., import of more than one vendor release), the head revision might contain incorrect content. cvsps issues a warning when this might occur.

If a CVS branch symbol could not be resolved to a translated commit, cvsps will issue a warning to that effect.


Report bugs to Eric S. Raymond <>. The project page is at