#title Git cheatsheet
#author Stefan Hornburg (Racke)
#lang en
#topics Git, cherry, branch, tag, tracking, filter, diff, bash, stash
#teaser Git Cheatsheet - Quick commandline recipes
** Commits
*** Lists and Graphs
List just the hash and the commit message for the ten most recent commits:
git log --pretty=oneline -n 10
Show a nice graph:
git log --oneline --graph --decorate
*** Display and listing files
**** Display list of files in a commit
Last commit:
git show --pretty="format:" --name-only
Commit before that:
git show --pretty="format:" --name-only HEAD~1
**** Display file contents for a given revision
git show ee63c9b61122bfe7e24f8512a3359157ae10066b:components/customer_info
**** Display file contents for in a branch
git show master:components/customer_info
**** Log commits for a file that was moved or removed
git log -- components/customer_info
**** List untracked files
git ls-files --others --exclude-standard
**** List files deleted in the working directory
git ls-files -d
To restore them:
git checkout `git ls-files -d`
**** Display list of files with the last commit date
racke@ambas:$ for file in $(find lib/ -type f ); \
do DT=$(git log -n 1 --pretty=%ci -- $file;); echo "$file:$DT "; done
lib/PerlDance.pm:2016-09-05 15:58:07 +0200
lib/PerlDance/Routes.pm:2016-08-08 14:42:54 +0200
lib/PerlDance/Filter/Wiki.pm:2015-09-15 14:39:36 +0200
lib/PerlDance/Filter/MongerGroups.pm:2016-08-19 11:09:05 +0200
lib/PerlDance/Filter/Markdown.pm:2015-08-06 15:53:25 +0200
*** Merges
**** Show all commits in a merge
#commitsinmerge
Given that the hashref of the merge is =6d31d51=, you can use the following command to show
all commits in that merge:
git log 6d31d51^..6d31d51
*** Store empty directory in the Git repository
#emptyfolder
In case you want to keep a directory for dynamic content in the Git repository, but no files in it, use the following =.gitignore= file:
# Explanation about the purpose of the directory
*
!.gitignore
*** Removing and purging files
**** Remove dynamic files
#removedynamicfiles
Sometimes you accidentally add dynamically created files to the Git repository.
In this case, you usually want to remove the file from the repository but not
from the working directory.
You can do this as follows, e.g for the file =adjtime=:
git rm --cached adjtime
echo adjtime >> .gitignore
git add .gitignore
git commit -m "Remove dynamically generated file"
**** Purge untracked files
Please *doublecheck first* that you don't remove
files with **valuable content**.
Purge untracked files:
git clean -f ...
Purge untracked files including directories:
git clean -df sql/
*** Reverting commits
**** Reverting multiple commits
If the commits are consecutive, you can do:
git revert 4ea5493d9ac1d6ebcdb0bd29d70275e82ef23467^..d56fe7e13df7ec26c73d744077d7b079b70cc029
Where 4ea5493
is the oldest and d56fe7e
is the newest commit to revert.
Please note that Git will still create one revert commit for each of the commits to be reverted.
*** Get rid of Git commits
Remove the last three commits (~3):
git reset --hard HEAD~3
Please note that you **will loose uncommitted** work as well.
*** "Root" commits
The following command shows you all parentless (root) commits accessible from current branch,
useful to find the **initial** commit in the repository:
git rev-list --max-parents=0 HEAD
It is useful to create an empty commit as "root" commit as the "root" commit
cannot be rebased:
{{{
$ git commit --allow-empty -m "Initial commit."
[master (root-commit) 99dbf81] Initial commit.
}}}
*** Signing commits/tags
**** Force signing
=git config --global commit.gpgsign true=
**** Multiple keys
If you have multiple keys with the same email address, you can configure the correct one as follows:
git config --global user.signingkey FA2720F8
**** Signing on remote hosts
#signremote
Add extra socket to your GPG agent configuration file (=~/.gnupg/gpg-agent.conf=):
extra-socket /path/to/extra-socket
*** Stage
Stage changes for the next commit:
git add README.md
Display staged files:
git diff --name-only --cached
Unstage changes:
git reset HEAD
** Tags
*** Show all tags and corresponding messages
git tag -l '*' -n 1
*** Display a tag
{{{
$ git show 6.2.70
tag 6.2.70
Tagger: Stefan Hornburg (Racke)
Date: Thu Nov 10 12:41:43 2022 +0100
[-release]Preparing version 6.2.70
-----BEGIN PGP SIGNATURE-----
...
}}}
*** Add a signed tag
{{{
git tag -a -s -m "Release 0.21" v0.21
}}}
*** Push tags to remote repository
Tags are not automatically pushed with =git push=:
{{{
git push --tags
}}}
** Diffs & Logs
*** Show all files changed in a branch
{{{
$ git checkout mybranch
$ git diff --name-only master
...
}}}
*** Compare releases
git diff 2.0.0 2.0.3
The diff can be restricted to changes in a file or a directory:
git diff 2.0.0:tasks 2.0.3:tasks
*** Commits
Diff for the last three commits:
{{{
git diff HEAD~3 HEAD
}}}
*** Ignore whitespace
Options for a diff ignoring whitespaces:
--ignore-space-at-eol :: Changes at end of line (e.g. LF to CRLF)
-b :: As above plus collapses multi whitespaces into one
-w :: Ignores all whitespace changes (usually not what you want ...)
*** Reverse diff
git diff -R
This can be useful, e.g to display deleted whitespace.
*** Formatting
Show the short hash:
{{{
git log --abbrev-commit
}}}
** Remotes
#remotes
*** Display remotes
Short form (just the names):
{{{
~# git remote
origin
upstream
}}}
Long form (includes URL):
{{{
~# git remote -v
origin git@github.com:racke/dancer2.git (fetch)
origin git@github.com:racke/dancer2.git (push)
upstream git@github.com:PerlDancer/Dancer2.git (fetch)
upstream git@github.com:PerlDancer/Dancer2.git (push)
}}}
*** Add remote repository
{{{
~# git remote add origin git@github.com:racke/dancer2.git
}}}
*** Rename remote repository
{{{
~# git remote rename origin upstream
}}}
*** Change URL for remote repository
{{{
~# git remote set-url upstream git@github.com:racke/dancer2.git
}}}
*** Reset after remote rebase
{{{
~# git reset --hard origin/topic/returns-user-control-3964
}}}
Please note that you **will loose uncommitted** changes.
** Checkout
*** Checkout a new branch from a tag
#gitcheckout
Creates a new branch *topic/v2.1-fixes* from tag *v2.1* and switches to it:
$ git checkout -b topic/v2.1-fixes v2.1
Switched to a new branch 'topic/v2.1-fixes'
** Stash
#gitstash
*** Add to stash
With message:
{{{
$ git stash save "Old cruft."
}}}
*** List stash entries
{{{
$ git stash list
}}}
Show list with creation date of the entries:
{{{
$ git stash list --date=local
}}}
*** Show stash contents
{{{
$ git stash show -p stash@{1}
}}}
*** Include untracked files
git stash -u
git stash save -u "Stash away experiments"
*** Stash specific file(s)
{{{
git stash push default/web_tt2/subscriber_table.tt2
}}}
Stash a directory including untracked files:
{{{
git stash push -u roles/sympa
}}}
*** Retrieve stash
{{{
$ git stash pop stash@{1}
On branch topic/header/from-as-envelope-sender
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: src/lib/Sympa/Spindle/ToList.pm
no changes added to commit (use "git add" and/or "git commit -a")
Dropped stash@{1} (9579fe0d020a8e63733dff961fc3bd0865100959)
}}}
*** Pick a file from the stash
{{{
git checkout stash@{0} -- src/lib/Sympa/Bulk.pm
}}}
** Branches
*** Tracking branches (set upstream)
Track remote branch *pr-fix-typo*:
git branch -u origin/pr-fix-typo
This effectively sets the upstream for the current branch to *origin/pr-fix-typo*.
*** Compare files between branches
git diff master:README pr/fix-typo:README
This also works for directories.
*** Files added in a branch
This lists all files added in the branch *topic/transaction-status*
and not present in the *master* branch.
git diff --name-only --diff-filter=A master topic/transaction-status
Similar for modified files only:
git diff --name-only --diff-filter=M master topic/transaction-status
*** Find branch for a commit
#findcommitbranch
Local repository:
{{{
$ git branch --contains c0cc8e3e185e1de308b0c86b01f8fccc98dd4485
topic/imap-search-header-2020-03-12
}}}
Remote repositories:
{{{
$ git branch -r --contains c0cc8e3e185e1de308b0c86b01f8fccc98dd4485
github/topic/imap-search-header-2020-03-12
origin/topic/imap-search-header-2020-03-12
}}}
*** First commit for a branch
#firstcommitbranch
Show starting point of the branch in relationship to the *main* branch.
{{{
git log --reverse main..topic/pr-foo-bar
}}}
*** Show commits in a branch not merged into master
git cherry -v master feature/mobile
You can also compare to a remote branch:
git cherry -v master origin/feature/mobile
If there are no unmerged commits left, you can delete the local and remote branch with the following commands:
git branch -d feature/mobile
git push origin :feature/mobile
*** Merge base
#mergebase
The merge base is the common commit where the current branch diverged from.
You can determine this commit with the *merge-base* command:
{{{
git merge-base master topic/org-addons
}}}
It is quite a common case to only see the logs or the diff of the changes
in your current branch since they diverged.
But the following command will also consider the changes in master since the diverge:
{{{
git diff master..topic/org-addons
}}}
Instead you can use
{{{
git diff $(git merge-base master topic/org-addons)..topic/org-addons
}}}
or the shorthand (**three** dots):
{{{
git diff master...topic/org-addons
}}}
The same applies of course to the *log* command as well.
*** Just show the name of the current branch
git symbolic-ref --short HEAD
*** Rename branch
This renames the current branch. It doesn't affect the remote branch.
git branch -m topic/newname
To rename a branch other than the current one:
{{{
git branch -m topic/oldname topic/newname
}}}
In order to rename the remote branch we are doing two steps.
First we delete the old remote branch and push the new local branch:
{{{
git push origin :topic/oldname topic/newname
}}}
Second we update the remote tracking information:
{{{
git branch -u origin/topic/newname
}}}
*** Move branch
Move from =master= to =main=:
{{{
git branch -M main
}}}
*** Merged and unmerged branches
Show merged branches:
git branch --merged
Show remote merged branches:
git branch -r --merged
Show unmerged branches:
git branch --no-merged
*** Sorted by age
#gitbranchsortedbyage
This sorts local branches by date of the latest commit and displays the commit date and the branch name:
for C in $(git for-each-ref --sort=committerdate refs/heads --format='%(refname)');
do
git show -s --format="%ci $C" "$C";
done
Same for remote branches from *origin*:
git fetch origin
for C in $(git for-each-ref --sort=committerdate refs/remotes/origin --format='%(refname)');
do
git show -s --format="%ci $C" "$C";
done
** Configuration
*** Name and email
{{{
git config --global user.name "Stefan Hornburg (Racke)"
git config --global user.email "racke@linuxia.de"
}}}
** Break out part of the repository
#gitbreakoutnewrepo
One example is to extract common code out of a Perl module to start a separate module,
but keep the history on the files involved, e.g. [[https://github.com/interchange/interchange6-schema/issues/208][extract code out of Interchange6::Schema into Test::DBIC::Roo]].
First step is to clone the repository into a new directory:
git clone git@github.com:interchange/interchange6-schema.git Test-DBIC-Roo
cd Test-DBIC-Roo
#removefromhistory
Now we decide which files respective directories we are going to remove from
the repository:
FILE_LIST=".gitignore .travis.yml bin CHANGES lib/Interchange6/Schema lib/Interchange6/Schema.pm lib/Interchange6/Test/Role/Fixtures.pm Makefile.PL MANIFEST MANIFEST.SKIP README t/inflatecolumn_datetime.t t/lib"
Go through the commit history and check if everything is fine for you.
If yes, the next step is to rewrite the history of the repository:
git filter-branch \
--index-filter "git rm -r --cached --ignore-unmatch $FILE_LIST" \
--prune-empty \
HEAD
If yes, clean out Git objects which are no longer relevant to the new repository:
git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
git reflog expire --expire=now --all
git gc --prune=now
Another use case is the removal of a file and its history due to legal or privacy reason.
git filter-branch --tree-filter somefile.txt HEAD
** Shell
#shellprompt
It is quite helpful to show Git related information in your shell prompt.
If you are using the =~/.bashrc= from the =/etc/skel= directory on Debian your shell prompts are configured as:
{{{
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
}}}
So a prompt could look like:
{{{
racke@linuxia:~/provisioning/ansible$
}}}
After we modified the prompts to include the Git branch information, the definitions look like:
{{{
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\[\033[01;35m\]$(__git_ps1 " (%s)")\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w$(__git_ps1 " (%s)")\$ '
fi
}}}
After reloading your =~/.bashrc= the prompt will change to include the Git branch information for a directory (if any):
{{{
racke@linuxia:~/provisioning/ansible (master)$
}}}
We can also add further information with setting the *GIT_PS1_SHOWDIRTYSTATE* environment variable.
So if you have changes in this working directory, the prompt shows an asterisk after the branch name:
{{{
racke@linuxia:~/provisioning/ansible (master *)$
}}}