git 的“子命令”到底是怎么找到的?为什么往 PATH 里放个 git-xxx 就多了一个 git xxx?别名能不能覆盖内置命令、和外部命令谁优先?shell 别名在哪个目录运行?这篇用几个小脚本逐个验证。
$ git hello 甲 乙
我是外部子命令 git-hello,参数: 甲 乙
当前目录: l70
GIT_EXEC_PATH 已设置: 是
$ git --exec-path | sed "s#.*/Developer#<Xcode>/Developer#"; ls $(git --exec-path) | wc -l | sed "s/^ */exec-path 目录里的文件数: /"
<Xcode>/Developer/usr/libexec/git-core
exec-path 目录里的文件数: 176
$ git help -a | sed -n "/^External commands/,/^$/p"
External commands
hellogit 找不到内置的 foo 时去找可执行文件 git-foo:先在 git --exec-path,再在 PATH 里。运行时参数原样传入、当前目录是调用时的目录。git help -a 的 “External commands” 列出它们。
$ git stauts 2>&1 | head -4
git: 'stauts' is not a git command. See 'git --help'.
The most similar command is
status
$ git -c help.autocorrect=immediate stauts 2>&1 | head -3 | cut -c1-80
WARNING: You called a Git command named 'stauts', which does not exist.
Continuing under the assumption that you meant 'status'.
On branch main默认只提示最相似的命令;help.autocorrect=immediate 会直接纠正并运行(数字则表示延迟,单位 0.1 秒)。
$ git config alias.co checkout; git config alias.last "log -1 --stat --format=%s"; git config alias.root "!pwd | sed 's#.*/##'"; git config alias.where "!echo \"cwd=\$(pwd | sed 's#.*/##') GIT_PREFIX=[\$GIT_PREFIX]\""; git co -b feat 2>&1 | head -1; git last | head -2
Switched to a new branch 'feat'
x
$ git root; echo "--- 从子目录 sub/ 里运行 ! 别名:"; cd sub && git where
l70
--- 从子目录 sub/ 里运行 ! 别名:
cwd=l70 GIT_PREFIX=[sub/]alias.co = checkout 展开成 git 命令;以 ! 开头的别名交给 shell,在仓库根目录运行,原来的子目录在环境变量 GIT_PREFIX 里。
$ git config alias.status "log --oneline"; git status 2>&1 | head -2; git config --unset alias.status
On branch feat
Untracked files:
$ git config alias.hello "!echo 我是别名 hello"; git hello
我是外部子命令 git-hello,参数:
当前目录: l70
GIT_EXEC_PATH 已设置: 是别名不能覆盖内置命令(alias.status 被忽略);与 PATH 上的外部命令同名时外部命令赢。顺序:内置 → 外部 → 别名。
$ git config alias.a b; git config alias.b a; git a 2>&1 | head -3; git config --unset alias.a; git config --unset alias.b
fatal: alias loop detected: expansion of 'a' does not terminate:
a <==
b ==>
$ git config alias.s status; git config alias.ss "s -s"; git ss
?? sub/别名可以套用别名(ss = s -s,s = status),循环会被检测并报 alias loop detected。
$ git co --help 2>&1 | head -2
'co' is aliased to 'checkout'
GIT-CHECKOUT(1) Git Manual GIT-CHECKOUT(1)
$ git help -a | sed -n "/^Command aliases/,\$p" | head -9
Command aliases
co checkout
hello !echo 我是别名 hello
last log -1 --stat --format=%s
root !pwd | sed 's#.*/##'
s status
ss s -s
where !echo "cwd=$(pwd | sed 's#.*/##') GIT_PREFIX=[$GIT_PREFIX]"
$ git config alias.say "!f() { echo \"收到 \$# 个参数: \$@\"; }; f"; git say 甲 乙 丙
收到 3 个参数: 甲 乙 丙git x --help 先说明它是别名;git help -a 的 “Command aliases” 列出全部;shell 别名用函数 f() { … }; f 接收参数。