Some notes on git

3rd December 2012

This set of notes covers the main things that I think you need to know about working with git. It is not comprehensive and mainly serves as a reminder for myself.

Remotes

In a typical open source workflow using GitHub or BitBucket, you would fork the main repository into your own and then clone that copy to your local computer:

    git clone git@github.com:akrabat/joind.in.git

You then need to connect your local repository to the main repository. By convention, the main repository is known as upstream:

    cd joind.in
    git remote add upstream git://github.com/joindin/joind.in.git

To sync your local repository with upstream and update your copy back on BitBucket/GitHub:

    git checkout master
    git fetch upstream
    git merge --ff-only upstream/master
    git push origin

If the merge fails, then something bad has happened and you’ll need Google! One option is to force your master to match upstream using git reset --hard upstream/master and then git push --force origin.

Branching

The main branch in the repository is called master. Never ever code directly on master. Always create a branch and code on that and then merge back to master when complete.

(Note that the push command is also used throughout this document to sync your local repository with your remote on BitBucket/GitHub. If don't want to publish, then don't push.)

Create a branch:

    git checkout -b my-branch-name
    git push origin my-branch-name

List all branches:

    git branch -v

To list all remote branches too use the -a switch:

    git branch -v -a

Change from one branch to another:

    git checkout another-branch-name

Delete a branch:

    git branch -D my-branch-name
    git push origin :my-branch-name

Rebase master onto a branch:

If lots of people are working on the project, then master has probably changed a lot since you started. It’s going to make your merge back to master much easier if you update your branch so that all your changes appear to be after all the changes on master. This is known as rebasing:

    git fetch upstream
    git checkout my-branch-name
    git rebase -f upstream/master
    git push -f origin my-branch

Bring in a remote branch to your local repository:

If you want to work with a branch that’s on a remote repository, then you need to create your own tracking branch:

    git fetch upstream
    git checkout master
    git branch -t upstream/remote-branch-name
    git push origin remote-branch-name

The name of your local branch will match the remote branch name.

Sync remote branch with local one

    git fetch upstream
    git checkout remote-branch-name
    git merge upstream/remote-branch-name
    git push origin

Committing

To commit a change:

To commit a change, you first need to stage the files that you want to commit to the index:

    git add filename

You can then commit:

    git commit -m "my commit message"
    git push origin

Note that if you change a filename after adding to the index, then the change will not be committed unless your git add the file again. For information about a good commit message, read A Note About Git Commit Messages by Tim Pope.

Merging

Merge a branch into master:

    git checkout master
    git fetch upstream && git merge --ff-only upstream/master
    git merge --no-ff my-branch-name

Note that you don’t need to commit anything. You do need push though:

    git push

If there were conflicts, then you need to resolve them by editing the files appropriately (look for <<<<<<<). At this point, you do need commit your changes:

    git add .
    git commit
    git push

Back out a conflicted merge:

    git reset --hard HEAD

Working with your repository

To find out what’s happened:

    git reflog -10

This provides a list of the last 10 things that you’ve done on this repository across all branches.

    git log --oneline -10

This provides a list of the last 10 commits that’s happened on this branch

In both cases, the first column contains the commit hash reference that uniquely identifies each commit.

To find out current commit:

    git log -1

To undo all working changes

    git checkout .

To revert a commit:

Find the commit that you want to go back to via log or reflog:

    git reset --hard abcdef0

You can also use the ‘HEAD’ number from reflog:

    git reset --hard HEAD@{1}

To amend last commit message:

    git commit --amend -m "New commit message"

It’s best to do this before pushing. If you have pushed, then you need to force push using git push --force and expect to get lots of hassle from your co-workers.

Differences

To find out the differences between your edits and the last commit:

All files:

    git diff

One file:

    git diff -- my-filename

To find out the differences between current branch and master:

    git diff master..HEAD my-filename

To find out the differences between local master and origin’s master:

    git diff HEAD...origin/master

Some git aliases

Aliases provide a way to create new git commands and are usually used for creating shortcuts:

Type these from the command line:

    $ git config --global alias.st status
    $ git config --global alias.staged 'diff --staged'
    $ git config --global alias.unstage 'reset HEAD --'
    $ git config --global alias.last 'log -1 HEAD'

This gives you some new git commands:

  • git st => view current status
  • git staged => view the diff of what is currently staged
  • git unstage filename => Remove filename from the staging area
  • git last => view last commit

Git export from BitBucket

29th November 2012

One of the consequences of moving to git is that I needed to get our deployment scripts working with it. The first requirement was an equivalent to svn export. This is simple enough, but, as usual, I wanted to save myself some effort and created a script that wrapped it up for me.

git-export.sh:

