Several projects have been moved to the git revision control system. While there exist tutorials out there for using git, one of git's features is that it allows people to use many different workflows, so many of those tutorials won't apply to git as it's used in X.Org projects. We might call what we usually do "shared central repository" mode.
Things which are within the scope of this document:
- Checking out code
- Committing code
- Resolving conflicts
Working on branches Things which are not within the scope of this document:
Using the index for selective committing First note about git: there are many commands. Each command exists as an argument to the git command (
git clone) and also as a hyphenated version to aid in tab-completion ("git-clone").
The next thing to understand is that when you check out code from a remote git repository, you end up with a full-fledged git repository yourself. When you diff, commit, etc. code, you are doing it against your local repository. Clone, pull, and push are used to move changes between repositories. Your local repository is stored in the .git directory at the top of the tree.
If you have an anonymously-checked out repository which you now wish to use to push, you can edit
.git/remotes/origin and replace the contents of the
URL: line (change
If your username at freedesktop.org is different from local, the refspec becomes
ssh://email@example.com/git/xorg/lib/libX11, or you can just add the following to
~/.ssh/config to tell ssh your default username for all of freedesktop.org:
Host *.freedesktop.org User myusername
Now, you have a copy of the master branch of the tree. Go ahead and build it and whatever else. If you make some changes, you can see what files you would commit with
git status -a or get a diff of the local tree against the repository with
git status -a -v
-a flag means all local updates. The git system actually has this concept of an "index", managed using
git-update-index, which lets you selectively commit changes. However, because this adds to confusion, I will leave learning about the index up to you to do later.
Getting the latest upstream code
There are two ways to update your local repository. Which one to use depends on whether you have committed changes in the meantime.
No changes: pull
The command to update your local repository is:
It will pull down the latest repository information from the
origin remote file (which points at where you initially cloned the repository from), then merge. If the new changes don't conflict with anything locally, the merge will "fast-forward" your branch reference to the new head. If new changes do conflict, it will try to do an automatic merge using a couple of different schemes, and then automatically commits the merge if it's satisfied. If you notice that your pull resulted in a merge (in the output of pull), you might want to use
gitk to see if the merge did what you expected it to. If it isn't satisfied, then you have to resolve the conflict manually.
You've made changes: fetch and rebase
If you have committed local changes, then
git-pull will create a spurious "Merge" commit, which will pollute the change list when you later push upstream. To avoid this, do these two commands:
git fetch git rebase origin
Instead of merging, this attempts to insert the changes you pull from the origin repository before your local changes, avoiding the merge message.
Dealing with conflicts
If you get a serious conflict that can't be merged automatically, git will leave familiar conflict markers in the files, and
git status should say that you've got unmerged files. Go edit them and fix your conflicts, and test. After you do so,
git status will still be noisy about unmerged files (since you haven't updated the index to say you've merged them), but you can still do
git commit -a
and commit your merge. It hands you a default log message for the merge, and it will retain the information on the parents of the commit you did, so that branch history is maintained.
It may happen that you commit something you really didn't want to go into the repository. This is not referring to broken changes, but things like mis-merges or getting branches confused. If you haven't pushed the code upstream, then it's really easy to make it look like the commit didn't happen. To reset to the immediate previous revision, you would run:
git reset --hard HEAD^
Reverting code prior to commit
git doesn't have a revert command like svn. Instead, just run
git checkout -- <yourfile> to revert it to the last committed version. The
-- isn't necessary in most cases, but does provide extra insurance.
If you're satisfied with your diff, you could commit it with:
git commit -a
The commit message should have a one-line summary ('Add support for FooBazzle 700'), and then a longer explanatory paragraph, separated by a blank line, e.g.:
Add support for FooBazzle 700 Add support for the FooBazzle 700, which is an interesting device in that it does not actually exist. This is our first driver for an entirely ficticious device, and as such, it performs no rendering.
Your subject line should include the subsystem name (for example, 'KDrive: Fix keymap memory leak', not just 'Fix leak'), unless it's entirely redundant.
After you enter your log message, it will quickly terminate. You've now committed your diff to your local repository. You could run:
to see your diff in the history now. Note that the
gitk output actually shows two branches: master and origin. The
master branch is the head of development in the local tree. The
origin branch is the last version from upstream.
Pushing your code upstream
Now that you've resolved the conflicts (if any) with others upstream, you're ready to push your changes. To do this, type
git push origin
This tells git to push every branch in a
Push: line in the
origin remote upstream (more on the Push: lines later). Unlike
git push does require you to specify the remote and doesn't have a default.
Emailing your code upstream
If you do not have direct commit access or wish to get patch review, type:
git format-patch origin (assuming you're on master)
This will generate a few sequentially-numbered text files, one per patch (e.g.
0002-Fix-memory-leak.txt), in mbox format. These can be sent individually. If part of a series, use the
-n argument to
git format-patch, so their subjects will be '[PATCH 1/7] Add support for FooBazzle 700', and so on, and so forth.
Traditionally, a '[PATCH 0/7] FooBazzle-related enhancements' mail would be sent to the list first, giving a relatively long explanation of the patchset, as well as a very brief rundown of the individual patches. You should then edit your patches before you send them, to include an
References header listing the
Message-Id of the 0th message, so it doesn't break threading.
Working on branches
Now you've learned how to check out HEAD code, diff, update, commit, and push code back upstream. The next thing to talk about is branching.
You can see what branch you're on using
git branch -- it should show
master initially. To switch branches in your current repository, do:
git checkout <branch>
If you've got local changes, there are flags to either throw out your local changes, or try to merge them across to the new branch. By default it will just refuse to change.
If you want to make a new branch, you can do:
git checkout -b <new-branch>
Now you've got a new branch. However, git checkout doesn't set up the branch: branch-origin mapping that we want. There's a rule in using git, which is: Never commit to the right side of a Pull line. This is referring to the contents of
.git/remotes/origin (what you're using currently) file should currently have:
URL: git://anongit.freedesktop.org/git/whatever Pull: master:origin
This means that the current remote master is mapped to the local origin. When working on a branch where you'll be committing code, add a few more lines:
Push: master:master Pull: new-branch:new-branch-origin Push: new-branch:new-branch
The first says "map the remote new-branch to new-branch-origin in the local repository". The second says "when pushing code, push from new-branch locally into new-branch remotely". If you haven't set any
Push: lines, then git implies a
master:master mapping for pushes. Once we add our new-branch push line, we also have to add
master:master if you intend to ever commit to master.
Now, you're set up for committing and pushing the branch. There's one more trick to know about. When you do
git pull, it is now pulling the remote branches into what they're mapped to locally. So, the latest new-branch upstream code is in new-branch-origin. To merge it to your local new-branch code, do:
git pull . new-branch-origin
This means "merge the new-branch-origin code into the current working directory". Like the other
git pull command we mentioned, it will by default commit the merge, unless it runs into conflicts which aren't automatically resolvable.
And, to push, you do it just like when you were working on master.
git push origin