June 2010 Archives

git for me

I use git for my own projects, and the day-by-day stuff does not involve much messing with remote repositories. Anyway, it's a fantastic tool to keep track of the code and avoid losing anything.

getting started (installation apart!)

In the directory for the new project, just init:

  git init
  Initialized empty Git repository in ...

At this point, there's nothing tracked, so after editing some files you can add whatever you want to track, either by filename or by directory name. The fastest thing to do is probably:

  git add .

Note that at this point nothing has actually been put inside the tracking system - you'll have to commit your change:

  git commit

You'll be asked to enter a commit log line and possibly something more, but it's higly probable that at this stage you'll want to always use the same message like this:

  git commit -m 'initial import'

We'll talk a bit more about commit in seeking and committing below.

excluding stuff

When there are files in your directory that you don't want to track (e.g. some tar archive), git will keep telling you about them. You can turn this whining off by adding elements to the .git/info/exclude file:

  echo '*.tar.gz' >> .git/info/exclude
  echo '*.swp'    >> .git/info/exclude

seeking and committing

To see what's going on:

  git status

or, to have an idea of the changes:

  git diff --color

In the last case, you can also provide some file names to restrict the diffing:

  git diff --color path/to/file

After you have an idea of the log line to write, you can commit:

  git commit path/to/file -m "your log line here"

Without the -m parameter you enter interactive mode to provide a log line and some more log details.

To commit all files just use -a instead of passing the file name.

If you want to select all files in the commit, you can first add them:

  git add file1
  ...
  git add file2
  git add path/to/file3

and then just commit:

  git commit -m 'some complex commit'

tag

Sometimes it's useful to set a tag in certain stable conditions. This can be done automatically by Dist::Zilla, for example.

To add a tag at the current HEAD:

  git tag name-of-tag

If you want to put a tag in the past, just find out the SHA1 digest of the commit with the log subcommand:

  git log
  ...
  commit 1a9529d56959f7ad9287faaf3b143649516fa63f
  Author: Flavio Poletti <flavio@polettix.it>
  Date:   Mon May 31 17:36:22 2010 +0200

     turned complete-check in pure Perl
  ...

and use that:

  git tag name-of-tag 1a9529d56959f7ad9287faaf3b143649516fa63f

You don't usually have to use the full SHA1, as long as git can be sure of what you're referring to. Usually some 8-10 characters suffice:

  git tag name-of-tag 1a9529d569

getting a previous version of a file

If you want to get some previous version of a file, you first have to seek the right version. You can either get a commit or a tag, then:

  git checkout name-of-tag path/to/file

You can use the commit SHA1 instead of name-of-tag. If you want to refer to some near version, you can refer to HEAD:

  git checkout HEAD^ path/to/file   # one step back
  git checkout HEAD^^ path/to/file  # HEAD - 2
  git checkout HEAD~5 path/to/file  # HEAD - 5

branching

If you want to make some experiments without messing up with the main trunk you can create a branch. To fork at the current point, you can just create the branch and switch to it:

  git branch branch-name-here
  git checkout branch-name-here

To do these two operations in a single step (which is what you want 99% of the times) just add -b to checkout:

  git checkout -b branch-name-here

You can also branch somewhere in the past, i.e. at a tag or specific SHA1:

  git checkout -b branch-name-here name-of-tag

When you're happy with the branch status and your modifications, you can merge them back in the main trunk:

  git checkout master
  git merge branch-name-here

At this point, you can also get rid of the branch if you want:

  git branch -d branch-name-here

If you don't want to get all the changes in a branch, but just some of them, you can cherry-pick instead. Cherry-picking means picking exactly the commits that you want, so you have to know them beforehand (e.g. via the log command, see example above in the tag section):

  git cherry-pick 1a9529d569
  git cherry-pick c8878c0b

In this case, the master branch and the new branch will not be aligned, so git will complain if you try to get rid of the new branch. If you're positive to get rid of it anyway, you can use -D instead of -d:

  git branch -D branch-name-here

Note that there's no turning back, so be sure that you can actually get rid of the branch.

aggregating commits

When you're merging a set of changes from a branch (either merge-ing or cherry-pick-ing) you might want to aggregate them all under a single commit, i.e. you might not be interested in the whole history for the change (which might include lots of try-and-error commits) but you would rather set a single, comprehensive log message.

In this case, what to do depends on the commits you're interested into. If you want to do a merge from some branch-name-here, then you can use the --no-ff switch (which is explained perfectly here):

  git merge --no-ff branch-name-here -m 'Added frozzbuzz feature, yay!'

On the other hand, if you just want to cherry-pick some modifications, --no-commit will be of help:

  git cherry-pick --no-commit 1a9529d569
  git cherry-pick --no-commit c8878c0b
  git commit -a -m 'Added long-wanted frozzbuzz feature, yay!'

In cherry-pick this option can be abbreviated to -n. Beware that merge has a --no-commit option too, but it does not seem to work (or it does not do what one would expect, anyway), so stick to --no-ff in the merge case.

[Edit] I know that there is another method that is more streamline and clean, but I just don't remember where I did see it at the moment!

remote repositories

It's not unusual that I have to use more than one computer - I have two at work and at least another one at home. You could argue that using three laptops is a bit weird, but this is life.

One thing that I tend to do quite early is duplicate a repository that I create locally on one of the three computers in a remote server, in order to be able to work on it from other computers when needed. For this, I found a blog post that is very useful, I'll try to mirror some of the contents here.

We're assuming that you already have your local repository local:/home/foo/project and you have ssh access to the remote server. First of all create a bare repository in the remote server:

remote:/home/bar$ mkdir project
remote:/home/bar$ cd project
remote:/home/bar/project$ git init --bare

Now you can perform other initialisation stuff, e.g. permissions, configurations, etc.

Back on the local computer:

local:/home/foo/project$ git remote add origin ssh://user@remote:/home/bar/project
local:/home/foo/project$ git push origin master

Of course you can also push whatever other branch you're interested into replicating in the remote server. At this point you need to set the branch as tracking the remote one, which can be done in several ways but I always stick to the suggestion in the link above:

local:/home/foo/project$ git checkout origin/master
local:/home/foo/project$ git branch -f master origin/master
local:/home/foo/project$ git checkout master

Again, repeat the first two steps for whatever branch you need. Done!

Some time ago I read that there should be also some other method, but one is sufficient and I don't find where!

To Depend Or Not To Depend

When starting an application, I always strive to reduce dependencies as much as possible, especially if I know that there's some way that does not take me too much time to go.

It turns out that I chose one of the worst possible examples. Check out the comments below and - more importantly - check out lib documentation!

This happens when I use lib, for example to add ../lib to @INC. For my own stuff I usually head towards Path::Class:

use Path::Class qw( file );
use lib file(__FILE__)->parent()->parent()->subdir('lib')->stringify();

but when I want to reduce dependencies I revert to File::Spec::Functions (which is CORE since perl 5.5.4 and now part of PathTools):

use File::Spec::Functions qw( splitpath splitdir catdir catpath );
my $path;
BEGIN {
   my ($volume, $directories) = splitpath(__FILE__);
   my @dirs = splitdir($directories);
   push @dirs, qw( .. lib ); # ../lib - naive but effective
   $path = catpath($volume, catdir(@dirs));
}
use lib $path;

Alas, how I miss the Path::Class solution!

About Flavio Poletti

user-pic I blog about Perl.