Overview

Git is a distributed vesioning control system.

References

Overviews

DONE Git vs. Mercurial blog entry: MacGyver vs. Bond

CLOSED: 2008-08-28 Thu 05:06

  • State "DONE" 2008-08-28 Thu 05:06
  • State "TODO" 2008-08-28 Thu 05:06

A nice, touchy-feely intro to the difference between Git and Mercurial. Despite the one-stop-shopping-appeal of Mercurial, I will go with my command-line-linux-philosophy-loving, blogs-with-ikiwiki heart and head into git-land.

Tutorials

TODO Rubinius git page Get going fast

  • State "TODO" 2008-08-28 Thu 05:06

TODO gittutorial The official intro ;), looks fairly readable…

  • State "TODO" 2008-08-28 Thu 05:11

TODO Everyday GIT With 20 Commands Or So Nicely broken down by goal

  • State "TODO" 2008-08-28 Thu 05:06

TODO Git Community Book Open source wiki/howto website

  • State "TODO" 2008-08-28 Thu 05:06

Interface with SVN

TODO Diving into git blog entry Importing the Subversion history

  • State "TODO" 2008-08-28 Thu 05:06

TODO Git - SVN Crash Course ?

  • State "TODO" 2008-08-28 Thu 05:06

TODO An introduction to git-svn for Subversion/SVK users and deserters

  • State "TODO" 2008-08-28 Thu 06:09

Lots of political digression, but also lots of useful info. Your call.

Enforcing development policies

TODO Push hooks

  • State "TODO" 2008-08-28 Thu 05:16

Push hooks

DONE Pre commit hooks

CLOSED: 2008-09-02 Tue 08:48

  • State "DONE" 2008-09-02 Tue 08:48
  • State "TODO" 2008-09-02 Tue 08:46

Basically, put whatever you want in .git/hooks/pre-commit. For example, I added:

./.git/hooks/pre-commit-make-check || exit 1

right after the initial comments with

$ cat .git/hooks/pre-commit-make-check
make check

Don't forget to chmod 744 both scripts to make them executible. Also note that these scripts are run from the base repo directory, which is why I had to include the relative path to pre-commit-make-check, and didn't need cd ../../ in the check script.

DONE Rebase Considered Harmful

CLOSED: 2008-08-28 Thu 05:27

  • State "DONE" 2008-08-28 Thu 05:27
  • State "TODO" 2008-08-28 Thu 05:18

A “keep the warts” vs. “stay true to history” monologue. Advocates against rebasing public repos because (quotes from the git-rebase manpage):

"When you rebase a branch, you are changing its history in a way that will cause problems for anyone who already has a copy of the branch in their repository and tries to pull updates from you."

Personally, I feel like a middle ground, where private mini-branches get a little constructive history tweaking is a good thing. Noone cares about typos in comments, and it adds noise to the development signal, so fix those commits in your private branch. Once you go public though, don't mess with the history, since it would be even more confusing to have conflicting histories in seperate public repositories. Anything serious enough to require a altering the author field should probably not be changed.

My thoughts are shared by others, for example commenter #7 Jing Xue. However, commenter #8 links to Synchronizing development: rebase vs. pull+merge, GIT vs. Mercurial, where oddbod points out that the real problem when “mid-level”, public repos rebase to avoid sending known-bugged patches upstream. He says they avoid the obviously better solution of sending up the bad patch with a patch-patch hard on its heels, which would avoid rebasing a public repo, and still preserve the spirit and authorship of the changes.

  • Aside: rebase etymology

    I just realized that “rebasing” is attaching your branch to a different base point on the tree. Afterward, it looks like you made all of your adjustments right now, not in parallel with a bunch of other fixes on the main branch.

Setting a description for your repo

Edit .git/description.

Peripherals

GitTorrent

People are indeed working on the obvious protocol.

The git-* to git * transition

The transition occured between git 1.5.4 and 1.6.0. See the 1.6.0 release notes for details.

Case studies

Gitting chem_web, my first git project

Repository creation and setup

From the Git Community Book.

Install

$ apt-get install git-core gitk

Setup

From the Git Community Book.

By default that file is ~/.gitconfig and the contents will then look like this:

[user]
        name = W. Trevor King
        email = wking at drexel dot edu

Initialize repository

From the Git Community Book.

$ cd ~/rsrch/chem_web
$ git init

Add some files to the repository

From the Git Community Book.

$ git add README *.py docs templates
$ git status

Oops, git add adds all of a directory's contents too. Remove some of the automatically generated files. git rm removes a file from both the working directory and the index. I wish I could just remove from the index, but I'm not sure how yet. Ah well, the reason I want to remove them is that they are automatically generated ;).

