Blog - /Tech
It's been over a month since I started using git. Time to share some of the more interesting things I've picked up from it.
One surprise was realizing that I actually like having multiple
branches share the same working directory. It's nice to be able to do
git checkout <name-of-branch> and immediately get either a particular
branch, tag, or even commit. One of the reasons for this change of
opinion is due to the excellent gitweb interface, which displays these
branches and tags in a nice-looking way.
One thing I would like to investigate is just how similar Mercurial is to git when it comes to the use of its SHA1 hashes for verification of a project's integrity. I don't recall reading anything about it in the Mercurial documentation, whereas git notes in its manpage, "[S]ince the SHA1 signature of a commit refers to the SHA1 signatures of the tree it is associated with and the signatures of the parent, a single named commit specifies uniquely a whole set of history, with full contents. You can't later fake any step of the way once you have the name of a commit."
I like being able to say git pull shared to get changes from the
shared public repo for my projects. To set this up, my per-project
.git/config file looks something like this:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "shared"]
url = ssh://repo.or.cz/srv/git/muse-el.git
fetch = +refs/heads/*:refs/remotes/shared/*
[branch "master"]
remote = shared
merge = refs/heads/master
If you were to add another location, just copy the last two stanzas and replace "shared" with your name for the location. The idea is to store all references for the new location into its own directory under refs/remotes.
One of the challenges with using Savannah and repo.or.cz for hosting projects is that they have no way (that I know of) to generate email messages when new commits are added. If you have access to a machine with cron (such as HCoop), however, there is a way of dealing with that. I call it the "push-me-pull-you" method, for lack of a less corny name.
The idea is to periodically pull from the public repo (call this the "staging" directory), and push into another repo on the same machine that has an update hook (call this the "target" directory). The staging directory has to be a working directory, but the target can be a bare repo (without a working directory) to save space. To make the target directory, clone from the staging directory with the "-s" option, so that you share objects with the staging directory.
To actually do the emailing of commits, do chmod +x hooks/update in
the target directory, and edit the contents to call some sort of email
commit script. It's probably possible to use the post-receive hook as
well, as in /usr/share/doc/git-core/contrib/hooks/post-receive-email
which comes with Debian, but I wanted to write my own.
The hook itself will look like this:
#!/bin/sh # # A hook script that sends commit notifications. # Called by git-receive-pack with arguments: refname sha1-old sha1-new # # To enable this hook, make this file executable by "chmod +x update". # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 <ref> <oldrev> <newrev>)" >&2 exit 1 fi if [ -z "$refname" ] || [ -z "$oldrev" ] || [ -z "$newrev" ]; then echo "Usage: $0 <ref> <oldrev> <newrev>" >&2 exit 1 fi # Send email for commit_id in $(git rev-list ${oldrev}..${newrev} | tac) ; do ~mwolson/scripts/email-git erc ${refname} ${commit_id} done # --- Finished exit 0
My email-git script is as follows.
#!/bin/bash # # Send commit notifications via email for git. # Parameters proj="$1" refname="$2" commit_id="$3" # Function to send email message function sendMail() { local from="$1" local recipients="$2" local commit_id="$3" local refname="${4##refs/heads/}" local summary=$(git show --pretty=format:%s ${commit_id} | head -n 1) git show --pretty=medium ${commit_id} | \ mail -a "From: ${from}" -s "[commit][${refname}] ${summary}" \ ${recipients} } # Determine where to send the messages # # Note: Unobfuscate your email addresses, rather than using "AT" # in real usage of this script. case $proj in erc) from_email=mwolson AT gnu.org to_email=erc-commit AT gnu.org ;; muse) from_email=mwolson AT gnu.org to_email=muse-el-logs AT gna.org ;; *) to_email= ;; esac # Send email [ -n "$to_email" ] && sendMail $from_email $to_email $commit_id $refname
I have cron set up to call this script every 10 minutes or so. A
custom-compiled version of git resides in ~mwolson/bin. The staging
directory has subdirectories which are named after each project, and
each is a checkout of that project.
#!/bin/bash # Sync git projects from various shared repos PATH=~mwolson/bin:"$PATH" # Place to prepare and mangle files before publishing DIST_DIR=~mwolson/dist-git # Process each config file and move to examples directory for i in $(find $DIST_DIR/staging -maxdepth 1 -mindepth 1 -type d); do # Pull the latest changes (cd $i && git pull shared >/dev/null 2>&1) # Push to another local repo in order to trigger hooks (cd $i && git push target >/dev/null 2>&1) done
This requires something like the following in each of the staging
directories in the .git/config file.
[remote "shared"]
url = http://repo.or.cz/r/muse-el.git
fetch = +refs/heads/master:refs/remotes/origin/master
[remote "target"]
url = ~mwolson/dist-git/target/muse.git
push = master