Push single commit

git push origin HEAD~1:master

This pushes the next-to-last commit (and those prior) to origin/master.

Reorder commits

git rebase -i HEAD~3

Then simply switch order between the last two commit picks.

Delete remote branch

git push origin --delete main

Default to rebase instead of merge

git config --global pull.rebase true
git config --global rebase.autoStash true

Remove files from git history

$ pip install git-filter-repo # official recommendation
$ git filter-repo --filename-callback 'return filename if b"filename.zip" not in filename else None' \
      --prune-empty=always --force

Tired of vim

git config --global core.editor nano

Find lost commit

git reflog

Add Linux executable files in Git For Windows

git update-index --chmod=+x twerk/*.py
Check your change:
git status --porcelain=2

Listing ignored files

git status --ignored -- path/to/files

Overwriting remote and local

git push -f origin master
Then on other machine:
git fetch
git reset --hard origin/master

svn update

(git stash)
 git svn fetch
 git svn rebase
(git stash pop)

Diffing file between two distinct commits

git log -p b3503535:path/to/file.cpp 1f3c4a34:path/to/file.cpp

Don't overuse less

This disables the git pager when there is less than one page available for viewing:
git config --global core.pager "less -F -X"

Swap places on your last two commits

Only attempt this if you haven't pushed yet.

git rebase --onto HEAD~2 HEAD~1 HEAD
git cherry-pick ORIG_HEAD~1
git log -1

Copy the the <SHA1>.

git checkout master
git reset --hard <SHA1>

Commit your working tree to a new branch

git checkout -b topic123
git commit -a
git push --set-upstream origin topic123

Drop Windows-linefeeds already in repo

git config --system core.autocrlf false
git filter-branch -f --prune-empty --tree-filter 'git ls-files -z | xargs -0 dos2unix' -- --all
git config --system core.autocrlf true
echo '* text=auto' >> .gitattributes
But only as a reaction to things gone bad. As a general rule of thumb, always start your git projects/imports by:
git config core.autocrlf true
echo '* text=auto' >> .gitattributes

Counting lines of code (non-git tip)

Put the following in your .bashrc as you're starting a new project:
alias loc='find . |grep -E '"'"'\.(h|hpp|c|cc|cpp|py|js|css|html|jsp|java|php)$'"'"' | xargs cat | wc -l'
alias projloc='for D in *; do [ -d "${D}" ] && echo $D && cd $D && loc && cd ..; done'

What git pull is

$ git pull
is shorthand for
$ git fetch
$ git merge origin

Looking inside a merge commit

$ git log --pretty=format:'%H: %s'
1cd2d7606bbb4b513c8a9460c24955a44ac85da5: HeliForce: fixed monster truck color variation as
a7b4c005498096c5b36d76c99ae694fb217dee1d: Added gaming ideas + improvements.
b13baf97a1fd7f8de3fffa2efa369fedf9d025de: Merge branch 'master' of ssh://localhost:2202/~rg
732b03941bdfdc6860bf557bab91eb7371f587b8:  - DRY'ed float_health,  - added monster trucks +
a8af0aa92c03120930241ed12739d474e2cfea9a: Made landings more velocity-dependant and also fi
...
Then diff from previous commit to merge commit:
git log -p 732b03941bdfdc6860bf557bab91eb7371f587b8..b13baf97a1fd7f8de3fffa2efa369fedf9d025de

Mesuring your quantitative worth in terms of code

It's hard to measure a programmer's worth. Here's a python script to measure quantity. Run like this:
git log -p | git_log_quantity_per_person.py
The script "git_log_quantity_per_person.py":
#!/usr/bin/env python

import pandas as pd
import sys

goodext = ('java')

removalfactor = 2.0 # Paraphrasing Tolstoy: "nothing can improve a piece of software as much as code removal".
commitfactor  = 0.1 # A commit is only as good as it's content.

def score(adds, removes, commits):
 return adds + removes*removalfactor + commits*commitfactor

def printflatstats(peopledata):
 print('name\tadds\tremoves\tcommits')
 for person, changedata in peopledata.items():
  print('%s\t%6.i\t%6.i\t%4.i' % (person, changedata['adds'], changedata['removes'], changedata['commits']))

def createdatetable(peopledata, dates):
 table = []
 headers = ['dates'] + list(peopledata.keys())
 table += [headers]
 for date in reversed(dates):
  datefmt = '%s-%s-%s' % (date.date[:4], date.date[4:6], date.date[6:])
  row = [datefmt]
  for person in peopledata.keys():
   changedata = date.peopledata.get(person)
   if changedata:
    row += [score(changedata['adds'], changedata['removes'], changedata['commits'])]
   else:
    row += [0.0]
  table += [row]
 return table

def transpose(table):
 return zip(*table)

def combinemonths(table):
 df = pd.DataFrame({c[0]:c[1:] for c in zip(*table)})
 df.dates = [d[:7]+'-01' for d in df.dates]
 df.dates = pd.to_datetime(df.dates)
 df = df.groupby('dates').sum()
 return df

def printdatestats(peopledata, dates):
 table = createdatetable(peopledata, dates)
 table = combinemonths(table)
 persons = table.iloc[-13:, :].sum() # last year-ish
 persons = persons.sort_values().index[::-1]
 table = table[persons]
 s = table.reset_index().to_csv(index=False, sep='\t')
 print(s)

class CommitDate:
 def __init__(self, date):
  self.date = date
  self.peopledata = {}

def addcommit(peopledata, person):
 if not peopledata.get(person):
  peopledata[person] = {'adds':0, 'removes':0, 'commits':1}
 else:
  peopledata[person]['commits'] += 1

def cat(inf):
 global goodext
 peopledata = {}
 dates = []
 import signal
 signal.signal(signal.SIGINT, signal.SIG_IGN)
 try:
  linecnt = 0
  goodfile = False
  for line in inf:
   line = line.strip()
   if line.startswith('Author:'):
    goodfile = False
    person = line.partition(' ')[2].partition('<')[0].strip()
   if line.startswith('Date:'):
    goodfile = False
    dateline = line.split()
    #print(dateline)
    month, day, year = dateline[2], dateline[3], dateline[5]
    #print(month)
    month = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec').index(month) + 1
    day = int(day)
    year = int(year)
    date = '%.4i%.2i%.2i' % (year, month, day)
    if len(dates) <= 0 or dates[-1].date != date:
     dates.append(CommitDate(date))
    addcommit(peopledata, person)
    addcommit(dates[-1].peopledata, person)
   if line.startswith('+++ ') or line.startswith('--- '):
    if line.endswith('/dev/null'):
     continue
    ext = line.rsplit('.', 1)[-1].lower()
    goodfile = (ext in goodext)
    #if not goodfile:
    # print('Not counting extension', ext)
   if not goodfile:
    continue
   elif line[:1] == '+':
    peopledata[person]['adds'] += 1
    dates[-1].peopledata[person]['adds'] += 1
   elif line[:1] == '-':
    peopledata[person]['removes'] += 1
    dates[-1].peopledata[person]['removes'] += 1
   else:
    continue
   linecnt += 1
   if linecnt % 10000 == 0:
    print('.', file=sys.stderr)
 except KeyboardInterrupt:
  pass
 return peopledata, dates

if __name__=='__main__':
 import codecs
 sys.stdin = codecs.getreader('utf8')(sys.stdin.detach(), errors='ignore')
 peopledata, dates = cat(sys.stdin)
 print()
 print()
 printflatstats(peopledata)
 print()
 printdatestats(peopledata, dates)
Two tables are printed: one flat and one over time. Cut'n'paste into Excel and you're done.

Reapply old changes when using some other SVC

You've stored changes without history.
The commit you started from in your SVC: master/HEAD.
The commit with your old changes: abc (based on same code as in master/HEAD).
The commit with latest development from your SVC: main/HEAD.
$ git checkout master
$ git checkout --patch abc
[Confirm all patches with a[ENTER].]
$ git commit
$ git merge main

Delete all files marked as "unstaged deletes"

$ git status --porcelain | awk '/^.D .*$/ {print $2}' | xargs git rm

SVN clone

$ git svn clone svn://svn.berlios.de/chibi-xmplay/trunk

Clone a single branch

$ mkdir project
$ cd project
$ git init
$ git remote add -t the_branch -f origin ssh://whoever@host.com:1234/~/blah.git
$ git checkout the_branch

Stop repeating yourself (rerere)

rerere stores your conflict (merge?) resolutions, so you won't have to redo the same thing over and over. It's off by default.
$ git config --global rerere.enabled 1

Patching

$ git branch my_fix
$ git checkout my_fix
[edit]
$ git commit -a
$ git format-patch HEAD~1 --stdout >my_fix.patch
Or if you don't want to save this commit at all, revert after creating patch:
git reset --hard HEAD~1
(Committing is good for storing the commit message.) Transfer my_fix.patch to someone who does:
$ git am my_fix.patch

Diffing UTF-16 (.strings) files

utf16toascii.py:
#!/usr/bin/env python
import sys
data = open(sys.argv[-1]).read()
ascii = data.decode('utf-16').encode('ascii', 'replace')
sys.stdout.write(ascii)
Shell:
$ echo "*.strings diff=xcode_strings" >> .gitattributes
$ git config diff.xcode_strings.textconv /path/to/utf16_to_ascii.py

Finding renames/deleted files

$ git log --diff-filter=DR --name-only

Tagging

$ git tag -a my_tag
$ git push --tags

Pushing contents of a submodule

$ cd my_submodule
$ git checkout -b topic
[edit]
$ git push origin HEAD (push submodule first)
$ cd ..
$ git commit -a
$ git push origin (push main repo afterwards)

Permanently purging a file from repo

$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch my_file'

Pulling in a submodule

$ git pull origin
$ git checkout topic_which_introduce_submodule
$ git submodule init
$ git submodule update

Creating a submodule

$ mkdir my_mod
$ cd my_mod
$ git init
[copy/edit]
$ git commit -a -m "Starting work on my module - yey!"
[create+push to remote, say ssh://domain.com/abc/my_mod.git]
$ cd ..
git submodule add ssh://domain.com/abc/my_mod.git my_mod
[drop stuff no longer needed]
git commit

Finding changes to a file in any branch

$ git log --all <file>
commit ff76f5235b46b7d488ded2c51361ad93ecf68136
commit f9cb7762267becb9136a24f208982680b12ba631
commit 53e81e63f13f7569e7118a58c4f9ca2b4b731dc3
$ git branch --contains f9cb7762267becb9136a24f208982680b12ba631

Simple commit-squashing

If you had a lot of merge-conflicts and (as I) forgot to enable rerere, do like this:
$ git log
$ git checkout -b my_squash
$ git reset --soft <commit before your squashables>
$ git commit

Cherry-picking

git cherry-pick cc9cdf189d1eb111ecda5fe7916347edea6025a6
Only use this when you don't want to merge the branch you're picking from. The most important reason for this is that merging retains commit SHA-1s, but cherry-picking does not; thus you lose tracability.

For example: I currently wanted a feature branch that only contained two commits, but was based on three branches that I didn't want. What I did was
$ git checkout master
$ git checkout -b my_feature_squash
$ git cherry-pick abc
$ git cherry-pick cde
<I edited a file>
$ git commit -a
$ git rebase -i HEAD~4
(I actually branched before rebasing to avoid rebase mistakes; nonetheless.) If you want to cherry-pick without committing it to the head of your current branch use
$git cherry-pick -n cc9cdf189d1eb111ecda5fe7916347edea6025a6

Viewing specific commit in different branch

git log topic@{e949f8c63c1df7059939199937a257d957fa3671}
The previous commit is actually shown first (concatenate a ^ to the refspec to skip).

Finding source-code changes

Use this alias in ~/.gitconfig:
[alias]
search = log --source --all -p --decorate=full -G
which allows you to
$ git search MyCall\\\(true\\\)
This will help you find your commit in any branch.

Check what branch a commit is in

git branch --contains <commit>

How to work on topics

  1. Head out from a common starting-point, usually in master
  2. git checkout -b topic
  3. Do your stuff, git commit, times 10.
  4. git checkout -b topic_squash
  5. git rebase master
  6. git rebase -i HEAD~10
If you release yourself:
  • git checkout master
  • git merge topic_squash
otherwise:
  • git push origin HEAD

Skipping diff on some content

Place the following line in your .gitattributes:
*.ma -diff

Git svn: getting a specific branch

Say you didn't clone the full svn repo, trunk/, branches/ and tag/ but just cloned trunk, and later realize you would like to work on a branch under (say) branches, take a look at this article. If that solves your problem, good! Only note that you need to point to a revision that really exist on that branch: pointing to the revision at which that branch was created won't cut it and the git svn fetch command will not get you anything. Worst case you may have to commit a new revision on that svn branch you'd like to work on, then do git svn fetch on that revision.

Cloing all remote branches

git clone ssh://...
cd my_project
git branch -a
* master
origin/master
origin/blahblah
git checkout -b blahblah origin/blahblah

Gitifying da prompt

Put in .bash_profile:
function show_git_branch {
  echo -n `git branch --no-color 2> /dev/null | sed -e '/^[^*] /d' -e 's/* \(.*\)/\1/'`
}

function has_local_changes {
  if ! git diff-files --quiet 2> /dev/null; then
    echo -n '*'
  fi
  if ! git diff-index --cached --quiet HEAD 2> /dev/null; then
    echo -n '!'
  fi
}

function has_push {
  HAS=`git rev-list -n 1 HEAD@{upstream}..HEAD 2> /dev/null`
  if [ "$HAS" != "" ]; then
    echo -n '->'
  fi
}

function show_git_info {
  if git rev-parse --git-dir > /dev/null 2>&1; then
    echo -ne "\033[0;32m ("
    show_git_branch
    echo -ne "\033[1;37m"
    has_local_changes
    has_push
    echo -ne "\033[0;32m)"
  fi
}

function proml {
  local LIGHT_BLUE="\[\033[0;94m\]"
  local        RED="\[\033[0;31m\]"
  local      RESET="\[\033[0;0m\]"
  case $TERM in
    xterm*)
    TITLEBAR='\[\033]0;\u@\h:\w\007\]'
    ;;
    *)
    TITLEBAR=""
    ;;
  esac

PS1="${TITLEBAR}\
$LIGHT_BLUE\$(date +%H:%M:%S) \
$RED\u@\h:\w\$(show_git_info) \
$RESET\$ "
PS2='> '
PS4='+ '
}
proml
Original thanks to Chris Wanstrath.

Changing last commit

Only use this on non-pushed commits. Otherwise apply another commit.
To change last commit message (use git revert to generate a reversing commit if you already pushed):
git commit --amend

To remove last commit entirely:
git reset --hard HEAD~1

To add/remove a file from last commit:
git add/rm file
git commit --amend

Moving last commit to staged, keeping files in working tree:
git reset --mixed HEAD~1

Discarding local changes

git checkout -- file
To throw everything away since last commit:
git reset --hard
To delete untracked files and directories:
git clean -f -d

Listing log messages AND changes

git log -p

Start working on a topic

git checkout -b topic_123 master
or, using my alias,
git nb topic_123 master

How to release

According to Juno C Hamano one should keep separate remote repos for all users, then create topic branches for each fix/feature/test, never do anything directly in master. Topic branches are pushed remotely to one's own repo. When the topic branch is ready for release, merge it down to master. Never merge master into the topic branch.

Git config

Config files located in
./.git/config
~/.gitconfig
/etc/gitconfig
Stuff I usually squeeze in there:
[core]
# Use under Windows:
autocrlf = true
# Use under *nix:
autocrlf = input
hideDotFiles = dotGitOnly
[alias]
ca = commit -a
nb = checkout -b
quicklog = log --pretty=format:\"%h %cr %cn %Cgreen%s%Creset\"
logdiff = log -p --stat
search = log --source --all -p --decorate=full -G
I also put the following in ./.gitattributes, which go into the repo:
* text=auto

Clone local -> remote

On remote machine:
cd /directory/to/
mkdir my_project.git
cd my_project.git/
git --bare init
On local machine:
cd my_project
git init
git add *
git commit -m "Started working on saving the world."
git remote add origin ssh://user@my.server.com:port/directory/to/my_project.git
git push --set-upstream origin master

Clone remote -> localhost

git clone ssh://user@my.server.com:port/~/subdir/to/repo.git

Check if commit xyz in remote repo

git fetch some_repo
git branch -r --contains xyz
>>> some_repo/feature_123
>>> some_repo/integration_x
>>> some_repo/bug_abc

About

This blog is here so I won't have to remember the git syntax. The syntax is ok for most of the commonly used stuff, but non-intuitive in others; especially to us who don't (want to) understand the semantics. Here we go.