bigywater.tech
Git · 74

alias 与外部子命令的查找

git 的“子命令”到底是怎么找到的?为什么往 PATH 里放个 git-xxx 就多了一个 git xxx?别名能不能覆盖内置命令、和外部命令谁优先?shell 别名在哪个目录运行?这篇用几个小脚本逐个验证。

git-<名字>
外部子命令就是 PATH(或 --exec-path)里的可执行文件,git foo 找的是 git-foo
内置 → 外部 → 别名
同名时的优先顺序,别名既不能覆盖内置命令,也盖不过 PATH 上的外部命令
GIT_PREFIX
! 别名总是在仓库根目录运行,原来所在的子目录通过这个环境变量传入

外部子命令

实测 · 本机执行输出
  $ 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
       hello

git 找不到内置的 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 接收参数。

分步演示

别名与外部子命令
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。外部子命令是实验目录里的 shell 脚本(PATH 前面加了实验目录);alias 的优先级结果是在这台机器的 git 2.54.0 上观察到的(别名与外部命令同名时外部命令胜出),没有读源码也没有查其它版本
    • 没有验证:别名里使用 git 命令选项的 alias.* 命令行解析细节;core.pager 对别名的影响;git-lfs 等真实外部命令的安装方式;Windows 上可执行文件的查找(.exe);GIT_EXEC_PATH 环境变量覆盖;completion 对别名的支持
    京ICP备2026067502号-1京公网安备11010502063487号