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?

Using clean and smudge filters to diff and merge on the XML

http://kerneltrap.org/mailarchive/git/2008/9/15/3305014 suggests a method which stores the unzipped OpenDocument file in git. Thus you should be able to both diff and merge the plain XML of two versions. However, note this warning about the "rezip" approach.

Getting a plain-text diff

The alternative is to store the binary, and have an alias to convert to plain text for diffing.

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, ... To have more information on textconv features, and to see some demos, please refer to this article

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: Use git checkout's --orphan flag, which has been available since v1.7.2:

git checkout --orphan new-branch optional-starting-point
git rm -rf . # start afresh
...          # add stuff to the index.
git commit   # make the root commit

The first line creates a new branch named new-branch, and the index and the working tree are set as if you had previously run git checkout optional-starting-point (or git checkout HEAD if no optional-starting-point is given). The first commit made on new-branch becomes a root commit, having no parents and thus being completely disconnected from existing history.

Since the index and working tree are already prepared with data, just running git commit creates a root commit with the same file contents as that provided by optional-starting-point. However, a completely different set of data can be committed by first clearing the index and the working tree; this is most safely achieved by running git rm -rf . from the top level of the working tree. Then the root commit may be prepared as usual.

Alternatives

  • 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
  • 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
  • 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. <p/> 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. <p/> 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 :-). Alternatively, you may use the very safe git rm -rf . command. <p/> 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.


Turn off translation (locale) for Git

To make Git be not localized, to avoid using translation (e.g. because it is confusing, and there is no translated documentation) you can use the following hack. Set GIT_TEXTDOMAINDIR to something nonsensical (like "/"), which will mean git cannot find the *.po files, and just uses the builtin messages.


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?

Perl script by Bennett Todd you can either adapt to your needs (which is mentioned on Interfaces Frontends And Tools page)

May be used 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.

Ruby Gem which does Git to RSS conversion and includes a standalone web server for the RSS if desiered.

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.gmail.com
    smtpuser = yourname@gmail.com
    smtpserverport = 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.



Personal tools