Overview
Git is a distributed vesioning control system.
References
Overviews
DONE Git vs. Mercurial blog entry: MacGyver vs. Bond
- State "DONE"
- State "TODO"
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"
TODO gittutorial The official intro ;), looks fairly readable…
- State "TODO"
TODO Everyday GIT With 20 Commands Or So Nicely broken down by goal
- State "TODO"
TODO Git Community Book Open source wiki/howto website
- State "TODO"
Interface with SVN
TODO Diving into git blog entry Importing the Subversion history
- State "TODO"
TODO Git - SVN Crash Course ?
- State "TODO"
TODO An introduction to git-svn for Subversion/SVK users and deserters
- State "TODO"
Lots of political digression, but also lots of useful info. Your call.
Enforcing development policies
TODO Push hooks
- State "TODO"
Push hooks
DONE Pre commit hooks
- State "DONE"
- State "TODO"
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
- State "DONE"
- State "TODO"
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 releasedev
, 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 havegit 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.
- Warning: here documents
- 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
Fromman 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
:Option Meaning -p x
Pass -x
tocvsps
, which ignores any~/.cvsps/cvsps.cache
file-v
Verbose -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
- Delete the relevant line from the
.gitmodules
file. - Delete the relevant section from
.git/config
. - Run
git rm --cached path_to_submodule
(no trailing slash). - 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 Wikia$ 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
$ 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
Option | Meaning |
---|---|
-d | Remove untracked directories in addition to untracked files |
-x | Don’t use the ignore rules (remove all untracked files) |
-n | Dry run |
-f | Force |
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 ofexpand_notes_ref
ingit/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
fromrepo-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.