$ git rm -f docs/*.pdf
...

a bunch of other work while I clean up the mess I've made. Keep going until git status looks right.

Commit the code to the master branch

From the Git Community Book.

$ git commit

Or you can automatically add any changed tracked-files with

$ git commit -a

Creating the development branch

From the Git Community Book.

My workflow will be in two branches:

  • master, the current stable/working release
  • dev, the feature development branch

I will develop new code in dev until I am happy with its performance, and then merge back into the master branch. Old bugs will be fixed in the master branch, and then cherry-picked into the dev branch. That's the current plan anyway ;). Create the dev branch with

$ git branch dev

Changing branches and coding

From the Git Community Book.

List branches with

$ git branch

Change to dev with

$ git checkout dev

Now add that cool new feature, NFPA diamonds for the cabinets :p. When you feel it is appropriate, commit your changes in the local branch with

$ git commit -a

Correcting private commit mistakes

From the Git Community Book.

Oops, I forgot X or made a typo in my commit message. My dev branch is not public, so go ahead and fix the mistake. Then run

$ git commit --amend

This may leave some danglers

$ git fsck
dangling blob 03cabac5fa426ca8df4dff1fdb2596b68d2f4c5a
dangling blob 87488a0b4ea976127d6b9171ef6f10941a1dd74e
...

which you can clean up with

$ git prune

Bring the master branch up to speed

From the Git Community Book.

$ git checkout master
$ git merge dev

You may have to deal with a conflicting merge.

Delete a branch

From the Git Community Book.

$ git branch -d dev

The -d ensures the changes have already been merged back into the current branch. If you want to kill the brach without merging use

$ git branch -D dev

Repository maitenance

From the Git Community Book.

Recompress (to keep the compression most effective)

$ git gc

Check repository consitency

$ git fsck

Both should be run manually from time to time.

Working over a network

From the Git Community Book.

  • Grab a remote repository

    A remote friend wants to work on your code, or more likely, you're at home and you want to work on your code at work. Grab the repo using ssh with

    $ git clone ssh://wking@loki/~/rsrch/chem_inventory ci
    

    using the

    ssh://[user@]host.xz/~/path/to/repo.git
    

    git URL notation listed in man git-clone.

  • Possibly remove the reference to the remote repository

    If you're decomissioning the remote repository, you can remove it from the current one with

    $ git remote rm <name>
    
  • Work on the repository

    Nothing changes here.

  • Get your remote partner to pull your changes back upstream
    $ git remote add bob /home/bob/repo
    $ git fetch remote
    $ git log -p dev remotes/bob/master
    $ git merge remotes/bob/master
    

    and possibly

    $ git remote rm bob
    

Gitting sawsim, migration from svn

Install git

$ apt-get install git-core git-svn gitk

Repository migration from svn

Following Jon Maddox at the Simplistic Complexity blog.

  • Create the decoy directory

    git svn keeps some info about the svn repository, to make it easier to keep the two in sync. However we are not interested in staying in sync, we just want to move to Git. Create a decoy Git version of the repo with

    $ mkdir sawsim.svn
    $ cd sawsim.svn
    $ git svn init svn://abax.physics.drexel.edu/sawsim/trunk/
    $ cat >> .git/config <<EOF
    [svn]
      authorsfile = users.txt
    EOF
    $ cat > users.txt <<EOF
    wking = W. Trevor King <wking at drexel dot edu>
    EOF
    $ git svn fetch
    

    Jon Maddox suggests using

    $ git config svn.authorsfile ~/Desktop/users.txt
    

    instead of my manual .git/config editing, but my stock Debian git (1.4.4.4) doesn't seem to have git config. No big deal.

    Check that this worked and translated your users correctly with

    $ git log
    
    • Warning: here documents

      Some shells might not like my here document Bash syntax. In that case, write the files another way.

  • Create the clean directory

    When we clone the decoy directory, all the svn junk gets left behind.

    First, leave the sawsim.svn directory

    $ cd ..
    

    and clone like you normally would

    $ git clone sawsim.svn sawsim
    

    You don't need the [svn] stuff in .git/config anymore either. I don't remember if they came over on their own or not…

    I removed the origin information with

    $ rm .git/remotes/origin
    

    Although a better approach would probably be

    $ git remote rm origin
    

Setup

  • gitignore
    From man gitignore

    The sawsim code is mostly in a single noweb file. Extracting all the source from the file creates lot of indirectly versioned clutter. To avoid having all your git status calls swamped with untracked files, Just add the filenames (globbing allowed) to .gitignore.

  • Push hooks
    TODO

Making your repository public/Publishing your repository

From the Git Community Book

To distribute your repo via HTTP (easier on someone else's server).

user@devel$ ssh server
server$ cd ~/public_html
server$ git clone --bare ssh://user@devel/~/path/to/repo.git repo.git
server$ cd repo.git
server$ touch git-daemon-export-ok
server$ git --bare update-server-info
server$ chmod a+x hooks/post-update

Others can clone or pull from your URL with

anon$ git clone http://server/~you/repo.git

From the Git Community Book

Once you've set up your repo, you can push changes to it with ssh

devel$ git push ssh://user@server/~/public_html/repo.git master

To save typing, add

[remote "public"]
    url = ssh://user@server/~/public_html/repo.git

to .git/config, after which you can push with

devel$ git push public master

Note that you may need to copy .git/description over by hand. I wrote up a git-publish script to automate this.

Gitting comedi, using Git as a frontend for CVS

From Takis blog

Repository creation and setup

This is quite similar to svn migration.

  • Create the decoy directory
    $ mkdir comedi.cvs
    $ cd comedi.cvs
    $ #cvs -d :pserver:anonymous@cvs.comedi.org:/cvs/comedi login
    $ git cvsimport -p x -v -d :pserver:anonymous@cvs.comedi.org:/cvs/comedi comedi
    

    The login line may not be necessary for other CVS projects. Arguments to git cvsimport:

    OptionMeaning
    -p xPass -x to cvsps, which ignores any ~/.cvsps/cvsps.cache file
    -vVerbose
    -d ...From http://www.comedi.org/download.html
  • Create the clean directory
    $ cd ..
    $ git clone comedi.cvs comedi
    

Do your work like normal in comedi

Update with the latest cvs

$ cd comedi.cvs
$ git cvsimport -p x -v -d :pserver:anonymous@cvs.comedi.org:/cvs/comedi comedi
$ cd ../comedi
$ git pull

You may have to deal with a conflicting merge.

Create a patch against the cvs source

$ git format-patch origin

Other useful examples

Purge all knowledge of a given file or subdir from history

For example, in case you accidentally started versioning /etc/shadow, or some other document containing sensative information.

$ git filter-branch --index-filter 'git rm -rf --cached --ignore-unmatch /etc/shadow'
$ git reflog expire --expire=0 --all
$ git prune
$ git repack -adf

Rewrite the repository to look as if foodir/ had been its project root

Discarding all other history

$ git filter-branch --subdirectory-filter foodir -- --all

Merge a single file (with history) into another repository

Merge the file myfile (with history) from REPO_A into REPO_B.

Create a filtered version of REPO_A:

$ git clone REPO_A work-a
$ cd work-a
$ SPELL='git ls-tree -r --name-only --full-tree "$GIT_COMMIT" | grep -v "[.]git/\|myfile" | grep -v "^[.]git\$" | tr "\n" "\0" | xargs -0 git rm --cached -r --ignore-unmatch'
$ git filter-branch --prune-empty --index-filter "$SPELL" -- --all

Alter the grep stuff as you need. The goal is to remove anything you want to keep from the listing produced by SPELL. If the spell becomes too complicated, you can move it to a standalone script:

$ cat /tmp/spell.sh
#!/bin/bash
git ls-tree -r …
…
$ git filter-branch --prune-empty --index-filter /tmp/spell.sh -- --all

Merge it into REPO_B:

$ cd ..
$ git clone REPO_B work-b
$ git remote add myfile-repo ../work-a
$ git pull myfile-repo master
$ git remote rm myfile-repo

Clean up:

$ cd ..
$ rm -rf work-a REPO_B
$ mv work-b REPO_B

Thanks to Greg Bayer and Peter Hillerström.

Cherry pick a commit from another repository

First, give the remote repository a nickname (optional)

$ git remote add bob /home/bob/myrepo

Then fetch the remote repo

$ git fetch bob

You can merge all the changes Bob made to his master branch with

$ git pull . remotes/bob/master

Or cherry-pick a particular one, e.g. commit 1d8fb1fe41dfc1b1eb38c7b5d574577c4b341c58

$ git cherry-pick 1d8fb1fe41dfc1b1eb38c7b5d574577c4b341c58

When a particular remote repo no longer contains interesting material, you can purge the fetched tags and objects with

$ git remote rm bob
$ git remote prune bob

Restore a deleted file to match an earlier version

Source: stackoverflow.

Find the commit that deleted the file in question

$ git rev-list -n 1 HEAD -- <file_path>

or

$ git log --diff-filter=D --summary

Then checkout the file

$ git checkout <deleting_commit>^ -- <file_path>

Move master.HEAD to a different location

Sometimes you screw up and want to drop the thread you've been working on. Place the HEAD of the master branch on commit XYZ with

$ git checkout XYZ
$ git branch -D master
$ git branch master XYZ
$ git checkout master

Note that this may remove some information from your .git/config's [branch "master"] entry. You should save your earlier .git/config before doing it, and make any appropriate corrections afterwards.

If you're just trying to roll back a branch a few commits, skip the above and try

$ git reset --hard HEAD^

or add however many ^ you need to get back to the last good commit.

Rewinding bare repositories

If you're trying to roll back a few commits in a bare repository you'll need to use:

$ git update-ref HEAD HEAD^

If you're in another branch, use:

$ git update-ref refs/heads/branch-name branch-name^

And if you're going back further, use the sha1:

$ git update-ref refs/heads/branch-name a12d48e2

Git submodules, nesting/tracking sub-repositories.

This is a nice way of grouping associated projects. The submodules are included as stand-alone repositories, and the super-project has pointers picking out a particular revision of each submoduel. See the related Git subtrees for an alternate approach.

Setup a super-module

From the Git Book

$ git submodule add ~/path/to/submoduleX $submoduleX

Warning: Do not use local URLs here if you plan to publish your supermodule. git submodule add

  • clones the submodule under the current directory and by default checks out the master branch.
  • adds the submodule's clone path to the gitmodules file and adds this file to the index, ready to be committed.
  • adds the submodule's current commit ID to the index, ready to be committed.
    $ git submodule init
    

Cloning a super-module

From the Git Wiki

Grab the content living in the super module itself

$ git clone ~/subtut/public/super

See how the submodules look

$ git submodule status
-d266b9873ad50488163457f025db7cdd9683d88b a
...

Add submodule repository URLs to .git/config

$ git submodule init

Check that they're there if you like

$ git config -l
...
submodule.a.url=/home/moses/subtut/public/a/.git

Now check out the referenced submodules

$ git submodule update

Changing submodules from supermodules

From the Git Wiki

To update a submodule, remember that it's just a checked-out branch in your supermodule. Create and publish the change using the usual method:

$ cd a
a$ git branch
* (no branch)
  master
a$ git checkout master
a$ echo "adding a line again" >> a.txt
a$ git commit -a -m "Updated the submodule from within the superproject."
a$ git push
$ cd ..

Now point the supermodule at the new commit. Warning: don't use git add a/ or git will think you mean to add the contents of the directory. For submodule updates, you must leave off the trailing slash.

$ git add a
$ git commit -m "Updated submodule a."
$ git show
...
diff --git a/a b/a
index d266b98..261dfac 160000
--- a/a
+++ b/a
@@ -1 +1 @@
-Subproject commit d266b9873ad50488163457f025db7cdd9683d88b
+Subproject commit 261dfac35cb99d380eb966e102c1197139f7fa24

Take a look at the submodule changes from the supermodule.

$ git submodule summary HEAD^
* a d266b98...261dfac (1):
  > Updated the submodule from within the superproject.

Publish your updated supermodule.

$ git push

Updating all submodules to follow upstream branches

Submodules are usually in the detached HEAD state, as you would get by running

$ git checkout <SHA>

The foreach command checks out a particular local branch in the submodule repository, and pulls any changes from that branch's default upstream (branch.name.remote and branch.name.merge in .git/modules/modname/config). The name of the branch to check out is extracted from .gitmodules, so you'll want to manually specify your intended branch (e.g. after adding a new submodule):

$ git submodule add -b xyz git://example.com/abc.git somedir
$ git config -f .gitmodules submodule.somedir.branch xyz

Once you've done that for each of your submodules, you can (since Git version 1.7.2, commit f030c96d) run

$ git submodule foreach 'git checkout $(git config --file $toplevel/.gitmodules submodule.$name.branch) && git pull'

Removing submodules

From the Git Wiki

  1. Delete the relevant line from the .gitmodules file.
  2. Delete the relevant section from .git/config.
  3. Run git rm --cached path_to_submodule (no trailing slash).
  4. Commit and delete the now untracked submodule files.

Warnings

From the Git Wiki

It's not safe to run git submodule update if you've made changes within a submodule. They will be silently overwritten:

  • Example
    From the Git Wiki
    a$ cat a.txt
    module a
    a$ echo line added from private2 >> a.txt
    a$ git commit -a -m "line added inside private2"
    $ cd ..
    $ git submodule update
    Submodule path 'a': checked out 'd266b9873ad50488163457f025db7cdd9683d88b'
    $ cat a/a.txt
    module a
    

    The changes are still visible in the submodule's reflog:

    $ git log -g --pretty=oneline
    d266b9873ad50488163457f025db7cdd9683d88b HEAD@{0}: checkout: moving to d266b9873ad50488163457f025db7
    4389b0d8e22e616c88a99ebd072cfebba40797ef HEAD@{1}: commit: line added inside private2
    d266b9873ad50488163457f025db7cdd9683d88b HEAD@{2}: checkout: moving to d266b9873ad50488163457f025db7
    

Git subtrees, merging an entire repository into a subdirectory of your repository

Setup

From kernel.org: merge-subtree

Name the other project Bproject, and fetch.

$ git remote add -f Bproject /path/to/B

Prepare for the later step to record the result as a merge. (-s ours selects the merge strategy as "keep our version")

$ git merge -s ours --no-commit Bproject/master

Read master branch of Bproject to the subdirectory dir-B.

$ git read-tree --prefix=dir-B/ -u Bproject/master

Record the merge result.

$ git commit -m "Merge B project as our subdirectory"

Keeping up to date with the sub-repository source

Maintain the result with subsequent pulls/merges using "subtree"

$ git pull -s subtree Bproject master

Pulling changes back into the sub-repository source

From git-subtree

The super-project makes some local alterations. Pull/merge them with git subtree.

Backdating Git tags

I rarely bother tagging my projects in the early stages. This is probably a mistake ;), but Git makes it easy to add backdated tags later on:

$ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1

(from git tag --help). Note that you will probably be tagging a previous commit

$ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1 <commit>

Inline word diff

mislav

$ git diff --word-diff

Find out if a change is part of a release

$ git name-rev --tags 33db5f4d9027a10e477ccf054b2c1ab94f74c85a
33db5f4d9027a10e477ccf054b2c1ab94f74c85a tags/v0.99~940

Now you are wiser, because you know that it happened 940 revisions before v0.99. A similar command is git describe, which finds the latest tag in a commit's history and uses that to construct a name:

$ git describe v1.0.5^2
tags/v1.0.0-21-g975b31d

List unmerged commits

To see what's outstanding in a given branch

$ git cherry [-v] [<upstream> [<head> [<limit>]]]

The -v option prints summaries as well as the usual commit hashes.

Remove a remote branch

If you want to delete a branch from a repository to which you only have push access, use:

git push repository :branch

after deleting the branch in your local repository.

Clean out your working directory

To list files that will be removed (a dry run), use:

git clean -dxn

Then remove the files with

git clean -dxf
OptionMeaning
-dRemove untracked directories in addition to untracked files
-xDon’t use the ignore rules (remove all untracked files)
-nDry run
-fForce

Bisecting a kernel bug (i.e. manual bisection)

Although it is possible to automate this (if you have two boxes, one to build kernels and drive the bisection, and one to boot them and check the results), it is also possible to bisect kernel bugs "by hand" using git bisect. The man page does a better job describing the process than I will.

Note that Git will track the bisection state across reboots, which is convenient for tracking down kernel bugs. You need to run git bisect reset to clear that state. If you're nervous, you can save the state on your own with git bisect log > your-file.

Automatic CCs with git send-email

The Linux kernel and Git mailing list (and other projects) prefer patch submissions via email (vs. pull requests), so that everyone can make line-by-line comments on the submissions before the patches are committed to the main development repositories. The Git commands that facilitate this technique are format-patch, send-email, and am. Here's how things will usually work. We'll start off following the patch developer. It saves typing in the long run if you configure some defaults first:

$ git config --global user.name "John Doe"
$ git config --global user.email jdoe@invalid.com
$ git config --global user.signingkey 0x0123456789ABCDEF
$ git config --global format.thread true
$ git config --global sendemail.cc-cmd ~/bin/git-cc
$ git config --global sendemail.aliasesfile ~/.mutt/aliases
$ git config --global sendemail.aliasfiletype mutt
$ echo 'alias git-list git@vger.kernel.org' >> ~/.mutt/aliases

The git-cc command is a slick, git-blame­based script by Felipe Contreras (gist).

Checkout the source, and start your development branch:

$ git clone git://github.com/gitster/git.git
$ cd git
$ git config sendemail.to git-list
$ git checkout -b dev/my-feature

Make your changes:

$ emacs gitweb/gitweb.perl
  (hack, hack, hack, …)
$ git commit -am "gitweb: refactor …"

If upstream has moved on, rebase your changes onto the new trunk:

$ git checkout master
$ git pull
$ git rebase master dev/my-feature

and mail them upstream:

$ git send-email --signoff --annotate --cover-letter origin/master

You can record your patches for comparison with:

$ git format-patch --output-directory patch-my-feature-v1 origin/master

When you hear back with comments on how to improve the patch set, make the appropriate changes with:

$ git checkout dev/my-feature
$ git rebase -i
$ git checkout master
$ git pull
$ git rebase master dev/my-feature

Then send off an updated version:

$ git format-patch --output-directory patch-my-feature-v2 origin/master
$ git diff -ru patch-my-feature-v{1,2} > /tmp/diff-for-cover-letter
$ git send-email --signoff --annotate --cover-letter --subject-prefix 'PATCH v2' origin/master

See gitworkflows(7) for other good ideas.

Signing commits with GPG

Since version 1.7.9, Git can sign commits with -S.

$ git commit -S -m 'Look, a signed commit!'
$ git log --show-signature

One more reason to be running gpg-agent.

Fun with git notes

Creating and showing notes

Say you have a repository

$ git log
commit 56db93aebad496c8ec28df0c4c90bed99da57e94
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:21:28 2012 -0400

    b

commit 84b126ce36567260bef8d0bbea03d426c9b7aa6d
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:21:18 2012 -0400

    a

You can attach a single note file (per note namespace) to each commit.

$ git notes add -m 'note on commit a' HEAD^
$ git notes add -m 'note on commit b' HEAD
$ git notes
bb511b3526c2300251c482b6f408248c83f22bff 56db93aebad496c8ec28df0c4c90bed99da57e94
068f07ddec5dfe0a8f93721acbdd7da87dd94db2 84b126ce36567260bef8d0bbea03d426c9b7aa6d

The output format is <note blob sha> <commit sha>. You can dump a note by hand,

$ git show 068f07
note on commit a

or show the note associated with an object,

$ git notes show HEAD
note on commit b

or show the notes in a log.

$ git log --notes
commit 56db93aebad496c8ec28df0c4c90bed99da57e94
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:21:28 2012 -0400

    b

Notes:
    note on commit b

commit 84b126ce36567260bef8d0bbea03d426c9b7aa6d
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:21:18 2012 -0400

    a

Notes:
    note on commit a

Altering notes

You can append new content to an existing note,

$ git notes append -m 'more notes on commit a' HEAD^
$ git notes show HEAD^
note on commit a

more notes on commit a

or spawn your EDITOR to make changes.

$ EDITOR='sed -i "s/more/many/"' git notes edit HEAD^
$ git notes show HEAD^
note on commit a

many notes on commit a

Removing notes

You can remove notes.

$ git notes remove HEAD^
Removing note for object HEAD^
$ git notes
bb511b3526c2300251c482b6f408248c83f22bff 56db93aebad496c8ec28df0c4c90bed99da57e94

Note namespaces

Git notes have their own references in refs/notes/....

$ git notes get-ref
refs/notes/commits

You can use other namespaces besides commits.

$ git notes --ref wtk get-ref
refs/notes/wtk
$ git notes --ref wtk add -m 'wtk note' HEAD
$ git log --notes=wtk
commit 56db93aebad496c8ec28df0c4c90bed99da57e94
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:21:28 2012 -0400

    b

Notes (wtk):
    wtk note

commit 84b126ce36567260bef8d0bbea03d426c9b7aa6d
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:21:18 2012 -0400

    a

Showing notes in the log

If you want to show multiple namespaces in a single log, just keep adding --notes arguments.

$ git log --notes=wtk --notes
commit 56db93aebad496c8ec28df0c4c90bed99da57e94
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:21:28 2012 -0400

    b

Notes:
    note on commit b

Notes (wtk):
    wtk note

commit 84b126ce36567260bef8d0bbea03d426c9b7aa6d
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:21:18 2012 -0400

    a

Searching notes

You can search through notes for regular expressions.

$ git grep wtk refs/notes/wtk
refs/notes/wtk:56db93aebad496c8ec28df0c4c90bed99da57e94:wtk note

The output format here is <ref>:<commit sha>:<matching line>.

The note branch

Because the notes are stored in their own branch, they have their own commit history.

$ git log -p notes/commits
commit 0625b737c1dd340122478d8e6ae1fe5f8d8ce183
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:25:24 2012 -0400

    Notes removed by 'git notes remove'

diff --git a/84b126ce36567260bef8d0bbea03d426c9b7aa6d b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
deleted file mode 100644
index 866bab5..0000000
--- a/84b126ce36567260bef8d0bbea03d426c9b7aa6d
+++ /dev/null
@@ -1,3 +0,0 @@
-note on commit a
-
-many notes on commit a

commit b046ea813f3e5324e0910da5e60b3aa933aee2ff
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:24:55 2012 -0400

    Notes added by 'git notes edit'

diff --git a/84b126ce36567260bef8d0bbea03d426c9b7aa6d b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
index 975b66d..866bab5 100644
--- a/84b126ce36567260bef8d0bbea03d426c9b7aa6d
+++ b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
@@ -1,3 +1,3 @@
 note on commit a
 
-more notes on commit a
+many notes on commit a

commit d6f6b1e5c8832552e807bcab723e6eaf07d5e5de
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:24:16 2012 -0400

    Notes added by 'git notes append'

diff --git a/84b126ce36567260bef8d0bbea03d426c9b7aa6d b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
index 068f07d..975b66d 100644
--- a/84b126ce36567260bef8d0bbea03d426c9b7aa6d
+++ b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
@@ -1 +1,3 @@
 note on commit a
+
+more notes on commit a

commit 76e670903fcf038b602190a2e0314b265d5a34cf
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:22:28 2012 -0400

    Notes added by 'git notes add'

diff --git a/56db93aebad496c8ec28df0c4c90bed99da57e94 b/56db93aebad496c8ec28df0c4c90bed99da57e94
new file mode 100644
index 0000000..bb511b3
--- /dev/null
+++ b/56db93aebad496c8ec28df0c4c90bed99da57e94
@@ -0,0 +1 @@
+note on commit b

commit 62b129c10cdfc8ec9f9d204dac04a5cf0562dffe
Author: W. Trevor King <wking@tremily.us>
Date:   Tue Sep 4 13:22:21 2012 -0400

    Notes added by 'git notes add'

diff --git a/84b126ce36567260bef8d0bbea03d426c9b7aa6d b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
new file mode 100644
index 0000000..068f07d
--- /dev/null
+++ b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
@@ -0,0 +1 @@
+note on commit a

You can checkout the note branch just like any other branch.

$ git checkout notes/commits

Where you will find one file for each note, where the filename is the hash of the object that the note is about.

$ ls
56db93aebad496c8ec28df0c4c90bed99da57e94
$ cat 56db93aebad496c8ec28df0c4c90bed99da57e94 
note on commit b

You can do all your usual manipulations in the note branch, merging, rebasing, updating refs, etc.

$ git update-ref refs/notes/commits b046ea
$ git notes
bb511b3526c2300251c482b6f408248c83f22bff 56db93aebad496c8ec28df0c4c90bed99da57e94
866bab52944f009b3f92c61beea7a8a7fad0e0a2 84b126ce36567260bef8d0bbea03d426c9b7aa6d

Adding notes to non-commit objects

There's no reason you need to limit your notes to commits. However, you'll need the hash for the object you're tagging. Here's how you can get a hash for a file:

$ git hash-object a
78981922613b2afb6025042ff6bd878ac1994e85

Alternatively, you can list all the files in a directory.

$ ls-tree HEAD^{tree}
100644 blob 78981922613b2afb6025042ff6bd878ac1994e85    a
100644 blob 61780798228d17af2d34fce4cfbdf35556832472    b

Then attach your note to the object directly.

$ git notes add -m 'note on blob a' 789819
$ $ git notes show $(git hash-object a)
note on blob a

Merging notes

  • Naming remote note references

    It's a good idea to place all of your note references under refs/notes, because the current (2012-09-04) implementation of expand_notes_ref in git/notes.c is:

    void expand_notes_ref(struct strbuf *sb)
    {
      if (!prefixcmp(sb->buf, "refs/notes/"))
        return; /* we're happy */
      else if (!prefixcmp(sb->buf, "notes/"))
        strbuf_insert(sb, 0, "refs/", 5);
      else
        strbuf_insert(sb, 0, "refs/notes/", 11);
    }
    

    This means that:

    refs/notes/commits → refs/notes/commits
    notes/commits      → refs/notes/commits
    commits            → refs/notes/commits
    

    But:

    refs/remotes/origin/notes/commits → refs/notes/refs/remotes/origin/notes/commits
    

    which is probably not what you wanted.

    For more details about why this is the case, read this thread.

  • Fast forward merges

    You can check out the note branch and pull/push/merge just like any other branch, but there is also a user interface for merging notes without bothering to checkout the note branch. To see this in action, we'll need to clone our repo.

    $ cd ..
    $ git clone repo-a repo-b
    Cloning into 'repo-b'...
    done.
    $ cd repo-b
    

    Git doesn't fetch (or push) notes by default, so we'll have to add a refspec.

    $ git config --add remote.origin.fetch '+refs/notes/*:refs/notes/remotes/origin/*'
    $ git fetch
    From ../repo-a
     * [new ref]         refs/notes/commits -> refs/notes/remotes/origin/commits
     * [new ref]         refs/notes/wtk -> refs/notes/remotes/origin/wtk
    

    Alrighty, let's make those note's our own.

    $ git notes merge origin/notes/commits
    fatal: Cannot merge empty notes ref (refs/notes/origin/notes/commits) into empty notes ref (refs/notes/commits)
    

    Hmm, looks like the initial commit may be troublesome. Let's adopt the current remote state.

    $ git update-ref refs/notes/commits refs/notes/remotes/origin/commits
    $ git notes
    bb511b3526c2300251c482b6f408248c83f22bff 56db93aebad496c8ec28df0c4c90bed99da57e94
    a4c2f4dcfdb7d547bc303282f2678581a9ebf35f 78981922613b2afb6025042ff6bd878ac1994e85
    866bab52944f009b3f92c61beea7a8a7fad0e0a2 84b126ce36567260bef8d0bbea03d426c9b7aa6d
    

    And make some changes.

    EDITOR='sed -i "N;/\nmany/s/\nmany/extra/;P;D"' git notes edit HEAD^
    git notes show HEAD^
    note on commit a
    extra notes on commit a
    

    Now go back to the original repo and fetch the changes.

    $ cd ../repo-a
    $ git remote add repo-b ../repo-b
    $ git config --add remote.repo-b.fetch '+refs/notes/*:refs/notes/remotes/repo-b/*'
    $ git fetch repo-b
    remote: Counting objects: 5, done.
    remote: Compressing objects: 100% (2/2), done.
    remote: Total 3 (delta 1), reused 0 (delta 0)
    Unpacking objects: 100% (3/3), done.
    From ../repo-b
     * [new branch]      master     -> repo-b/master
     * [new ref]         refs/notes/commits -> refs/notes/remotes/repo-b/commits
    $ git diff notes/commits notes/remotes/repo-b/commits
    diff --git a/84b126ce36567260bef8d0bbea03d426c9b7aa6d b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
    index 866bab5..710e3e0 100644
    --- a/84b126ce36567260bef8d0bbea03d426c9b7aa6d
    +++ b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
    @@ -1,3 +1,2 @@
     note on commit a
    -
    -many notes on commit a
    +extra notes on commit a
    

    Merge.

    $ git notes merge -v remotes/repo-b/commits
    Fast-forward
    
  • Resolving conflicts

    Set up conflicting changes.

    repo-a$ EDITOR='sed -i "s/extra/tons of/"' git notes edit HEAD^
    repo-b$ EDITOR='sed -i "s/extra/lots of/"' git notes edit HEAD^
    repo-b$ git fetch
    remote: Counting objects: 5, done.
    remote: Compressing objects: 100% (2/2), done.
    remote: Total 3 (delta 1), reused 0 (delta 0)
    Unpacking objects: 100% (3/3), done.
    From ../repo-a
       69e6240..bf61bfc  refs/notes/commits -> refs/notes/remotes/origin/commits
    repo-b$ git diff refs/notes/commits refs/notes/remotes/origin/commits
    diff --git a/84b126ce36567260bef8d0bbea03d426c9b7aa6d b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
    index 0808976..b847e1d 100644
    --- a/84b126ce36567260bef8d0bbea03d426c9b7aa6d
    +++ b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
    @@ -1,2 +1,2 @@
     note on commit a
    -lots of notes on commit a
    +tons of notes on commit a
    

    Try to merge repo-a from repo-b.

    $ git notes merge refs/notes/remotes/origin/commits
    Auto-merging notes for 84b126ce36567260bef8d0bbea03d426c9b7aa6d
    CONFLICT (content): Merge conflict in notes for object 84b126ce36567260bef8d0bbea03d426c9b7aa6d
    Automatic notes merge failed. Fix conflicts in .git/NOTES_MERGE_WORKTREE and commit the result with 'git notes merge --commit', or abort the merge with 'git notes merge --abort'.
    $ ls .git/NOTES_MERGE_WORKTREE/
    84b126ce36567260bef8d0bbea03d426c9b7aa6d
    $ cat .git/NOTES_MERGE_WORKTREE/84b126ce36567260bef8d0bbea03d426c9b7aa6d
    note on commit a
    <<<<<<< refs/notes/commits
    lots of notes on commit a
    =======
    tons of notes on commit a
    >>>>>>> refs/notes/remotes/origin/commits
    $ emacs .git/NOTES_MERGE_WORKTREE/84b126ce36567260bef8d0bbea03d426c9b7aa6d
    <edit as you see fit>
    $ git notes merge --commit
    $ git notes show HEAD^
    note on commit a
    zounds of notes on commit a
    

    You can also set some useful merge strategies. Lets go back and try the merge again. First, undo the merge

    $ git update-ref refs/notes/commits refs/notes/commits^
    $ git diff refs/notes/commits refs/notes/remotes/origin/commits
    diff --git a/84b126ce36567260bef8d0bbea03d426c9b7aa6d b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
    index 0808976..b847e1d 100644
    --- a/84b126ce36567260bef8d0bbea03d426c9b7aa6d
    +++ b/84b126ce36567260bef8d0bbea03d426c9b7aa6d
    @@ -1,2 +1,2 @@
     note on commit a
    -lots of notes on commit a
    +tons of notes on commit a
    

    Now merge with a particular strategy.

    $ git notes merge --strategy=cat_sort_uniq refs/notes/remotes/origin/commits
    Concatenating unique lines in local and remote notes for 84b126ce36567260bef8d0bbea03d426c9b7aa6d
    $ git notes show HEAD^
    lots of notes on commit a
    note on commit a
    tons of notes on commit a
    

    There are more details about the available merge strategies in git notes --help.

