GitTips

From Git SCM Wiki

Jump to: navigation, search

See also:


Table of contents:

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

  1. http://book.git-scm.com/5_creating_new_empty_branches.html
  2. 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.

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.

Mail

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.


Personal tools