tricky
Last updated
Was this helpful?
Last updated
Was this helpful?
Was this helpful?
$ git var GIT_COMMITTER_IDENT
marslo <marslo.jiao@gmail.com> 1719963678 -0700
$ git var -l
$ git config --edit --global
# quick repalce config
$ git config --global --replace-all core.pager cat
$ 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
$ git branch
sandbox/marslo
* master
$ git branch --show-current
# or
$ git branch --show
# or
$ git branch | sed -ne 's:^\*\s*\(.*\)$:\1:p'
master
$ git symbolic-ref --short HEAD
master
$ git symbolic-ref HEAD
refs/heads/master
$ git name-rev --name-only HEAD
remotes/origin/master
describe
get previous branch name
$ git rev-parse --symbolic-full-name @{-1}
refs/heads/sandbox/marslo/291
or
$ git describe --all $(git rev-parse @{-1})
heads/sandbox/marslo/291
$ git checkout -
or
$ git checkout @{-1}
quick diff with previous branch
$ git diff ..@{-1}
# or
$ git diff @..@{-1}
# or
$ git diff HEAD..@{-1}
$ git push origin @
# or
$ git push origin HEAD
$ git add --all -u --renormalize .
or ignore the warning
$ git config --global core.safecrlf false
$ for c in {0..10}; do
echo "$c" >> squash.txt
git add squash.txt
git commit -m "add '${c}' to squash.txt"
done
the first revision
$ git rev-list --max-parents=0 HEAD
get absolute root path
$ git rev-parse --show-toplevel
get relative root path
$ git rev-parse --show-cdup
get absolute root path inside submodules
$ git rev-parse --show-superproject-working-tree
get .git
path
$ git rev-parse --git-dir
inside the work tree or not
$ git rev-parse --is-inside-work-tree
.gitattributes
Refreshing the repository after committing .gitattributes
reference:
$ rm -rf .git/index
# or
$ git rm --cached -r .
# or
$ git ls-files -z | xargs -0 rm
$ git reset --hard
$ 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
$ cat .gitattributes
* text=auto
*.sh eol=lf
path/to/file eol=lf
get repo active days
$ 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
$ git log --oneline <hash-id> |
wc -l |
tr -d ' '
635
since the initial commit
$ git log --oneline |
wc -l |
tr -d ' '
780
get all files count in the repo
$ git ls-files | wc -l | tr -d ' '
get contributors
$ git shortlog -n -s -e
110 marslo <marslo.jiao@gmail.com>
31 marslo <marslo@xxx.com>
$ 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
$ 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
$ 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
$ git log --reverse --pretty=oneline --format="%ar" |
head -n 1 |
LC_ALL=C sed 's/ago//'
4 months
[!NOTE|label:references:]
# 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
$ echo -e 'protocol=https\nhost=github.com' | git credential reject
$ git bisect start
$ git bisect bad # current commit is bad
$ git bisect good <commit-hash> # a known good commit
$ git show <commit-hash>:path/to/file
$ echo "node_modules/" >> .gitignore
$ git rm -r --cached node_modules/
$ git commit -m "Update .gitignore"
[!NOTE|label:references:]
[!TIP|label:tips:]
if
trailer.sign.command
is not set, the default value isgit var GIT_COMMITTER_IDENT
if
trailer.sign.key
set as"Signed-off-by: "
, it will impacted thegit log --format=%(trailers:key=Signed-off-by:,valueonly,separator=%x2C)
$ 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)>\"
[!NOTE|label:references:]
$ 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
$ cat ~/.git-template
Signed-off-by: Your Name <your.email@example.com>
$ git config commit.template ~/.git-template
commit
$ git commit --signoff
# or
$ git commit -s
with control
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.:
[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 \
[!NOTE|label:references:]
$ 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:
$ 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
$ 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>
$ 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) '"
...
--stat
$ git diff --stat HEAD^ HEAD
docs/programming/groovy/groovy.md | 1 +
docs/vim/tricky.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
2 files changed, 61 insertions(+), 21 deletions(-)
$ 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
$ 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
$ 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
$ 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
$ 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(-)
[alias]
summary = "!git log --numstat --format=\"\" \"$@\" | awk '{files += 1}{ins += $1}{del += $2} END{print \"total: \"files\" files, \"ins\" insertions(+) \"del\" deletions(-)\"}' #"
$ git diff --shortstat HEAD^..HEAD
2 files changed, 61 insertions(+), 21 deletions(-)
$ git diff $(git log -5 --pretty=format:"%h" | tail -1) --shortstat
7 files changed, 253 insertions(+), 24 deletions(-)
[!NOTE|label:references:]
commit-msg
for signed-off-by
#!/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
#!/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
[!NOTE|label:references:]
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
仅文档修改
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.
[!NOTE|label:references:]
show git alias
[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\
'''"
[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' -"
[alias]
aliases = !git config --get-regexp ^alias\\. | sed -e s/^alias.// -e s/\\ /\\ $(printf \"\\043\")--\\>\\ / | column -t -s $(printf \"\\043\")
$ 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
or
ls
[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\" #"
$ MANWIDTH=80 MANPAGER='col -bx' git help rev-parse |
groff -P-pa4 -Tps -mandoc -c |
open -f -a Preview.app
# 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
[!NOTE|label:references:]
$ git describe --contains --all HEAD
master
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
[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"
[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"