| git for CVS users |
| ================= |
| |
| Ok, so you're a CVS user. That's ok, it's a treatable condition, and the |
| first step to recovery is admitting you have a problem. The fact that |
| you are reading this file means that you may be well on that path |
| already. |
| |
| The thing about CVS is that it absolutely sucks as a source control |
| manager, and you'll thus be happy with almost anything else. git, |
| however, may be a bit 'too' different (read: "good") for your taste, and |
| does a lot of things differently. |
| |
| One particular suckage of CVS is very hard to work around: CVS is |
| basically a tool for tracking 'file' history, while git is a tool for |
| tracking 'project' history. This sometimes causes problems if you are |
| used to doing very strange things in CVS, in particular if you're doing |
| things like making branches of just a subset of the project. git can't |
| track that, since git never tracks things on the level of an individual |
| file, only on the whole project level. |
| |
| The good news is that most people don't do that, and in fact most sane |
| people think it's a bug in CVS that makes it tag (and check in changes) |
| one file at a time. So most projects you'll ever see will use CVS |
| 'as if' it was sane. In which case you'll find it very easy indeed to |
| move over to git. |
| |
| First off: this is not a git tutorial. See |
| link:tutorial.html[Documentation/tutorial.txt] for how git |
| actually works. This is more of a random collection of gotcha's |
| and notes on converting from CVS to git. |
| |
| Second: CVS has the notion of a "repository" as opposed to the thing |
| that you're actually working in (your working directory, or your |
| "checked out tree"). git does not have that notion at all, and all git |
| working directories 'are' the repositories. However, you can easily |
| emulate the CVS model by having one special "global repository", which |
| people can synchronize with. See details later, but in the meantime |
| just keep in mind that with git, every checked out working tree will |
| have a full revision control history of its own. |
| |
| |
| Importing a CVS archive |
| ----------------------- |
| |
| Ok, you have an old project, and you want to at least give git a chance |
| to see how it performs. The first thing you want to do (after you've |
| gone through the git tutorial, and generally familiarized yourself with |
| how to commit stuff etc in git) is to create a git'ified version of your |
| CVS archive. |
| |
| Happily, that's very easy indeed. git will do it for you, although git |
| will need the help of a program called "cvsps": |
| |
| http://www.cobite.com/cvsps/ |
| |
| which is not actually related to git at all, but which makes CVS usage |
| look almost sane (ie you almost certainly want to have it even if you |
| decide to stay with CVS). However, git will want 'at least' version 2.1 |
| of cvsps (available at the address above), and in fact will currently |
| refuse to work with anything else. |
| |
| Once you've gotten (and installed) cvsps, you may or may not want to get |
| any more familiar with it, but make sure it is in your path. After that, |
| the magic command line is |
| |
| git cvsimport -v -d <cvsroot> -C <destination> <module> |
| |
| which will do exactly what you'd think it does: it will create a git |
| archive of the named CVS module. The new archive will be created in the |
| subdirectory named <destination>; it'll be created if it doesn't exist. |
| Default is the local directory. |
| |
| It can take some time to actually do the conversion for a large archive |
| since it involves checking out from CVS every revision of every file, |
| and the conversion script is reasonably chatty unless you omit the '-v' |
| option, but on some not very scientific tests it averaged about twenty |
| revisions per second, so a medium-sized project should not take more |
| than a couple of minutes. For larger projects or remote repositories, |
| the process may take longer. |
| |
| After the (initial) import is done, the CVS archive's current head |
| revision will be checked out -- thus, you can start adding your own |
| changes right away. |
| |
| The import is incremental, i.e. if you call it again next month it'll |
| fetch any CVS updates that have been happening in the meantime. The |
| cut-off is date-based, so don't change the branches that were imported |
| from CVS. |
| |
| You can merge those updates (or, in fact, a different CVS branch) into |
| your main branch: |
| |
| git resolve HEAD origin "merge with current CVS HEAD" |
| |
| The HEAD revision from CVS is named "origin", not "HEAD", because git |
| already uses "HEAD". (If you don't like 'origin', use cvsimport's |
| '-o' option to change it.) |
| |
| |
| Emulating CVS behaviour |
| ----------------------- |
| |
| |
| So, by now you are convinced you absolutely want to work with git, but |
| at the same time you absolutely have to have a central repository. |
| Step back and think again. Okay, you still need a single central |
| repository? There are several ways to go about that: |
| |
| 1. Designate a person responsible to pull all branches. Make the |
| repository of this person public, and make every team member |
| pull regularly from it. |
| |
| 2. Set up a public repository with read/write access for every team |
| member. Use "git pull/push" as you used "cvs update/commit". Be |
| sure that your repository is up to date before pushing, just |
| like you used to do with "cvs commit"; your push will fail if |
| what you are pushing is not up to date. |
| |
| 3. Make the repository of every team member public. It is the |
| responsibility of each single member to pull from every other |
| team member. |
| |
| |
| 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 archeologist"). |
| |
| 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. |