Advanced Git


More delicate Git commands

Here goes more advanced Git commands not found among the basic ones.

for-each-ref

Display info in .git/refs.

#!/bin/sh

git for-each-ref --shell --format="ref=%(refname)" refs/tags | \
while read entry
do
  eval "$entry"
  echo `dirname $ref`
done

This modified example taken from the one in the official manual pipes

ref=refs/tags/181116
ref=refs/tags/copyBtn0
ref=refs/tags/fa531
ref=refs/tags/solarized

to a while loop. In each iteration, the read command reads each line in the piped output and sets it into the variable entry. The command will exit normally unless it meets an EOF, so that the code inside can be executed. The variable eval set the shell variable ref to the output Git reference name. dirname chops off the tag names (181116, fa531, etc) and returns refs/tags.

Using refname:short will give short reference names that we usually type in the terminal. The wildcard * has to be escaped by an antislash \ in the last argument.

To remove remote branches (both local and remote copies), xargs won’t work because it’s impossible to deal with the white space following the colon in the command ... | xargs git push origin :. A while loop has to be used to loop through all matching refnames.

The following command deletes all remote branches whose name begins with staticman in both the remote Git server and the local repo.

git for-each-ref --shell --format="%(refname:short)" refs/remotes/origin/staticman\* | \
grep -o 'staticman_[a-z0-9-]*' | while IFS="" read -r branch; do
  git push origin :$branch
done

ls-files, ls-tree

List files (resp. files in tree). ls-tree displays a table with permissions, Git object types, full SHA1 hashes and file names.

Examples

$ git ls-files
.gitignore
.gitlab-ci.yml
.gitmodules
LICENSE
archetypes/default.md
...
static/js/katex-macros.js
staticman.yml
themes/beautifulhugo

$ git ls-tree origin/master:content/page
100644 blob 1604fbe12feb561b557eab2e7fa5ec95bc68a649    about.md
040000 tree 599c4da34630b2068121a66cf3e82e3e560e3590    advanced-git
040000 tree 10ec0300326e712dc468e4a47826f6056c5eb4a4    bash-commands
100644 blob bd6da925f4118b83fc42eb9af4c33efe53b7ab2c    math-se-comment-templates.md
040000 tree 6a196a2c445425ab4e05940dc84257ad002516fa    sublime

$ git ls-tree -r origin/master:content/page
100644 blob 1604fbe12feb561b557eab2e7fa5ec95bc68a649    about.md
100644 blob 5dccbc2c2dfee0bb50809292019af599aee01e4b    advanced-git/index.md
100644 blob ef7356ed7a22d0acc1fc4bfcf089828852b1b890    bash-commands/180826153752-xubu1804-w400.png
100644 blob d85403db306fde697fd18c19723ca9aa54bd9496    bash-commands/180826153752-xubu1804.png
100644 blob dd65a9113abd99da6586dfb176cda5c2a88392ba    bash-commands/index.md
100644 blob bd6da925f4118b83fc42eb9af4c33efe53b7ab2c    math-se-comment-templates.md
100644 blob 472b544e4497be91d40af7aa937ca8c7f1f9ac53    sublime/index.md

Options for ls-tree:

  • -r: recursive
  • --names-only: print only the file names

Applications

For iteration over cached files only if the file name is “regular enough”. These two commands are synonymous to each other.

Missing EOF detection

Crude version:

!# /bin/sh

g ls-files 1_chapter | \
while IFS= read -r file; do
    echo $file
    tail -c1 $file | od -c
    test `tail -c1 $file` && echo 'missing EOF!' || echo 'has EOF'
done

The output on this GitHub repo would look like

1_chapter/bubbleSort.jl
0000000   )
0000001
missing EOF!
1_chapter/comprehension.jl
0000000  \n
0000001
has EOF

See test for an explanation for what test does.

merge-base

Return the SHA1 hash of the youngest common ancestor of two branches.

$ git merge-base master dev

merge –squash, rebase -i

merge --squash <src> condenses commits into one on the current branch without touching src.

rebase, meaning “re-base”, is analogous to a pot transplant. The -i flag opens an interactive session for that. The text editor opened is determined by the parameter core.editor.

Applications

Both merge --squash and rebase can be used for cleaning the commit history. The former can be used if <src> branch is to be thrown away. In rebase -i, one can also squash Git commits.

reflog

Track the SHA1 hash that HEAD represents.

Applications

Find lost commit.

rev-parse

Return the SHA1 hash that Git ref represents.

$ rev-parse FETCH_HEAD  # head of fetched commits

Applications

Verify repo status.

diff

Some advanced options that enable us to spot out differences invisible using normal commands.

  • --ws-error-highlight=<kind>: highlight whitespace errors at lines of type <kind> in git diff’s output. <kind> is a comma-separated list ofnew (+), old (-) and context (neither + nor -) lines. all is a shorthand for old,new,context.
  • --ignore-all-space
  • --ignore-blank-lines
  • --ignore-space-at-eol
  • --ignore-space-change
  • --ignore-submodules

fsck

Run a file system check. I use this for retrieving --unreachable commits. The --no-reflogs overturns the default behavior of treating commits in the reflog as reachable.

The output has three columns.

$ git fsck --full --unreachable --no-reflogs
Checking object directories: 100% (256/256), done.
Checking objects: 100% (502/502), done.
unreachable tree 1e004e2884574d5a17b290632e4fb55e3feae750
unreachable blob 5300d869180014538a6081418a0e6984c0c4130d
unreachable tree 7720282acf3279de0508633055082a7bd893d732
...

We can

  1. apply a filter on 2nd column to get commit type
  2. map the third column to a meaningful one line summary
  3. sort by reversed chronological order
  4. list only the first N entries.
$ git fsck --full --unreachable --no-reflogs | \
awk '/commit/ {
    system("git -P show --no-patch --format=format:\"%h <%an> %as %s%n\" " $NF)
}' | sort -rk3 | head -n3
Checking object directories: 100% (256/256), done.
Checking objects: 100% (502/502), done.
33c5ee5 <Vincent Tam> 2021-08-23 Fixed two recent posts' heading level
61f9eea <Vincent Tam> 2019-05-10 Bash & Git: added  section
b771614 <Vincent Tam> 2019-05-09 Pages: Git & Bash

(Last modified on August 23, 2021)