# tricky

* [tricky](#tricky)
  * [hidden feature](#hidden-feature)
  * [quick edit gitconfig](#quick-edit-gitconfig)
  * [create git patch](#create-git-patch)
  * [get current branch](#get-current-branch)
  * [get previous branch](#get-previous-branch)
  * [quick push to current branch](#quick-push-to-current-branch)
  * [remove `warning: CRLF will be replaced by LF in xxx file` for `.gitattributes`](#remove-warning-crlf-will-be-replaced-by-lf-in-xxx-file-for-gitattributes)
  * [create multiple commits](#create-multiple-commits)
  * [revision](#revision)
  * [git commit](#git-commit)
  * [git path](#git-path)
  * [`.gitattributes`](#gitattributes)
  * [git summaries](#git-summaries)
  * [who-am-i](#who-am-i)
  * [find bug with bisect](#find-bug-with-bisect)
  * [view file at specific commit](#view-file-at-specific-commit)
  * [edit .gitignore after committing](#edit-gitignore-after-committing)
* [trailers](#trailers)
  * [git config](#git-config)
  * [generate trailers](#generate-trailers)
  * [show trailers](#show-trailers)
* [scripts](#scripts)
  * [fetch merge all](#fetch-merge-all)
  * [gfall](#gfall-branch)
  * [iGitOpt](#igitopt)
  * [hook](#hook)
* [git message](#git-message)
* [refspec](#refspec)
* [others](#others)
  * [alias](#alias)
  * [check help in previw.app](#check-help-in-previwapp)
  * [disk size](#disk-size)
  * [How is git commit sha1 formed](#how-is-git-commit-sha1-formed)

### tricky

{% hint style="info" %}
> references:
>
> * [git-tips/tips](https://github.com/git-tips/tips)
> * [git 的奇技淫巧](https://github.com/521xueweihan/git-tips)
> * [k88hudson/git-flight-rules](https://github.com/k88hudson/git-flight-rules/blob/master/README.md)
> * [git飞行规则(flight rules)](https://github.com/k88hudson/git-flight-rules/blob/master/README_zh-CN.md)
> * [firstaidgit.io](https://firstaidgit.io/)
> * [unixorn/git-extra-commands](https://github.com/unixorn/git-extra-commands)
> * [tj/git-extras](https://github.com/tj/git-extras)
> * [shell tricks: one git alias to rule them all](https://brettterpstra.com/2014/08/04/shell-tricks-one-git-alias-to-rule-them-all/)
{% endhint %}

#### hidden feature

[**git var**](https://git-scm.com/docs/git-var)

```bash
$ git var GIT_COMMITTER_IDENT
marslo <marslo.jiao@gmail.com> 1719963678 -0700

$ git var -l
```

[**git verify-commit**](https://git-scm.com/docs/git-verify-commit)

#### quick edit gitconfig

```bash
$ git config --edit --global

# quick repalce config
$ git config --global --replace-all core.pager cat
```

#### create git patch

```bash
$ git diff --no-color HEAD^..HEAD > <name>.patch

# or
$ git format-patch HEAD^^           # create 3 patch files automatically
$ git format-patch -1 <revision>    # create 1 patch file only
```

#### get current branch

```bash
$ git branch
  sandbox/marslo
* master
```

*   [`branch`](https://git-scm.com/docs/git-branch)

    ```bash
    $ git branch --show-current

    # or
    $ git branch --show

    # or
    $ git branch | sed -ne 's:^\*\s*\(.*\)$:\1:p'
    master
    ```
*   [`symbolic-ref`](https://git-scm.com/docs/git-symbolic-ref)

    ```bash
    $ git symbolic-ref --short HEAD
    master

    $ git symbolic-ref HEAD
    refs/heads/master
    ```
*   [`name-rev`](https://git-scm.com/docs/git-name-rev)

    ```bash
    $ git name-rev --name-only HEAD
    remotes/origin/master
    ```
*   `describe`

    ```bash
    $ git describe --contains --all HEAD
    master
    ```

#### get previous branch

**get previous branch name**

```bash
$ git rev-parse --symbolic-full-name @{-1}
refs/heads/sandbox/marslo/291
```

or

```bash
$ git describe --all $(git rev-parse @{-1})
heads/sandbox/marslo/291
```

[**checkout to previous branch**](https://stackoverflow.com/a/7207542/2940319)

```bash
$ git checkout -
```

*   or

    ```bash
    $ git checkout @{-1}
    ```

**quick diff with previous branch**

```bash
$ git diff ..@{-1}

# or
$ git diff @..@{-1}

# or
$ git diff HEAD..@{-1}
```

#### quick push to current branch

{% hint style="info" %}
* `@`

```bash
@ alone is a shortcut for HEAD.
```

> references:
>
> * [gitrevisions(7) Manual Page](https://mirrors.edge.kernel.org/pub/software/scm/git/docs/gitrevisions.html)
{% endhint %}

```bash
$ git push origin @

# or
$ git push origin HEAD
```

#### [remove `warning: CRLF will be replaced by LF in xxx file` for `.gitattributes`](https://help.github.com/en/github/using-git/configuring-git-to-handle-line-endings)

```bash
$ git add --all -u --renormalize .
```

*   or ignore the warning

    ```bash
    $ git config --global core.safecrlf false
    ```

#### [create multiple commits](https://git-rebase.io/)

```bash
$ for c in {0..10}; do
    echo "$c" >> squash.txt
    git add squash.txt
    git commit -m "add '${c}' to squash.txt"
done
```

#### revision

**the first revision**

```bash
$ git rev-list --max-parents=0 HEAD
```

#### git commit

[**emoji**](https://gist.github.com/risan/41a0e4a462477875217346027879f618)

#### git path

**get absolute root path**

```bash
$ git rev-parse --show-toplevel
```

**get relative root path**

```bash
$ git rev-parse --show-cdup
```

**get absolute root path inside submodules**

```bash
$ git rev-parse --show-superproject-working-tree
```

**get `.git` path**

```bash
$ git rev-parse --git-dir
```

**inside the work tree or not**

```bash
$ git rev-parse --is-inside-work-tree
```

#### `.gitattributes`

[**Refreshing the repository after committing .gitattributes**](https://www.droidship.com/posts/gitattributes/)

> reference:
>
> * [Please Add .gitattributes To Your Git Repository](https://dev.to/deadlybyte/please-add-gitattributes-to-your-git-repository-1jld)
> * [Git tip: Add a .gitattributes file to deal with line endings](https://www.droidship.com/posts/gitattributes/)
> * [gitattributes - Defining attributes per path](https://git-scm.com/docs/gitattributes)
> * [Force LF eol in git repo and working copy](https://stackoverflow.com/a/34810209/2940319)

```bash
$ rm -rf .git/index
# or
$ git rm --cached -r .
# or
$ git ls-files -z | xargs -0 rm

$ git reset --hard
```

[or](https://git-scm.com/docs/gitattributes)

```bash
$ echo "* text=auto" >.gitattributes
$ git add --renormalize .
$ git status        # Show files that will be normalized
$ git commit -m "Introduce end-of-line normalization"
```

**format**

> reference [Be a Git ninja: the .gitattributes file](https://medium.com/@pablorsk/be-a-git-ninja-the-gitattributes-file-e58c07c9e915)

```bash
$ cat .gitattributes
*             text=auto
*.sh          eol=lf
path/to/file  eol=lf
```

#### git summaries

**get repo active days**

```bash
$ git log --pretty='format: %ai' $1 |
      cut -d ' ' -f 2 |
      sort -r |
      uniq |
      awk '{ sum += 1 } END {print sum}'
```

**get commit count**

*   since particular commit

    ```bash
    $ git log --oneline <hash-id> |
          wc -l |
          tr -d ' '
    635
    ```
*   since the initial commit

    ```bash
    $ git log --oneline |
          wc -l |
          tr -d ' '
    780
    ```

**get all files count in the repo**

```bash
$ git ls-files | wc -l | tr -d ' '
```

**get contributors**

```bash
$ git shortlog -n -s -e
   110   marslo <marslo.jiao@gmail.com>
    31   marslo <marslo@xxx.com>
```

[collection](https://github.com/tj/git-extras/blob/master/bin/git-summary#L81)

```bash
$ git shortlog -n -s -e |
      awk ' {
        sum += $1
        if ($NF in emails) {
            emails[$NF] += $1
        } else {
            email = $NF
            emails[email] = $1
            # set commits/email to empty
            $1=$NF=""
            sub(/^[[:space:]]+/, "", $0)
            sub(/[[:space:]]+$/, "", $0)
            name = $0
            if (name in names) {
                # when the same name is associated with existed email,
                # merge the previous email into the later one.
                emails[email] += emails[names[name]]
                emails[names[name]] = 0
            }
            names[name] = email
        }
      } END {
        for (name in names) {
            email = names[name]
            printf "%6d\t%s\n", emails[email], name
      }
    }'
   141  marslo
```

**format the author**

```bash
$ git shortlog -n -s -e | awk '
  { args[NR] = $0; sum += $0 }
  END {
    for (i = 1; i <= NR; ++i) {
      printf "%s♪%2.1f%%\n", args[i], 100 * args[i] / sum
    }
  }
  ' | column -t -s♪ | sed "s/\\\x09/\t/g"
   110  marslo <marslo.jiao@gmail.com>  78.0%
    31  marslo <marslo@xxx.com>         22.0%
```

**show diff file only**

```bash
$ git log --numstat --pretty="%H" --author=marslo HEAD~3..HEAD
9fdb297ba0d2d51975e91d2b7e40fb5e96be4f5f

8       1       docs/artifactory/artifactory.md
095ec79c89d98831c0a485f55011bf81c6f712ad

49      11      docs/linux/disk.md
5       1       docs/osx/util.md
f15a40c8dea2927db54570268aca4203cd50a416

1       0       docs/SUMMARY.md
-       -       docs/screenshot/tools/ms/outlook-keychain-1.png
81      0       docs/tools/ms.md
```

**repo age**

```bash
$ git log --reverse --pretty=oneline --format="%ar" |
      head -n 1 |
      LC_ALL=C sed 's/ago//'
4 months
```

#### who-am-i

> \[!NOTE|label:references:]
>
> * [Can I hold git credentials in environment variables?](https://stackoverflow.com/a/78064753/2940319)
> * [git-tool credential](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage#_under_the_hood)

```bash
# show default credential
$ echo -e 'protocol=https\nhost=github.com' | git credential fill
protocol=https
host=github.com
username=marslo
password=gho_jzuA**************************1VRqXz

# show antoher credential with specific subpath
$ echo -e 'protocol=https\nhost=github.com/mdevapraba' | git credential fill
protocol=https
host=github.com/mdevapraba
username=marslojiao-mvl
password=ghp_ppHq*************************g1PXSvr
```

*   reject the cached

    ```bash
    $ echo -e 'protocol=https\nhost=github.com' | git credential reject
    ```

#### [find bug with bisect](https://dev.to/jagroop2001/20-git-command-line-tricks-every-developer-should-know-1i21)

```bash
$ git bisect start
$ git bisect bad                 # current commit is bad
$ git bisect good <commit-hash>  # a known good commit
```

#### [view file at specific commit](https://dev.to/jagroop2001/20-git-command-line-tricks-every-developer-should-know-1i21)

```bash
$ git show <commit-hash>:path/to/file
```

#### [edit .gitignore after committing](https://dev.to/jagroop2001/20-git-command-line-tricks-every-developer-should-know-1i21)

```bash
$ echo "node_modules/" >> .gitignore
$ git rm -r --cached node_modules/
$ git commit -m "Update .gitignore"
```

### trailers

> \[!NOTE|label:references:]
>
> * [`git interpret-trailers`](https://git-scm.com/docs/git-interpret-trailers)
> * [`git log`](https://git-scm.com/docs/git-log)
> * [`git commit`](https://git-scm.com/docs/git-commit)
> * [Git Trailers](https://alchemists.io/articles/git_trailers)
> * [mmcclimon/git-slog.pl](https://gist.github.com/mmcclimon/9410bfa7ef7d4302edd3bb49414f0b88)
> * [What is the Sign Off feature in Git for?](https://stackoverflow.com/a/35238070/2940319)
> * [Git magic keywords in commit messages (Signed-off-by, Co-authored-by, Fixes, ...)](https://stackoverflow.com/a/67040353/2940319)

#### git config

> \[!TIP|label:tips:]
>
> * if `trailer.sign.command` is not set, the default value is `git var GIT_COMMITTER_IDENT`
> * if `trailer.sign.key` set as `"Signed-off-by: "`, it will impacted the `git log --format=%(trailers:key=Signed-off-by:,valueonly,separator=%x2C)`

```bash
$ git config --global trailer.sign.key "Signed-off-by"
$ git config --global trailer.sign.ifmissing add
$ git config --global trailer.sign.ifexists doNothing
$ git config --global trailer.sign.command "echo \"$(git config user.name) <$(git config user.email)>\""

# or
$ cat ~/.gitconfig
[trailer "sign"]
  key               = Signed-off-by
  ifmissing         = add
  ifexists          = doNothing
  command           = echo \"$(git config user.name) <$(git config user.email)>\"
```

#### generate trailers

> \[!NOTE|label:references:]
>
> * [Git sign off previous commits?](https://stackoverflow.com/a/15667644/2940319)

```bash
$ git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p'

## commit-msg.sample

# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
         sort | uniq -c | sed -e '/^[   ]*1[    ]/d')" || {
        echo >&2 Duplicate Signed-off-by lines.
        exit 1
}
```

*   by template

    ```bash
    $ cat ~/.git-template
    Signed-off-by: Your Name <your.email@example.com>

    $ git config commit.template ~/.git-template
    ```

**commit**

```bash
$ git commit --signoff
# or
$ git commit -s
```

*   with control

    ```bash
    declare signed="$(git log -n1 --format='%(trailers:key=Signed-off-by,valueonly,separator=%x2C)' | command grep -q "$(git config user.email)"; echo $?)";
    if [ 0 -eq ${signed} ]; then
      OPT='commit --amend --allow-empty';
    else
      OPT='commit --signoff --amend --allow-empty';
    fi;
    ```
*   i.e.:

    ```bash
    [alias]
    ### [c]ommit [a]dd [a]all
    caa         = "!f() { \
                          git add --all; \
                          declare signed=\"$(git log -n1 --format='%(trailers:key=Signed-off-by,valueonly,separator=%x2C)' | command grep -q \"$(git config user.email)\"; echo $?)\"; \
                          if [ 0 -eq ${signed} ]; then \
                            git commit --amend --no-edit --allow-empty;\
                          else \
                            git commit --signoff --amend --no-edit --allow-empty;\
                          fi; \
                        }; f \
    ```

#### show trailers

> \[!NOTE|label:references:]
>
> * [Git - Detect if commit is signed off](https://stackoverflow.com/q/31261884/2940319)
> * [Git: How to list specific trailers (footers) in git-log format?](https://stackoverflow.com/a/60423945/2940319)

```bash
$ git log -n1 --format='%(trailers:key=Signed-off-by:,valueonly,separator=%x2C)'
marslo <marslo.jiao@gmail.com>

$ git log -n1 --format='%(trailers:key=Signed-off-by:,keyonly,separator=%x2C)'
Signed-off-by

# or
$ git log -n1 --format=%B | git interpret-trailers --parse
Signed-off-by: marslo <marslo.jiao@gmail.com>
Change-Id: I3cc1cb4cfaf4300d2e7972eb39a7319e81012c65

# or
$ git log -1 --pretty=format:"%b"
Signed-off-by: marslo <marslo.jiao@gmail.com>
Change-Id: I3cc1cb4cfaf4300d2e7972eb39a7319e81012c65

# or
$ git log --pretty=format:"%b" | command grep -E "^(Signed-off-by|Co-authored-by):"
```

**configure and format**

*   `Signed-off-by:`

    ```bash
    $ git config --global trailer.sign.key 'Signed-off-by: '
    $ git log -1 --format="%(trailers:key=Signed-off-by,valueonly,separator=%x2C)"

    $ git log -1 --format="%(trailers:key=Signed-off-by: ,valueonly,separator=%x2C)"
    marslo <marslo.jiao@gmail.com>
    ```
*   `Signed-off-by`

    ```bash
    $ git config --global trailer.sign.key 'Signed-off-by'

    $ git log -1 --format="%(trailers:key=Signed-off-by: ,valueonly,separator=%x2C)"

    $ git log -1 --format="%(trailers:key=Signed-off-by:,valueonly,separator=%x2C)"
    marslo <marslo.jiao@gmail.com>
    $ git log -1 --format="%(trailers:key=Signed-off-by,valueonly,separator=%x2C)"
    marslo <marslo.jiao@gmail.com>
    ```

### scripts

#### [fetch merge all](https://github.com/marslo/mylinux/blob/master/confs/home/.marslo/.gitalias#L183)

```bash
$ cat ~/.gitconfig
...
[alias]
  ua          = "!bash -c 'while read branch; do \n\
                   echo -e \"\\033[1;33m~~> ${branch}\\033[0m\" \n\
                   git fetch --all --force; \n\
                   if [ 'meta/config' == \"${branch}\" ]; then \n\
                     git fetch origin --force refs/${branch}:refs/remotes/origin/${branch} \n\
                   fi \n\
                   git rebase -v refs/remotes/origin/${branch}; \n\
                   git merge --all --progress refs/remotes/origin/${branch}; \n\
                   git remote prune origin; \n\
                   if git --no-pager config --file $(git rev-parse --show-toplevel)/.gitmodules --get-regexp url; then \n\
                     git submodule sync --recursive; \n\
                     git submodule update --init --recursive \n\
                   fi \n\
                 done < <(git rev-parse --abbrev-ref HEAD) '"
...
```

#### [gfall](https://github.com/marslo/mylinux/blob/master/confs/home/.marslo/bin/ig.sh#L117)

#### iGitOpt

* [ig.sh](https://raw.githubusercontent.com/marslo/mylinux/master/confs/home/.marslo/bin/ig.sh)

**`--stat`**

```bash
$ git diff --stat HEAD^ HEAD
 docs/programming/groovy/groovy.md |  1 +
 docs/vim/tricky.md                | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
 2 files changed, 61 insertions(+), 21 deletions(-)
```

*   [for particular account](https://stackoverflow.com/a/2528129/2940319)

    ```bash
    $ git --no-pager diff --author='marslo' --stat HEAD^ HEAD
     docs/programming/groovy/groovy.md |  1 +
     docs/vim/tricky.md                | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
     2 files changed, 61 insertions(+), 21 deletions(-)
    ```

**`--numstat`**

```bash
$ git --no-pager log --numstat --author="marslo" HEAD^..HEAD
commit c361ddf2687319f978bb4ec0069b4b996607615f (HEAD -> marslo, origin/marslo)
Author: marslo <marslo.jiao@gmail.com>
Date:   Wed Jul 28 22:21:03 2021 +0800

    add bufdo for vim

1   0   docs/programming/groovy/groovy.md
60  21  docs/vim/tricky.md
```

*   for total count of changes

    ```bash
    $ git log --numstat --pretty="%H" --author="marslo" HEAD^..HEAD |
          awk 'NF==3 {plus+=$1; minus+=$2} END {printf("+%d, -%d\n", plus, minus)}'
    +61, -21
    ```
*   or [for pretty format](https://stackoverflow.com/a/63501669/2940319)

    ```bash
    $ git log HEAD^..HEAD --numstat --pretty="%H" |
          awk 'NF==3 {added+=$1; deleted+=$2} NF==1 {commit++} END {printf("total lines added: +%d\ntotal lines deleted: -%d\ntotal commits: %d\n", added, deleted, commit)}'
    total lines added: +61
    total lines deleted: -21
    total commits: 1
    ```
*   [or](https://stackoverflow.com/a/61945239/2940319)

    ```bash
    $ git log --numstat --format="" HEAD^..HEAD |
          awk '{files += 1}{ins += $1}{del += $2} END{print "total: "files" files, "ins" insertions(+) "del" deletions(-)"}'
    total: 2 files, 61 insertions(+) 21 deletions(-)
    ```

    [git alias](https://stackoverflow.com/a/61945239/2940319)

    ```bash
    [alias]
    summary = "!git log --numstat --format=\"\" \"$@\" | awk '{files += 1}{ins += $1}{del += $2} END{print \"total: \"files\" files, \"ins\" insertions(+) \"del\" deletions(-)\"}' #"
    ```

[**`--shortstat`**](https://stackoverflow.com/a/41307958/2940319)

```bash
$ git diff --shortstat HEAD^..HEAD
 2 files changed, 61 insertions(+), 21 deletions(-)
```

*   or [check for multiple commits](https://stackoverflow.com/a/53338858/2940319)

    ```bash
    $ git diff  $(git log -5 --pretty=format:"%h" | tail -1) --shortstat
     7 files changed, 253 insertions(+), 24 deletions(-)
    ```

#### hook

> \[!NOTE|label:references:]
>
> * [git提交待审核代码，报错没有change-id的解决方法](https://www.cnblogs.com/yzhihao/p/8392704.html)
> * [git add Signed-off-by line using format.signoff not working](https://stackoverflow.com/a/46536244/2940319)

*   `commit-msg` for signed-off-by

    ```bash
    #!/bin/sh

    NAME=$(git config user.name)
    EMAIL=$(git config user.email)

    if [ -z "$NAME" ]; then
      echo "empty git config user.name"
      exit 1
    fi

    if [ -z "$EMAIL" ]; then
      echo "empty git config user.email"
      exit 1
    fi

    git interpret-trailers --if-exists doNothing --trailer \
        "Signed-off-by: $NAME <$EMAIL>" \
        --in-place "$1"
    ```
*   `commit-msg` for change-id

    ```bash
    #!/bin/sh
    # From Gerrit Code Review 2.6
    #
    # Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
    #
    # Copyright (C) 2009 The Android Open Source Project
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    # http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    #

    unset GREP_OPTIONS

    CHANGE_ID_AFTER="Bug|Issue"
    MSG="$1"

    # Check for, and add if missing, a unique Change-Id
    #
    add_ChangeId() {
      clean_message=`sed -e '
      /^diff --git a\/.*/{
      s///
      q
      }
      /^Signed-off-by:/d
      /^#/d
      ' "$MSG" | git stripspace`
      if test -z "$clean_message"; then return; fi

      # Does Change-Id: already exist? if so, exit (no change).
      if grep -i '^Change-Id:' "$MSG" >/dev/null; then return; fi

      id=`_gen_ChangeId`
      T="$MSG.tmp.$$"
      AWK=awk
      if [ -x /usr/xpg4/bin/awk ]; then
        # Solaris AWK is just too broken
        AWK=/usr/xpg4/bin/awk
      fi

      # How this works:
      # - parse the commit message as (textLine+ blankLine*)*
      # - assume textLine+ to be a footer until proven otherwise
      # - exception: the first block is not footer (as it is the title)
      # - read textLine+ into a variable
      # - then count blankLines
      # - once the next textLine appears, print textLine+ blankLine* as these
      # aren't footer
      # - in END, the last textLine+ block is available for footer parsing
      $AWK '
      BEGIN {
      # while we start with the assumption that textLine+
      # is a footer, the first block is not.
      isFooter = 0
      footerComment = 0
      blankLines = 0
      }

      # Skip lines starting with "#" without any spaces before it.
      /^#/ { next }

      # Skip the line starting with the diff command and everything after it,
      # up to the end of the file, assuming it is only patch data.
      # If more than one line before the diff was empty, strip all but one.
      /^diff --git a/ {
        blankLines = 0
        while (getline) { }
        next
      }

      # Count blank lines outside footer comments
      /^$/ && (footerComment == 0) {
        blankLines++
        next
      }

      # Catch footer comment
      /^\[[a-zA-Z0-9-]+:/ && (isFooter == 1) {
        footerComment = 1
      }

      /]$/ && (footerComment == 1) {
        footerComment = 2
      }

      # We have a non-blank line after blank lines. Handle this.
      (blankLines > 0) {
        print lines
        for (i = 0; i < blankLines; i++) {
          print ""
        }

        lines = ""
        blankLines = 0
        isFooter = 1
        footerComment = 0
      }

      # Detect that the current block is not the footer
      (footerComment == 0) && (!/^\[?[a-zA-Z0-9-]+:/ || /^[a-zA-Z0-9-]+:\/\//) {
        isFooter = 0
      }

      {
        # We need this information about the current last comment line
        if (footerComment == 2) {
          footerComment = 0
        }
        if (lines != "") {
          lines = lines "\n";
        }
        lines = lines $0
      }

      # Footer handling:
      # If the last block is considered a footer, splice in the Change-Id at the
      # right place.
      # Look for the right place to inject Change-Id by considering
      # CHANGE_ID_AFTER. Keys listed in it (case insensitive) come first,
      # then Change-Id, then everything else (eg. Signed-off-by:).
      #
      # Otherwise just print the last block, a new line and the Change-Id as a
      # block of its own.
      END {
      unprinted = 1
      if (isFooter == 0) {
      print lines "\n"
      lines = ""
      }
      changeIdAfter = "^(" tolower("'"$CHANGE_ID_AFTER"'") "):"
      numlines = split(lines, footer, "\n")
      for (line = 1; line <= numlines; line++) {
      if (unprinted && match(tolower(footer[line]), changeIdAfter) != 1) {
      unprinted = 0
      print "Change-Id: I'"$id"'"
      }
      print footer[line]
      }
      if (unprinted) {
      print "Change-Id: I'"$id"'"
      }
      }' "$MSG" > "$T" && mv "$T" "$MSG" || rm -f "$T"
    }

    _gen_ChangeIdInput() {
      echo "tree `git write-tree`"
      if parent=`git rev-parse "HEAD^0" 2>/dev/null`; then
        echo "parent $parent"
      fi
      echo "author `git var GIT_AUTHOR_IDENT`"
      echo "committer `git var GIT_COMMITTER_IDENT`"
      echo
      printf '%s' "$clean_message"
    }
    _gen_ChangeId() {
      _gen_ChangeIdInput |
      git hash-object -t commit --stdin
    }

    add_ChangeId
    ```

### git message

> \[!NOTE|label:references:]
>
> * [\* Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
> * [Commit Message Guidelines](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines)
> * [AngularJS Git Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit?pli=1\&tab=t.0#heading=h.uyo6cb12dt6w)
> * [Add Choice \[ chore, revert \] to question "Select the type of change you are committing" #36](https://github.com/commitizen-tools/commitizen/issues/36#issuecomment-520243444)

| TYPE       | FULL NAME     | SIMPLE DESCRIPTION     | DESCRIPTION                                                                                                 |
| ---------- | ------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------- |
| `build`    |               | 构建系统或外部依赖项的变化          | changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)         |
| `ci`       |               | CI 配置文件和脚本的更改          | changes to our ci configuration files and scripts (example scopes: travis, circle, browserstack, saucelabs) |
| `docs`     | documentation | 仅文档修改                  | documentation only changes                                                                                  |
| `feat`     | feature       | 新功能                    | a new feature                                                                                               |
| `fix`      | bugfix        | 修复问题                   | a bug fix                                                                                                   |
| `perf`     |               | 性能提高                   | a code change that improves performance                                                                     |
| `refactor` |               | 重构 (即不是新增功能, 也不是修改bug) | a code change that neither fixes a bug nor adds a feature                                                   |
| `style`    | formatting    | 格式 (不影响代码运行的变动)        | changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)      |
| `test`     |               | 增加测试                   | adding missing tests or correcting existing tests                                                           |
| `chore`    | maintain      | 构建过程或辅助工具的变动           | changes to the build process or auxiliary tools and libraries such as documentation generation              |

*   sample

    ```
    docs(changelog): update changelog to beta.5
    ```

    ```
    fix(release): need to depend on latest rxjs and zone.js

    The version in our package.json gets copied to the one we publish, and users need the latest of these.
    ```

### refspec

> \[!NOTE|label:references:]
>
> * [10.5 Git Internals - The Refspec](https://git-scm.com/book/en/v2/Git-Internals-The-Refspec)

### others

#### alias

{% hint style="info" %}
> references:
>
> * [putermancer/.gitconfig](https://gist.github.com/putermancer/841286/5bffd46cb2a1b6fbe385a16f8bc1ce14ec45ebb7)
> * [List Git aliases](https://stackoverflow.com/a/48697231/2940319)
> * [Shell Tricks: One Git Alias to Rule Them All](https://brettterpstra.com/2014/08/04/shell-tricks-one-git-alias-to-rule-them-all/)
> * [automatic documentation of gitconfig aliases using sed or awk](https://stackoverflow.com/q/53841043/2940319)
> * [git config alias escaping](https://stackoverflow.com/questions/38057261/git-config-alias-escaping/39616600#39616600)
{% endhint %}

**show git alias**

{% hint style="info" %}
```bash
$ git --list-cmds=alias

# or
$ git config --get-regexp '^alias\.'
```
{% endhint %}

```
[alias]
  # https://stackoverflow.com/q/53841043/2940319
  ### show [g]it alia[s]
  as         = "! bash -c '''grep --no-group-separator -A1 -e \"^\\s*###\" \"$HOME\"/.marslo/.gitalias | \n\
                              awk \"END{if((NR%2))print p}!(NR%2){print\\$0p}{p=\\$0}\" | \n\
                              sed -re \"s/( =)(.*)(###)/*/g\" | \n\
                              sed -re \"s:[][]::g\" | \n\
                              awk -F* \"{printf \\\"\\033[1;33m%-20s\\033[0m » \\033[0;34m%s\\033[0m\\n\\\", \\$1, \\$2}\" | \n\
                              sort \n\
                           '''"
```

*   [or](https://gist.github.com/putermancer/841286/5bffd46cb2a1b6fbe385a16f8bc1ce14ec45ebb7)

    ```
    [alias]
      alias = "!sh -c '[ $# = 2 ] && git config --global alias.\"$1\" \"$2\" && exit 0 || [ $# = 1 ] && [ $1 = \"--list\" ] && git config --list | grep \"alias\\.\" | sed \"s/^alias\\.\\([^=]*\\)=\\(.*\\).*/\\1@@@@=>@@@@\\2/\" | sort | column -ts \"@@@@\" && exit 0 || echo \"usage: git alias <new alias> <original command>\\n       git alias --list\" >&2 && exit 1' -"
    ```
*   [or](https://stackoverflow.com/a/48697231/2940319)

    ```
    [alias]
      aliases = !git config --get-regexp ^alias\\. | sed -e s/^alias.// -e s/\\ /\\ $(printf \"\\043\")--\\>\\ / | column -t -s $(printf \"\\043\")
    ```
*   [or](https://brettterpstra.com/2014/08/04/shell-tricks-one-git-alias-to-rule-them-all/)

    ```
    $ git config --global --get-regexp alias |
          awk -v nr=2 '{sub(/^alias\./,"")}; \
                       { printf "\033[31m%_10s\033[1;37m", $1}; \
                       {sep=FS}; \
                       {for (x=nr; x<=NF; x++) \
                       { printf "%s%s", sep, $x; }; \
                       print "\033[0;39m"}'
    ```

    *   `finda`

        ```
        [alias]
          finda = "!grepalias() { git config --global --get-regexp alias | grep -i \"$1\" | awk -v nr=2 '{sub(/^alias\\./,\"\")};{printf \"\\033[31m%_10s\\033[1;37m\", $1};{sep=FS};{for (x=nr; x<=NF; x++) {printf \"%s%s\", sep, $x; }; print \"\\033[0;39m\"}'; }; grepalias"
        ```
*   or [`show-cmd`](https://stackoverflow.com/a/62772985/2940319)

    ```
    [alias]
        show-cmd = "!f() { \
            sep="㊣" ;\
            name=${1:-alias};\
            echo -n -e '\\033[48;2;255;255;01m' ;\
            echo -n -e '\\033[38;2;255;0;01m' ;\
            echo "$name"; \
            echo -n -e '\\033[m' ;\
            git config --get-regexp ^$name\\..*$2+ | \
            cut -c 1-40 | \
            sed -e s/^$name.// \
            -e s/\\ /\\ $(printf $sep)--\\>\\ / | \
            column -t -s $(printf $sep) | \
            sort -k 1 ;\
        }; f"
    ```

**ls**

{% hint style="info" %}
> references:
>
> * [putermancer/.gitconfig](https://gist.github.com/putermancer/841286/5bffd46cb2a1b6fbe385a16f8bc1ce14ec45ebb7)
> * [taxilian/.gitconfig](https://gist.github.com/taxilian/1338308)
{% endhint %}

```
[alias]
  ls           = "!git status -suno"
  ls-modified  = "!git status --porcelain -uno | awk 'match($1, /M/) {print $2}'"
  ls-added     = "!git status --porcelain -uno | awk 'match($1, /A/) {print $2}'"
  ls-deleted   = "!git status --porcelain -uno | awk 'match($1, /D/) {print $2}'"
  ls-renamed   = "!git status --porcelain -uno | awk 'match($1, /R/) {print $2}'"
  ls-copied    = "!git status --porcelain -uno | awk 'match($1, /C/) {print $2}'"
  ls-updated   = "!git status --porcelain -uno | awk 'match($1, /U/) {print $2}'"
  ls-staged    = "!git status --porcelain -uno | grep -P '^[MA]' | awk '{ print $2 }'"
  ls-untracked = "!git status --porcelain -uall | awk '$1 == \"??\" {print $2}'"
```

**git alias escaping**

```
[alias]
  # https://stackoverflow.com/a/39616600/2940319
  # Quote / unquote a sh command, converting it to / from a git alias string
  quote-string = "!read -r l; printf \\\"!; printf %s \"$l\" | sed 's/\\([\\\"]\\)/\\\\\\1/g'; printf \" #\\\"\\n\" #"
  quote-string-undo = "!read -r l; printf %s \"$l\" | sed 's/\\\\\\([\\\"]\\)/\\1/g'; printf \"\\n\" #"
```

#### [check help in previw.app](https://brettterpstra.com/2014/08/05/shell-tricks-man-pages/)

```bash
$ MANWIDTH=80 MANPAGER='col -bx' git help rev-parse |
              groff -P-pa4 -Tps -mandoc -c |
              open -f -a Preview.app
```

#### [disk size](https://git-scm.com/docs/git-rev-list#Documentation/git-rev-list.txt)

```bash
# reachable objects
$ git rev-list --disk-usage --objects --all

# plus reflogs
$ git rev-list --disk-usage --objects --all --reflog

# total disk size used
$ du -c .git/objects/pack/*.pack .git/objects/??/*
# alternative to du: add up "size" and "size-pack" fields
$ git count-objects -v

# report the disk size of each branch
$ git for-each-ref --format='%(refname)' |
  while read branch; do
    size=$(git rev-list --disk-usage --objects HEAD..$branch)
    echo "$size $branch"
  done |
  sort -n

# compare the on-disk size of branches in one group of refs, excluding another
$ git rev-list --disk-usage --objects --remotes=$suspect --not --remotes=origin
```

#### [How is git commit sha1 formed](https://gist.github.com/masak/2415865)

> \[!NOTE|label:references:]
>
> * [How is the Git hash calculated?](https://stackoverflow.com/q/35430584/2940319)