Printing a file

You can cat a file from an arbitraty revision using

$ git cat-file blob <rev>:<path>

For example,

$ git cat-file blob master:README

Tag a file (not a commit)

Add the file (e.g. my-file to the object database and get it's SHA-1:

$ git hash-object -w my-file
fe113d3f96636710600c6b02d5fd421fa7e87dd6

Then tag that hash.

$ git tag junio-gpg-pub fe113d3f96636710600c6b02d5fd421fa7e87dd6

Combining these into one command using Bash's command substitution:

$ git tag junio-gpg-pub $(git hash-object -w my-file)

Prune remote tracking branches

Sometimes an upstream remote will remove branches, and you'll be left with stale remote-tracking branches. You can clean them up with remote prune:

$ git remote prune origin
Pruning origin
URL: git://git.kernel.org/pub/scm/git/git.git
 * [pruned] origin/html
 * [pruned] origin/man

Troubleshooting

Git commit hangs with no output

You probably corrupted something in .git.

$ git gc
$ git fsck

fixed the problem for me.

Conflicting merge

git pull or git merge aborts with merge-failed or some such. This leaves your checked out branch in a state where

$ git diff

shows the merge difficulties. You can either abort the merge, or merge by hand.

Abort the merge

$ git reset --hard HEAD

Merge by hand

Edit the marked packages as you see fit. Then let Git know you've accepted your changes with either

$ git update-index

or

$ git add CONFLICT_FILE

Then commit with

$ git commit -a