GitTips
From Git SCM Wiki
See also:
- GitFaq (Frequently Asked Questions) page.
- GitWorkflows which attempts to decribe actual, useful, real-world things that people do with git, step by step.
- ExampleScripts - You can do a lot by writing a simple script.
- Aliases are very powerful ways to avoid writing a script.
- git ready - daily tips for the noob to the guru
Table of contents:
General
How to fix the most recent commit
Git allows you to easily fix up the most recent commit you've made on a branch with the --amend option:
For example the following command will allow you to alter the commit message at the top of current head:
$ git commit --amend
while
$ git commit -s --amend
will let you alter the commit message and will also automatically add a sign-off message for you.
How to change commits deeper in history
Since history in Git is immutable, fixing anything but the most recent commit (commit which is not branch head) requires that the history is rewritten from the changed commit and forward.
You can use StGIT for that, initialize branch if necessary, uncommitting up to the commit you want to change, pop to it if necessary, make a change then refresh patch (with -e option if you want to correct commit message), then push everything and stg commit.
Or you can use rebase to do that. Create new temporary branch, rewind it to the commit you want to change using git reset --hard, change that commit (it would be top of current head), then rebase branch on top of changed commit, using git rebase --onto <tmp branch> <commit after changed> <branch>.
Or you can use git rebase --interactive, which allows various modifications like patch re-ordering, collapsing, ...
git filter-branch can also be useful. It has been ported from the obsoleted Cogito's cg-admin-rewritehist(1).
How to get only merges in gitk?
With recent version of git you can use
gitk --full-history -- a//b
which will give you each commit that changes that nonexistent file (because there can't be file with double slash in name in git repository), and the full commit history for those (i.e. all the merges).
If you use "git log", you also need to add "--parents" while gitk will do it for you.
What to do if you have realized that you are on wrong branch?
If you have not committed your changes yet, it is enough to
git checkout -m <correct-branch>
(if you use core Git at least v1.4.1-rc2).
(Cogito users will do cg switch <correct-branch>)
Let's assume that you wanted your changes to go on top of <current>, but by accident you committed your changes on top of <master> for example, and that the commit before your commits (the tip of <master> branch should be) is <before>. Let as assume that all changes are committed, and the working dir is clean.
First, create temporary branch (just in case), e.g.
git branch <tmpBranch>
Then you can reset <master> branch to correct commit
git reset <before>
Move temporary branch to correct branch, i.e. <current>, using
git rebase --onto <current> <master> <tmpBranch>
After resolving all conflict what is left is to fast-forward <current> to correct commit, and remove temporary branch
git checkout <current> git reset --hard <tmpBranch> git branch -d <tmpBranch>
I'm not sure if --hard option is really needed.
How to compare two local repositories
In Git you can use, being in one of repositories
GIT_ALTERNATE_OBJECT_DIRECTORIES=../repo/.git/objects git-diff-tree $(GIT_DIR=../repo/.git git rev-parse --verify HEAD) HEAD
if you want to compare current repository with ../repo/ repository.
In the obsoleted Cogito you would nearly exactly the same
GIT_ALTERNATE_OBJECT_DIRECTORIES=../repo/.git/objects cg-diff -r `GIT_DIR=../repo/.git cg-object-id -c HEAD`..HEAD
How to use git to track OpenDocument (OpenOffice, Koffice) files?
Instructions for Git 1.6.1 or later
Git now comes with the "textconv" features, which allows using an arbitrary command to convert a file to text before diffing. It makes it very easy to set up, and allows keeping all the goodness of git diff like --color, --color-words, ...
First, install odt2txt, a simple (and stupid) converter from OpenDocument to plain text, and configure git to allow it to run it, by adding this to ~/.gitconfig:
[diff "odf"]
textconv=odt2txt
Now, for each project, you just need to ask git to use this driver in .gitattributes or $GIT_DIR/info/attributes, like this:
*.ods diff=odf *.odt diff=odf *.odp diff=odf
... and you're done! Enjoy "git diff", "git log -p", "git show" in this project.
Instructions for older Git's
To view plaintext diff of OpenDocument files in Git, you can use the GIT_EXTERNAL_DIFF environment variable. First, install :
- odt2txt: A simple (and stupid) converter from OpenDocument to plain text
- git-oodiff: Wrapper script for odt2txt and diff.
You can now, for example, do
$ GIT_EXTERNAL_DIFF=git-oodiff git diff git-oodiff presentation-expl.odp presentation-expl.odp --- a/presentation-expl.odp +++ b/presentation-expl.odp @@ -3,7 +3,7 @@ First item - First version of second item + Second version of second item Last item
Now, let's automate this a bit more. Add this to your ~/.gitconfig (or .git/config):
[diff "oodiff"]
command=git-oodiff
This defines a "oodiff" diff driver, that you can now use in .gitattributes (at the root of your working tree, or in $GIT_DIR/info/attributes)
*.odp diff=oodiff *.odt diff=oodiff *.ods diff=oodiff
Now, git will use this oodiff driver for any file whose name ends with .odp, .odt, or .ods.
(this is also documented here).
user-note:
- BUG in git-oodiff script - it will give an error if /bin/sh points to "dash" instead of "bash" (this will affect current debian & ubuntu distros)
How to remove all files which are missing from working directory?
git ls-files -z --deleted | git update-index -z --remove --stdin
To both remove files that are missing, and add all files that are new, one can use the (much easier) command:
git add -A
How to ignore files which are "Untracked" now?
$ git ls-files -o --exclude-standard >> .gitignore $ $EDITOR .gitignore
(note : --exclude-standard is not yet in a released version of git as of november 2007, you'll have to use --exclude-from=.gitignore --exclude-from=.git/info/exclude ... if you don't have it).
How to unpack all branches?
For example if you have realized that you repository must be accessible by older dumb clients, or some script you use doesn't understand packed refs.
git for-each-ref refs/heads | \
( x=`git rev-parse --git-dir`; \
while read id junk ref; do \
echo $id > "$x/$ref"; \
done )
How to find a commit for a shortened git-describe string
For your local (!) repository you can get the commit for a string like v2.6.26-rc9-56 by the following command :
git rev-list --all v2.6.26-rc9.. | xargs git describe | grep '\-56\-'
How to create a new branch that has no ancestor
Why? You might want to create a new component in a branch by itself, or perhaps you want to create a test case branch, or perhaps a documentation branch, any of which you want to keep separate from other development branches because they have completely different files.
So, here is how to add a new and empty branch.
The easy way: create a new repository, start the branch, and push this branch into your original repository. Example:
mkdir anew cd anew git init # add some files here cp ../<files...> ./ git add . git commit git push .. HEAD:new-branch-without-ancestor cd .. rm -rf anew
The quick way: reuse the working directory, but initialize a new repository with a new branch, and then fetch the branch. Example:
git --git-dir=tmp.git init # add some files git --git-dir=tmp.git add <files...> git --git-dir=tmp.git commit git fetch tmp.git master:new-branch-without-master rm -rf tmp.git
Of course, you can make life hard on yourself instead:
git symbolic-ref HEAD refs/heads/mynewbranch rm .git/index git clean -fdx # add some files here git add . git commit
How does this work?
git symbolic-ref HEAD refs/heads/mynewbranch
HEAD is a symbolic reference that refers to the current branch. Try doing 'git symbolic-ref HEAD' and it will show you the actual reference to which it refers.
By providing that second argument, you are saying that you now want the symbolic reference HEAD to refer to this new reference refs/heads/mynewbranch. Voila! a new branch is created.
But the branch is not yet empty ... which bring us to the next couple commands.
rm .git/index git clean -fdx
The first command removes the index file with with Git is tracking the working directory changes. No index, no tracking.
The second command will clean out all untracked files (which is now everything except for the Git repository files. This is a safe way of emptying the directory (whereas rm -rf * is NOT safe :-).
Now you can create one or more files that will be part of the initial commit of this new branch. Then add and commit, as you normally would. For example:
git add . git commit
That's it. A bit tricky at the start, but not too tricky.
References
- http://book.git-scm.com/5_creating_new_empty_branches.html
- http://madduck.net/blog/2007.07.11:creating-a-git-branch-without-ancestry/
Configuration
Specify attribute values in the correct format
When specifying values for gitattributes(5) do not include white space around '=' as whitespace is the attribute delimiter. This differs from git configuration files where white space around '=' is supported.
How to upgrade repository config to use post-1.5.0 features?
Updating a pre-1.5.0 Git repository to be like a 1.5.0 (and later) style repository basically means rewriting the remotes (.git/info/remotes/) into .git/config, setting up wildcard tracking branches under refs/remotes and deleting the old tracking branches from the refs/heads namespace.
The following shell script can be used to upgrade non-bare (!) repository (with a working directory) to post-1.5.0 style, to be able to use new features:
#!/bin/sh
##
## Upgrades a pre-1.5.0 repository to a 1.5.0-style layout.
## Use at your own risk.
##
sh remotes2config.sh # from git.git/contrib
git config core.bare false
git config core.logallrefupdates true
for r in `git remote`
do
old="`git config --get-all remote.$r.fetch | sed s,^.*:refs/heads/,,`"
if [ -z "$old" ]
then
echo "No fetch lines for $r."
continue
fi
echo "Converting $r..."
git config --replace-all remote.$r.fetch +refs/heads/*:refs/remotes/$r/*
git fetch $r
for r in $old
do
case "$r" in
*:*) echo "Not deleting $r" ;;
*) git branch -D $r ;;
esac
done
done
This script requires remotes2config.sh conversion tool by Johannes Schindelin, which can be found in the contrib area of git.git repository.
- Upgrade to 1.5.0 utility post by Shawn O. Pearce on Git mailing list
StGIT
How to rebase StGIT stack?
If you have <n> patches on stack, and you want to rebase it (for example because git branch got rebased, or you want to base stack on different branch)
stg pop -a git reset --hard <new_base> stg push -n <n>
Web
How to generate RSS feed off-line?
You can either adapt git2rss tool by Bennett Todd to your needs (which is mentioned on Interfaces Frontends And Tools page), or use gitweb to do that, running it as a script. Assuming that gitweb.cgi is setup correctly, you can use:
env REQUEST_METHOD=GET QUERY_STRING='p=git/git.git;a=rss' \ ./gitweb.cgi | tail -n +2
where of course instead of git/git.git you put project (git repository) you are interested in.
How to push/pull via ssh to host behind gateway?
There are two possible solutions. With first, using ProxyCommand option of ssh, you can connect to the entire network behind firewall. In your ssh configuration file, for example ~/.ssh/config put the following:
Host *.foo.internal
ProxyCommand ssh gateway.foo.com exec nc %h %p
(where '%h' expands to the host you connect to, and '%p' to the port you are using). You have to have nc (netcat or GNU Netcat) installed on gateway, but this should be no problem as it is 22K binary. Note that path is expanded on local side, while it should be to file on gateway side.
Second is slightly faster, doesn't need any tool besides ssh, but you have to run port forwarding command before connecting, must make sure that ssh tunnel is up, and must be set up host for host. In your ssh configuration file, for example ~/.ssh/config put the following:
Host hostname
NoHostAuthenticationForLocalhost yes
HostName localhost
Port 2222
where `hostname' is the name you would be using for connecting. Before trying to connect to host begind gateway, you must run
ssh -f -N -L 2222:host.foo.internal:22 username@gateway.foo.com
Of course port numbers must match (and port number should be above 1024 if you won't be running ssh tunnel as root). If you have sufficiently new ssh, you can add '-M' option, for use with ControlMaster option. You can also use autossh for running port forwarding/ssh tunnel.
How to pass ssh options in git?
You can set the path of the ssh executable to use with the GIT_SSH environment variable. Create a shell script like
#!/bin/sh exec ssh --your-options-- "$@"
and make GIT_SSH point to it.
But if you have just a couple of "standard" setups, then it's really better to use a ".ssh/config" file instead. If you want to use different options "dynamically" (the same host, different options), you can just use different "fake hostnames". For example, you can do
Host private.host.com
User myname
Hostname host.com
IdentityFile /home/myname/.ssh/private-identity
Host public.host.com
User groupname
Hostname host.com
IdentityFile /home/myname/.ssh/public-identity
and now you can ssh to "host.com" using different identities by just using "private.host.com" and "public.host.com" respectively.
Using gmail to send your patches
git send-email now supports TSL/SSL, so using gmail is as simple as setting the following configuration variables:
[sendemail]
smtpencryption = tls
smtpserver = smtp.gmai.com
smtpuser = yourname@gmail.com
smptserverport = 587
It'll ask you for your password when you're sending the emails. Like so:
git format-patch --no-color -C -M origin/master..topic-branch -o outgoing/ git send-email --compose outgoing/00*
Using msmtp to send your patches
NOTE: git send-email supports TLS/SSL now. If you are looking for gmail tips see the section above.
Mailing off a set of patches to a mailing list can be quite neatly done by git-send-email. One of the problems you may encounter there is figuring out which machine is going to send your mail. I tried smtp.gmail.com, but that one requires tls and a password, and git-send-email could not handle that.
A neat little program, msmtp http://msmtp.sourceforge.net/ can help you there. Install and configure a .msmtprc file in your home directory, the likes of:
# Example for a user configuration file # Set default values for all following accounts. defaults tls on tls_trust_file /etc/pki/tls/certs/ca-bundle.crt logfile ~/.msmtp.log # My email service account gmail host smtp.gmail.com port 587 from some.user.name@gmail.com auth on user some.user.name@gmail.com password my-secret # Set a default account account default : gmail
This takes gmail as an example. The password field is not required. msmtp will prompt you for your password if the field is omitted. The ca-bundle.crt is the file with CA certificates for Fedora Core 6, for other distros you might have to dig it up from somewhere else. On Ubuntu /etc/ssl/certs/ca-certificates.crt should be used, for example. Now, by giving the full path name to the msmtp program as smtp server to git-send-email, you can send the patches through gmail or some other smtp account with TLS and/or user authentication.
git-send-email --smtp-server /usr/local/bin/msmtp <file|directory>
An alternative to setting --smtp-server each time is to set the global sendemail.smtpserver value.
git-config --global sendemail.smtpserver /usr/local/bin/msmtp
Another option is to configure a local smtp server on your machine, using a well-known SMTP server as smarthost. Then, all applications using localhost as a mail server (e.g. /usr/bin/mail) will work.
On OS X with macports, msmtp can be installed with 'sudo port install msmtp', and '/usr/share/curl/curl-ca-bundle.crt' should work for tls_trust_file.
git svn
How to use git branches with git svn
git-svn(1) allows you to use git as an interface to a Subversion repository. However, there are a number of caveats when doing this because Subversion's branching model is less powerful. git-merge(1) should not be used on the branch you intend to git svn dcommit from because the merge object cannot be expressed in a useful form to the Subversion repository when committing. This essentially makes it impossible to usefully use git branches for local development - yet the branching/merging mechanism is one of the most powerful features that git provides.
There are two ways to get around the git merge problem and so be able to use git branches for (local) development. For the following, assume you've branched and the commit geneology looks like:
C--E--F branch
/
A--B--D master
The first merge solution is to replicate the changes you made on your branch to master using git-cherry-pick(1) (or alternatively git-format-patch(1) and git-am(1)). However, it can be rather laborious determining the list of required commits (C,E,F) and iteratively re-applying them to master (C', E', F').
C--E--F branch
/
A--B--D--C'--E'--F' master
Whilst this does allow you to develop multiple projects simultaneously and independently, this work-around isn't much friendlier than just working directly on master in the first place.
The second solution takes advantage of git-rebase(1) and git-branch(1). When you want to merge your changes back to master you rebase your branch with respect to master HEAD (i.e. what the Subversion repository looks like):
git checkout <branch> git rebase master
Your branch now looks like master plus the extra changes you made on the branch:
C"--E"--F" branch
/
A--B--D master
In fact, it's what you'd ideally like to git svn dcommit now (or later) but it's stuck on the wrong branch. So now we tell git that we'd like to pretend that the branching never happened and that branch is what master should really look like:
git branch -M master
The -M specifies that we want to do a forced rename of the branch (forced because master already exists):
A--B--D--C"--E"--F" master
Note that this destroys the development branch in the process* so this should only be done when you've finished development on the branch. However, there's nothing stopping you from re-branching again:
git checkout -b <branch>
o branch
/
A--B--D--C"--E"--F" master
- Technically we destroyed the old master branch but since branch can no longer be referred to by name it appears as though that is the object that was destroyed.
An Alternate Method Using `git-svn rebase`
If you have a branch like above, you can often perform a git-svn rebase on the branch directly. Instead of rebasing to master, this rebases the branch directly onto the latest remote revision in subversion. Then you can git-svn dcommit in the branch. Finally, check out master and do another git-svn rebase to bring it up to date. This will merge in the changes from the branch as well as any new changes in subversion. Note that with this method, the branch sticks around, but afterwards gitk will show that it marks the same history as master -- until you commit to it again or delete it, anyway.