#!/bin/sh
 
#
# USAGE: export.sh REPO_NAME BRANCH_NAME [DIRECTORY_NAME] [BITBUCKET_ACCOUNT_NAME]
#
#
 
BITBUCKET_ACCOUNT_NAME=akrabat
GITROOT=git@bitbucket.org:${BITBUCKET_ACCOUNT_NAME}
 
if [ -z "$1" ]; then
    echo "Usage is git-export.sh {repo_name} [{branch_name = master}] [{dir_name = repo_name}]"
    exit 1
fi
 
 
REPO_NAME=$1
BRANCH_NAME=$2
THIS_DIR=$3
 
if [ -z "$BRANCH_NAME" ]; then
    BRANCH_NAME=master
fi
if [ -z "$THIS_DIR" ]; then
    THIS_DIR=$REPO_NAME
fi
 
if [ -d "$THIS_DIR" ]; then
    echo "Error: directory '$THIS_DIR' already exists."
    exit 1
fi
 
echo "Exporting $BRANCH_NAME branch of $REPO_NAME into ./$THIS_DIR..."
mkdir $THIS_DIR
git archive --format=tar --remote=$GITROOT/$REPO_NAME $BRANCH_NAME | tar -xf - -C $THIS_DIR
 
echo "Done"
echo ""

Not the most complicated script in the world, but I never need to think about it again and it's easy to call from Phing and other deployment scripts and doesn't require a local clone to exist.

Migrating to BitBucket from Subversion

26th November 2012

I've recently started the process of moving all of my Subversion repositories to BitBucket. BitBucket is my preferred git hosting supplier as its pricing structure suits me much better; that is, I have lots of private repositories and GitHub is too expensive for my case!

As I needed to migrate over one hundred repositories from Subversion to BitBucket, I automated it via a simple shell script. Rather usefully, BitBucket has an API that let me create the repository using curl and then use git to do the rest.

I've put this script here in the hope that it may be useful to someone else. It's certainly been useful to me!

#!/bin/sh
 
# Inspired by :
# * http://blogs.atlassian.com/2012/01/moving-confluence-from-subversion-to-git/
# * http://john.albin.net/git/git-svn-migrate
# * http://blog.woobling.org/2009/06/git-svn-abandon.html
# * http://stackoverflow.com/questions/10637378/how-do-i-convert-a-bare-git-repository-into-a-normal-one-in-place
 
# usage: svn2git.sh REPO
# NOTE that we assume that REPO lives underneath SVN_URL and is a standard subversion layout
 
# SET THESE CORRECTLY!
USERNAME=my_bitbucket_username
PASSWORD=my_bitbucket_password
AUTHORS_TEXT_FILE=~/authors.txt
SVN_URL=http://svn.example.com/svn
 
 
REPO=$1
if [ -z "$REPO" ]; then
    echo ""
    echo "USAGE: svn2git.sh {repo} [{delete_repo_first =1|0}]"
    echo ""
    exit;
fi
DELETE_REPO_FIRST=$2
if [ -z "$DELETE_REPO_FIRST" ]; then
    DELETE_REPO_FIRST=0
fi
 
BASE_DIR=/tmp
TEMP_REPO=${REPO}_tmp
BARE_REPO=${REPO}.git
 
echo ""
if [ $DELETE_REPO_FIRST -eq 1 ]; then
    echo "Delete repository on BitBucket first"
    curl -L --silent -X DELETE -u ${USERNAME}:${PASSWORD} https://api.bitbucket.org/1.0/repositories/${USERNAME}/${REPO}
fi
echo "Create repository on BitBucket"
r=`curl -L --silent -X POST -u ${USERNAME}:${PASSWORD} https://api.bitbucket.org/1.0/repositories/ -d name=${REPO} -d scm=git`
t=${r:0:11}
if [ "${r:0:11}" = "Bad Request" ]; then
    echo ${r}
    exit 1
fi
 
echo ""
echo "Clone from subversion into temporary repository"
cd ${BASE_DIR}
git svn clone --stdlayout --no-metadata \
 -A ${AUTHORS_TEXT_FILE} \
 ${SVN_URL}/${REPO}/ ${TEMP_REPO}
 
if [ "$?" -ne "0" ]; then
    echo "ERROR: Failed to convert $REPO from subversion to git"
    exit 1
fi
 
echo ""
echo "Create bare repository"
git init --bare ${BARE_REPO}
cd ${BASE_DIR}/${BARE_REPO}
git symbolic-ref HEAD refs/heads/trunk
 
echo ""
echo "Push temporary repository into bare one"
cd ${BASE_DIR}/${TEMP_REPO}
git remote add bare ${BASE_DIR}/${BARE_REPO}
git config remote.bare.push 'refs/remotes/*:refs/heads/*'
git push bare
 
