|
|
|
git for CVS users
|
|
|
|
=================
|
|
|
|
|
|
|
|
So you're a CVS user. That's ok, it's a treatable condition. The job of
|
|
|
|
this document is to put you on the road to recovery, by helping you
|
|
|
|
convert an existing cvs repository to git, and by showing you how to use a
|
|
|
|
git repository in a cvs-like fashion.
|
|
|
|
|
|
|
|
Some basic familiarity with git is required. This
|
|
|
|
link:tutorial.html[tutorial introduction to git] should be sufficient.
|
|
|
|
|
|
|
|
First, note some ways that git differs from CVS:
|
|
|
|
|
|
|
|
* Commits are atomic and project-wide, not per-file as in CVS.
|
|
|
|
|
|
|
|
* Offline work is supported: you can make multiple commits locally,
|
|
|
|
then submit them when you're ready.
|
|
|
|
|
|
|
|
* Branching is fast and easy.
|
|
|
|
|
|
|
|
* Every working tree contains a repository with a full copy of the
|
|
|
|
project history, and no repository is inherently more important than
|
|
|
|
any other. However, you can emulate the CVS model by designating a
|
|
|
|
single shared repository which people can synchronize with; see below
|
|
|
|
for details.
|
|
|
|
|
|
|
|
Importing a CVS archive
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
First, install version 2.1 or higher of cvsps from
|
|
|
|
link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
|
|
|
|
sure it is in your path. The magic command line is then
|
|
|
|
|
|
|
|
-------------------------------------------
|
|
|
|
$ git cvsimport -v -d <cvsroot> -C <destination> <module>
|
|
|
|
-------------------------------------------
|
|
|
|
|
|
|
|
This puts a git archive of the named CVS module in the directory
|
|
|
|
<destination>, which will be created if necessary. The -v option makes
|
|
|
|
the conversion script very chatty.
|
|
|
|
|
|
|
|
The import checks out from CVS every revision of every file. Reportedly
|
|
|
|
cvsimport can average some twenty revisions per second, so for a
|
|
|
|
medium-sized project this should not take more than a couple of minutes.
|
|
|
|
Larger projects or remote repositories may take longer.
|
|
|
|
|
|
|
|
The main trunk is stored in the git branch named `origin`, and additional
|
|
|
|
CVS branches are stored in git branches with the same names. The most
|
|
|
|
recent version of the main trunk is also left checked out on the `master`
|
|
|
|
branch, so you can start adding your own changes right away.
|
|
|
|
|
|
|
|
The import is incremental, so if you call it again next month it will
|
|
|
|
fetch any CVS updates that have been made in the meantime. For this to
|
|
|
|
work, you must not modify the imported branches; instead, create new
|
|
|
|
branches for your own changes, and merge in the imported branches as
|
|
|
|
necessary.
|
|
|
|
|
|
|
|
Development Models
|
|
|
|
------------------
|
|
|
|
|
|
|
|
CVS users are accustomed to giving a group of developers commit access to
|
|
|
|
a common repository. In the next section we'll explain how to do this
|
|
|
|
with git. However, the distributed nature of git allows other development
|
|
|
|
models, and you may want to first consider whether one of them might be a
|
|
|
|
better fit for your project.
|
|
|
|
|
|
|
|
For example, you can choose a single person to maintain the project's
|
|
|
|
primary public repository. Other developers then clone this repository
|
|
|
|
and each work in their own clone. When they have a series of changes that
|
|
|
|
they're happy with, they ask the maintainer to pull from the branch
|
|
|
|
containing the changes. The maintainer reviews their changes and pulls
|
|
|
|
them into the primary repository, which other developers pull from as
|
|
|
|
necessary to stay coordinated. The Linux kernel and other projects use
|
|
|
|
variants of this model.
|
|
|
|
|
|
|
|
With a small group, developers may just pull changes from each other's
|
|
|
|
repositories without the need for a central maintainer.
|
|
|
|
|
|
|
|
Emulating the CVS Development Model
|
|
|
|
-----------------------------------
|
|
|
|
|
|
|
|
Start with an ordinary git working directory containing the project, and
|
|
|
|
remove the checked-out files, keeping just the bare .git directory:
|
|
|
|
|
|
|
|
------------------------------------------------
|
|
|
|
$ mv project/.git /pub/repo.git
|
|
|
|
$ rm -r project/
|
|
|
|
------------------------------------------------
|
|
|
|
|
|
|
|
Next, give every team member read/write access to this repository. One
|
|
|
|
easy way to do this is to give all the team members ssh access to the
|
|
|
|
machine where the repository is hosted. If you don't want to give them a
|
|
|
|
full shell on the machine, there is a restricted shell which only allows
|
|
|
|
users to do git pushes and pulls; see gitlink:git-shell[1].
|
|
|
|
|
|
|
|
Put all the committers should in the same group, and make the repository
|
|
|
|
writable by that group:
|
|
|
|
|
|
|
|
------------------------------------------------
|
|
|
|
$ chgrp -R $group repo.git
|
|
|
|
$ find repo.git -mindepth 1 -type d |xargs chmod ug+rwx,g+s
|
|
|
|
$ GIT_DIR=repo.git git repo-config core.sharedrepository true
|
|
|
|
------------------------------------------------
|
|
|
|
|
|
|
|
Make sure committers have a umask of at most 027, so that the directories
|
|
|
|
they create are writable and searchable by other group members.
|
|
|
|
|
|
|
|
Suppose this repository is now set up in /pub/repo.git on the host
|
|
|
|
foo.com. Then as an individual commiter you can clone the shared
|
|
|
|
repository:
|
|
|
|
|
|
|
|
------------------------------------------------
|
|
|
|
$ git clone foo.com:/pub/repo.git/ my-project
|
|
|
|
$ cd my-project
|
|
|
|
------------------------------------------------
|
|
|
|
|
|
|
|
and hack away. The equivalent of `cvs update` is
|
|
|
|
|
|
|
|
------------------------------------------------
|
|
|
|
$ git pull origin
|
|
|
|
------------------------------------------------
|
|
|
|
|
|
|
|
which merges in any work that others might have done since the clone
|
|
|
|
operation.
|
|
|
|
|
|
|
|
[NOTE]
|
|
|
|
================================
|
|
|
|
The first `git clone` places the following in the
|
|
|
|
`my-project/.git/remotes/origin` file, and that's why the previous step
|
|
|
|
and the next step both work.
|
|
|
|
------------
|
|
|
|
URL: foo.com:/pub/project.git/ my-project
|
|
|
|
Pull: master:origin
|
|
|
|
------------
|
|
|
|
================================
|
|
|
|
|
|
|
|
You can update the shared repository with your changes using:
|
|
|
|
|
|
|
|
------------------------------------------------
|
|
|
|
$ git push origin master
|
|
|
|
------------------------------------------------
|
|
|
|
|
|
|
|
If someone else has updated the repository more recently, `git push`, like
|
|
|
|
`cvs commit`, will complain, in which case you must pull any changes
|
|
|
|
before attempting the push again.
|
|
|
|
|
|
|
|
In the `git push` command above we specify the name of the remote branch
|
|
|
|
to update (`master`). If we leave that out, `git push` tries to update
|
|
|
|
any branches in the remote repository that have the same name as a branch
|
|
|
|
in the local repository. So the last `push` can be done with either of:
|
|
|
|
|
|
|
|
------------
|
|
|
|
$ git push origin
|
|
|
|
$ git push repo.shared.xz:/pub/scm/project.git/
|
|
|
|
------------
|
|
|
|
|
|
|
|
as long as the shared repository does not have any branches
|
|
|
|
other than `master`.
|
|
|
|
|
|
|
|
[NOTE]
|
|
|
|
============
|
|
|
|
Because of this behaviour, if the shared repository and the developer's
|
|
|
|
repository both have branches named `origin`, then a push like the above
|
|
|
|
attempts to update the `origin` branch in the shared repository from the
|
|
|
|
developer's `origin` branch. The results may be unexpected, so it's
|
|
|
|
usually best to remove any branch named `origin` from the shared
|
|
|
|
repository.
|
|
|
|
============
|
|
|
|
|
|
|
|
Advanced Shared Repository Management
|
|
|
|
-------------------------------------
|
|
|
|
|
|
|
|
Git allows you to specify scripts called "hooks" to be run at certain
|
|
|
|
points. You can use these, for example, to send all commits to the shared
|
|
|
|
repository to a mailing list. See link:hooks.txt[Hooks used by git].
|
|
|
|
|
|
|
|
You can enforce finer grained permissions using update hooks. See
|
|
|
|
link:howto/update-hook-example.txt[Controlling access to branches using
|
|
|
|
update hooks].
|
|
|
|
|
|
|
|
CVS annotate
|
|
|
|
------------
|
|
|
|
|
|
|
|
So, something has gone wrong, and you don't know whom to blame, and
|
|
|
|
you're an ex-CVS user and used to do "cvs annotate" to see who caused
|
|
|
|
the breakage. You're looking for the "git annotate", and it's just
|
|
|
|
claiming not to find such a script. You're annoyed.
|
|
|
|
|
|
|
|
Yes, that's right. Core git doesn't do "annotate", although it's
|
|
|
|
technically possible, and there are at least two specialized scripts out
|
|
|
|
there that can be used to get equivalent information (see the git
|
|
|
|
mailing list archives for details).
|
|
|
|
|
|
|
|
git has a couple of alternatives, though, that you may find sufficient
|
|
|
|
or even superior depending on your use. One is called "git-whatchanged"
|
|
|
|
(for obvious reasons) and the other one is called "pickaxe" ("a tool for
|
|
|
|
the software archaeologist").
|
|
|
|
|
|
|
|
The "git-whatchanged" script is a truly trivial script that can give you
|
|
|
|
a good overview of what has changed in a file or a directory (or an
|
|
|
|
arbitrary list of files or directories). The "pickaxe" support is an
|
|
|
|
additional layer that can be used to further specify exactly what you're
|
|
|
|
looking for, if you already know the specific area that changed.
|
|
|
|
|
|
|
|
Let's step back a bit and think about the reason why you would
|
|
|
|
want to do "cvs annotate a-file.c" to begin with.
|
|
|
|
|
|
|
|
You would use "cvs annotate" on a file when you have trouble
|
|
|
|
with a function (or even a single "if" statement in a function)
|
|
|
|
that happens to be defined in the file, which does not do what
|
|
|
|
you want it to do. And you would want to find out why it was
|
|
|
|
written that way, because you are about to modify it to suit
|
|
|
|
your needs, and at the same time you do not want to break its
|
|
|
|
current callers. For that, you are trying to find out why the
|
|
|
|
original author did things that way in the original context.
|
|
|
|
|
|
|
|
Many times, it may be enough to see the commit log messages of
|
|
|
|
commits that touch the file in question, possibly along with the
|
|
|
|
patches themselves, like this:
|
|
|
|
|
|
|
|
$ git-whatchanged -p a-file.c
|
|
|
|
|
|
|
|
This will show log messages and patches for each commit that
|
|
|
|
touches a-file.
|
|
|
|
|
|
|
|
This, however, may not be very useful when this file has many
|
|
|
|
modifications that are not related to the piece of code you are
|
|
|
|
interested in. You would see many log messages and patches that
|
|
|
|
do not have anything to do with the piece of code you are
|
|
|
|
interested in. As an example, assuming that you have this piece
|
|
|
|
of code that you are interested in in the HEAD version:
|
|
|
|
|
|
|
|
if (frotz) {
|
|
|
|
nitfol();
|
|
|
|
}
|
|
|
|
|
|
|
|
you would use git-rev-list and git-diff-tree like this:
|
|
|
|
|
|
|
|
$ git-rev-list HEAD |
|
|
|
|
git-diff-tree --stdin -v -p -S'if (frotz) {
|
|
|
|
nitfol();
|
|
|
|
}'
|
|
|
|
|
|
|
|
We have already talked about the "\--stdin" form of git-diff-tree
|
|
|
|
command that reads the list of commits and compares each commit
|
|
|
|
with its parents (otherwise you should go back and read the tutorial).
|
|
|
|
The git-whatchanged command internally runs
|
|
|
|
the equivalent of the above command, and can be used like this:
|
|
|
|
|
|
|
|
$ git-whatchanged -p -S'if (frotz) {
|
|
|
|
nitfol();
|
|
|
|
}'
|
|
|
|
|
|
|
|
When the -S option is used, git-diff-tree command outputs
|
|
|
|
differences between two commits only if one tree has the
|
|
|
|
specified string in a file and the corresponding file in the
|
|
|
|
other tree does not. The above example looks for a commit that
|
|
|
|
has the "if" statement in it in a file, but its parent commit
|
|
|
|
does not have it in the same shape in the corresponding file (or
|
|
|
|
the other way around, where the parent has it and the commit
|
|
|
|
does not), and the differences between them are shown, along
|
|
|
|
with the commit message (thanks to the -v flag). It does not
|
|
|
|
show anything for commits that do not touch this "if" statement.
|
|
|
|
|
|
|
|
Also, in the original context, the same statement might have
|
|
|
|
appeared at first in a different file and later the file was
|
|
|
|
renamed to "a-file.c". CVS annotate would not help you to go
|
|
|
|
back across such a rename, but git would still help you in such
|
|
|
|
a situation. For that, you can give the -C flag to
|
|
|
|
git-diff-tree, like this:
|
|
|
|
|
|
|
|
$ git-whatchanged -p -C -S'if (frotz) {
|
|
|
|
nitfol();
|
|
|
|
}'
|
|
|
|
|
|
|
|
When the -C flag is used, file renames and copies are followed.
|
|
|
|
So if the "if" statement in question happens to be in "a-file.c"
|
|
|
|
in the current HEAD commit, even if the file was originally
|
|
|
|
called "o-file.c" and then renamed in an earlier commit, or if
|
|
|
|
the file was created by copying an existing "o-file.c" in an
|
|
|
|
earlier commit, you will not lose track. If the "if" statement
|
|
|
|
did not change across such a rename or copy, then the commit that
|
|
|
|
does rename or copy would not show in the output, and if the
|
|
|
|
"if" statement was modified while the file was still called
|
|
|
|
"o-file.c", it would find the commit that changed the statement
|
|
|
|
when it was in "o-file.c".
|
|
|
|
|
|
|
|
NOTE: The current version of "git-diff-tree -C" is not eager
|
|
|
|
enough to find copies, and it will miss the fact that a-file.c
|
|
|
|
was created by copying o-file.c unless o-file.c was somehow
|
|
|
|
changed in the same commit.
|
|
|
|
|
|
|
|
You can use the --pickaxe-all flag in addition to the -S flag.
|
|
|
|
This causes the differences from all the files contained in
|
|
|
|
those two commits, not just the differences between the files
|
|
|
|
that contain this changed "if" statement:
|
|
|
|
|
|
|
|
$ git-whatchanged -p -C -S'if (frotz) {
|
|
|
|
nitfol();
|
|
|
|
}' --pickaxe-all
|
|
|
|
|
|
|
|
NOTE: This option is called "--pickaxe-all" because -S
|
|
|
|
option is internally called "pickaxe", a tool for software
|
|
|
|
archaeologists.
|