Date: Mon, 15 Aug 2005 12:17:41 -0700 From: tony.luck@intel.com Subject: Some tutorial text (was git/cogito workshop/bof at linuxconf au?) Abstract: In this article, Tony Luck discusses how he uses GIT as a Linux subsystem maintainer. Here's something that I've been putting together on how I'm using GIT as a Linux subsystem maintainer. -Tony Last updated w.r.t. GIT 0.99.9f Linux subsystem maintenance using GIT ------------------------------------- My requirements here are to be able to create two public trees: 1) A "test" tree into which patches are initially placed so that they can get some exposure when integrated with other ongoing development. This tree is available to Andrew for pulling into -mm whenever he wants. 2) A "release" tree into which tested patches are moved for final sanity checking, and as a vehicle to send them upstream to Linus (by sending him a "please pull" request.) Note that the period of time that each patch spends in the "test" tree is dependent on the complexity of the change. Since GIT does not support cherry picking, it is not practical to simply apply all patches to the test tree and then pull to the release tree as that would leave trivial patches blocked in the test tree waiting for complex changes to accumulate enough test time to graduate. Back in the BitKeeper days I achieved this by creating small forests of temporary trees, one tree for each logical grouping of patches, and then pulling changes from these trees first to the test tree, and then to the release tree. At first I replicated this in GIT, but then I realised that I could so this far more efficiently using branches inside a single GIT repository. So here is the step-by-step guide how this all works for me. First create your work tree by cloning Linus's public tree: $ git clone \ master.kernel.org:/pub/scm/linux/kernel/git/torvalds/linux-2.6.git work Change directory into the cloned tree you just created $ cd work Set up a remotes file so that you can fetch the latest from Linus' master branch into a local branch named "linus": $ cat > .git/remotes/linus URL: master.kernel.org:/pub/scm/linux/kernel/git/torvalds/linux-2.6.git Pull: master:linus ^D and create the linus branch: $ git branch linus The "linus" branch will be used to track the upstream kernel. To update it, you simply run: $ git fetch linus you can do this frequently (and it should be safe to do so with pending work in your tree, but perhaps not if you are in mid-merge). If you need to keep track of other public trees, you can add remote branches for them too: $ git branch another $ cat > .git/remotes/another URL: ... insert URL here ... Pull: name-of-branch-in-this-remote-tree:another ^D and run: $ git fetch another Now create the branches in which you are going to work, these start out at the current tip of the linus branch. $ git branch test linus $ git branch release linus These can be easily kept up to date by merging from the "linus" branch: $ git checkout test && git merge "Auto-update from upstream" test linus $ git checkout release && git merge "Auto-update from upstream" release linus Set up so that you can push upstream to your public tree (you need to log-in to the remote system and create an empty tree there before the first push). $ cat > .git/remotes/mytree URL: master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux-2.6.git Push: release Push: test ^D and the push both the test and release trees using: $ git push mytree or push just one of the test and release branches using: $ git push mytree test or $ git push mytree release Now to apply some patches from the community. Think of a short snappy name for a branch to hold this patch (or related group of patches), and create a new branch from the current tip of the linus branch: $ git checkout -b speed-up-spinlocks linus Now you apply the patch(es), run some tests, and commit the change(s). If the patch is a multi-part series, then you should apply each as a separate commit to this branch. $ ... patch ... test ... commit [ ... patch ... test ... commit ]* When you are happy with the state of this change, you can pull it into the "test" branch in preparation to make it public: $ git checkout test && git merge "Pull speed-up-spinlock changes" test speed-up-spinlocks It is unlikely that you would have any conflicts here ... but you might if you spent a while on this step and had also pulled new versions from upstream. Some time later when enough time has passed and testing done, you can pull the same branch into the "release" tree ready to go upstream. This is where you see the value of keeping each patch (or patch series) in its own branch. It means that the patches can be moved into the "release" tree in any order. $ git checkout release && git merge "Pull speed-up-spinlock changes" release speed-up-spinlocks After a while, you will have a number of branches, and despite the well chosen names you picked for each of them, you may forget what they are for, or what status they are in. To get a reminder of what changes are in a specific branch, use: $ git-whatchanged branchname ^linus | git-shortlog To see whether it has already been merged into the test or release branches use: $ git-rev-list branchname ^test or $ git-rev-list branchname ^release [If this branch has not yet been merged you will see a set of SHA1 values for the commits, if it has been merged, then there will be no output] Once a patch completes the great cycle (moving from test to release, then pulled by Linus, and finally coming back into your local "linus" branch) the branch for this change is no longer needed. You detect this when the output from: $ git-rev-list branchname ^linus is empty. At this point the branch can be deleted: $ git branch -d branchname Some changes are so trivial that it is not necessary to create a separate branch and then merge into each of the test and release branches. For these changes, just apply directly to the "release" branch, and then merge that into the "test" branch. To create diffstat and shortlog summaries of changes to include in a "please pull" request to Linus you can use: $ git-whatchanged -p release ^linus | diffstat -p1 and $ git-whatchanged release ^linus | git-shortlog Here are some of the scripts that I use to simplify all this even further. ==== update script ==== # Update a branch in my GIT tree. If the branch to be updated # is "linus", then pull from kernel.org. Otherwise merge local # linus branch into test|release branch case "$1" in test|release) git checkout $1 && git merge "Auto-update from upstream" $1 linus ;; linus) before=$(cat .git/refs/heads/linus) git fetch linus after=$(cat .git/refs/heads/linus) if [ $before != $after ] then git-whatchanged $after ^$before | git-shortlog fi ;; *) echo "Usage: $0 linus|test|release" 1>&2 exit 1 ;; esac ==== merge script ==== # Merge a branch into either the test or release branch pname=$0 usage() { echo "Usage: $pname branch test|release" 1>&2 exit 1 } if [ ! -f .git/refs/heads/"$1" ] then echo "Can't see branch <$1>" 1>&2 usage fi case "$2" in test|release) if [ $(git-rev-list $1 ^$2 | wc -c) -eq 0 ] then echo $1 already merged into $2 1>&2 exit 1 fi git checkout $2 && git merge "Pull $1 into $2 branch" $2 $1 ;; *) usage ;; esac ==== status script ==== # report on status of my ia64 GIT tree gb=$(tput setab 2) rb=$(tput setab 1) restore=$(tput setab 9) if [ `git-rev-list release ^test | wc -c` -gt 0 ] then echo $rb Warning: commits in release that are not in test $restore git-whatchanged release ^test fi for branch in `ls .git/refs/heads` do if [ $branch = linus -o $branch = test -o $branch = release ] then continue fi echo -n $gb ======= $branch ====== $restore " " status= for ref in test release linus do if [ `git-rev-list $branch ^$ref | wc -c` -gt 0 ] then status=$status${ref:0:1} fi done case $status in trl) echo $rb Need to pull into test $restore ;; rl) echo "In test" ;; l) echo "Waiting for linus" ;; "") echo $rb All done $restore ;; *) echo $rb "<$status>" $restore ;; esac git-whatchanged $branch ^linus | git-shortlog done