echo ""
echo "Rename 'trunk' to 'master' as that's the git norm"
cd ${BASE_DIR}/${BARE_REPO}
git branch -m trunk master
 
 
echo ""
echo "Clean up branches and tags"
git for-each-ref --format='%(refname)' refs/heads/tags |
cut -d / -f 4 |
while read ref
do
  git tag "$ref" "refs/heads/tags/$ref";
  git branch -D "tags/$ref";
done
 
echo ""
echo "Convert to 'normal' repository from bare"
mkdir .git
mv * .git
git config --local --bool core.bare false
 
 
echo ""
echo "Push branches & tags to BitBucket"
git remote add upstream git@bitbucket.org:${USERNAME}/${REPO}
git push --all upstream
git push --tags upstream
 
 
echo ""
echo "Clean up"
cd ${BASE_DIR}
rm -rf ${BARE_REPO}
rm -rf ${TEMP_REPO}
 
echo ""
echo "All done"

Note that at the top there's this section:

# SET THESE CORRECTLY!
USERNAME=my_bitbucket_username
PASSWORD=my_bitbucket_password
AUTHORS_TEXT_FILE=~/authors.txt
SVN_URL=http://svn.example.com/svn

You need to put your information here if you want to use this script :)

The authors.txt file is a simple text file that maps Subversion user names to git names and emails. It should look something like this:

rob = Rob Allen <rob@akrabat.com>
jsmith = John Smith <jsmith@example.com>

To use this script, I simply call:

svn2bitbucket.sh reponame

and it does its thing. Note that it's not especially fast for a repository that has lots of history. You can also do svn2bitbucket.sh reponame 1 which will delete the repository on BitBucket first which allows you to re-run the transfer. Note that if you do this, then you will lose any commits you independently pushed to the git repository!

Preparing for the ZF2 Tutorial at PHPNW12

1st October 2012

If you are coming to the ZF2 tutorial at PHPNW12, then you will get the best out of it if you do a little preparation before you arrive.

Laptop

You should bring a laptop with the following working on it:

  • A web server (preferably Apache) running PHP 5.3.3 or higher
  • A working MySQL server along with an administration tool such as phpMyAdmin
  • A text editor or IDE that you're comfortable coding with

Vhost set up

You should set up the following directory structure somewhere that Apache can access:

	phpnw12/
          albums/
          ex1/
          ex2/
          ex3/
          ex4/
          skeleton/

Set up 6 virtual-hosts so that you can access the public folder within each directory like this:

URL DocumentRoot
http://albums.localhost /path/to/phpnw12/albums/public
http://ex1.localhost /path/to/phpnw12/ex1/public
http://ex2.localhost /path/to/phpnw12/ex2/public
http://ex3.localhost /path/to/phpnw12/ex3/public
http://ex4.localhost /path/to/phpnw12/ex4/public
http://skeleton.localhost /path/to/phpnw12/skeleton/public

Place a working copy of the ZendSkeletonApplication in /path/to/phpnw12/skeleton.

Place a working copy of the ZF2 albums tutorial in /path/to/phpnw12/albums. Follow the tutorial in order to get it working.

Once you have both the skeleton and the albums application working, then you are ready for the tutorial and we'll see you on Friday!

Introducing Avalance: A landslide presentations theme

4th September 2012

Evan and I are giving a joint Zend Framework 2 tutorial at the PHPNW12 and ZendCon conferences this year and, consequently, we needed a way to create the presentation collaboratively, ideally using a text based system which we could store in git.

There are plenty of HTML5/JS based solutions out there and we decided on landslide which is a python based markdown -> HTML presentation system. However, I really didn't like any of the default themes and Evan wasn't impressed with the JavaScript, so we wrote Avalanche, a new theme for landslide. By default, it's very clean, which is intentional as we expect people will want to override as they create their own look.

We've created two examples:

Avalanche: white exampleAvalanche: black example

(click on the image and then press 'h', for help)

As landslide is a build system, you create the presentation in markdown like this and then "render" it to html using a config file to set up useful parameters which control the build. You then type landslide presentation.cfg to render the html file. One thing that we like about landslide is that there is an option to embed everything into the one file. This means that the single html file has everything you need for your presentation. However, if your presentation is very image-heavy, this can cause problems, so we're working on a solution that embeds everything but the images. Also, the slides are written to be 1024px by 768px, but will scale using CSS to whatever size is required, which is useful.

We think Avalanche provides an excellent starting point for your creating your own style for presentation slides. If you're interested in it, please visit the github page and have a play. When you find bugs, please report them and consider including a patch :)