Back in May last year I switched over several projects from Subversion to Git. It’s a shame that git doesn’t have a better project name, something that says “version control system” rather than being a mild insult. That aside, though, what a relief it was to finally use something that is Not CVS.
At the time I couldn’t find a good “best practice” document, just lots of instructions on individual commands. Here is how I manage the system-config-printer source code using git. I hope someone finds it useful. I don’t pretend that this is “best practice” — it’s just some of the things I’ve learned since starting to use git.
The first thing to say is that gitk is really quite good at displaying a graphical representation of the repository. This is invaluable if you are in any doubt about which commit went on which branch, or whether local changes need to be pushed to a remote server. You can try to get the same sort of thing with git log ‑‑graph if you wish, but it will not be as clear or easy to understand.
Start off your local repository by cloning it from the public server. I’ll assume the remote repository (e.g. fedorahosted.org for me) is already set up. Next, you want to set up tracking branches for any branches you are going to be working on. Use git branch ‑r to see what branches are available, and then for each one use the following command to create tracking branches for them: git checkout ‑‑track -b branch origin/branch.
One of the main things I really love about git is that it can do proper merging. I have three main branches for system-config-printer at the moment:
- 1.0.x, for bug-fixes only,
- 1.1.x, for bug-fixes and small features, and
- master for larger features.
When fixing a bug I make sure to find the oldest branch that requires the bug-fix and check it in on that branch only. I periodically merge 1.0.x into 1.1.x, and 1.1.x into master.
This way, when I fix a bug on 1.0.x I know that it will eventually get merged onto the newer branches and won’t be forgotten. Next time I merge 1.0.x into 1.1.x it will automatically include that bug-fix, because merging remembers the point at which we last merged. Likewise, after having merged 1.0.x into 1.1.x, merging 1.1.x into master will also pull that fix into the master branch.
In this gitk window you can see that I committed a fix for status icon display on branch 1.0.x (the highlighted line), removed some blank lines from a file in 1.1.x (see the line below it), then collected the fixes into newer branches by merging 1.0.x into 1.1.x, and 1.1.x into master. The colours of the lines representing change history are arbitrary; they are only different colours so it is easier to tell them apart if they cross over each other.
The green boxes with text in are branch tags (“heads” in git language). You can see in this window that the repository I’m working with contains changes not yet in the remote repository (on fedorahosted.org) — the remotes/origin branch tags show the current state of that repository. To bring fedorahosted.org’s repository up to date I’ll need to run git push. Before pushing changes it is advisable to run git pull first, in case other people had made changes.
The yellow box with text in is a release tag, set using git tag.
Some changes don’t make sense to merge into newer branches, for example changing the version number in 1.0.x. To mark these changes as merged without actually applying them to the current branch I use git merge ‑s ours 1.0.x.
Sometimes the gitk view of the various branches can be a bit overwhelming if the change history is complicated. Use View→Edit view to select only those branches you are interested in — you have to type them in, separated by spaces. I have a saved view for “master 1.1.x 1.0.x” so that feature branches don’t clutter up the display.
It is easy enough to add and remove branches in your local repository, but the syntax for doing so in the remote repository is less obvious. Here’s how I do that sort of thing.
Adding a branch locally is straightforward: creating a new branch called cups-pk-helper starting from the point currently checked out is done like this: git checkout ‑b cups-pk-helper. The current branch is now cups-pk-helper, but it only exists locally.
To make that branch exist in the remote repository (named “origin”), we need to run git push telling it to make a new “head”: git push origin cups-pk-helper:refs/heads/cups-pk-helper.
Once the feature the branch was created for has been completed and tested and is ready to merge, merge it. The feature branch is no longer needed. All of the commits from the feature branch will stay around because they are reachable from the main branch. The branch name “cups-pk-helper” only marks the point immediately before the feature was merged.
To delete that “head” locally, use git branch ‑d cups-pk-helper. The remote repository will still have that branch name around. To get rid of it remotely, do this: git push origin :refs/heads/cups-pk-helper.
Merging remote branches from random repositories is similar. Once commits are reachable from some branch head in your repository, they will stay around in your repository.
I treat translation files (po/*.po) as a bit of a special case, and never merge them between branches. It becomes just too hard to merge. PO files don’t seem to lend themselves to being merged for some reason. To avoid merging them I have this line in a file called .gitattributes in the repository:
When merging, if any *.po files have been changed they will now show as unmerged and the merge will stop. I just run git checkout HEAD po to mark them as up to date, and complete the merge with git commit.
When I’m sent patches it is easiest to apply them if they have been generated with git format-patch. In that case I can just save run git am the-patch and have the patch committed under the author’s name, with the commit message taken from the email.