#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 *)$ }}}