看了早期Git的代码
从这里下载了一个早期的Git版本,我下载的是 git-core-0.99.6.tar.gz ,
https://mirrors.edge.kernel.org/pub/software/scm/git/
解压开,发现其中的文件已经很多,而且有大量的脚本,说明很多Git命令是用脚本包装实现的。
随便打开了其中的一个脚本git-commit-script,看了几行,不禁惊讶:代码精巧、紧凑,干脆利索,果真老hacker手笔,我这辈子都写不出这样的代码!
就先看 git-sh-setup-script 吧,这个片段包含基本的变量和函数,被其它脚本引用:
1 #!/bin/sh
2 #
3 # Set up GIT_DIR and GIT_OBJECT_DIRECTORY
4 # and return true if everything looks ok
5 #
6 : ${GIT_DIR=.git}
7 : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
8
9 die() {
10 echo "$@" >&2
11 exit 1
12 }
13
14 check_clean_tree() {
15 dirty1_=`git-update-cache -q --refresh` && {
16 dirty2_=`git-diff-cache --name-only --cached HEAD`
17 case "$dirty2_" in '') : ;; *) (exit 1) ;; esac
18 } || {
19 echo >&2 "$dirty1_"
20 echo "$dirty2_" | sed >&2 -e 's/^/modified: /'
21 (exit 1)
22 }
23 }
24
25 [ -h "$GIT_DIR/HEAD" ] &&
26 [ -d "$GIT_DIR/refs" ] &&
27 [ -d "$GIT_OBJECT_DIRECTORY/00" ]
- 第6、7两行,定义两个shell变量,如果变量已经有值,比如预先在环境变量里定义了,那么就忽略赋值。冒号「:」是shell里的noop,不做任何事,但是在这里的副作用,就是让其后的「${GIT_DIR=.git}」被解释执行。如果不带冒号,只有赋值语句(准确地说是shell变量扩展语句,Parameter Expansion),那么这样的语句将返回实际的变量值,而后该值被 shell 认为是个命令,自然将报错。
- 第9行,定义一个方便使用的报错函数 die。
- 第14行,定义一个函数 check_clean_tree 。仔细看,这个函数其实只有一个表达式,当然是个极其复杂的表达式,
- 该表达式由三个语句构成,设为A、B、C,操作符 && 和 || 把它们联系起来,即 A && B || C,用括号括起来更清晰:(A && B) || C,(这是我试验了之后才知道的)。
- 由 (A && B) || C 这样的形式可知,若A、B都为真,结果为真;只要 C 为真,结果也为真;若A、C都为假,则结果为假。不过,真假是一方面,另一方面,是A、B、C分别执行了更具体的命令。
- A 语句,第15行,dirty1_=... 是简单的赋值,语句最后的真假取决于命令 git-update-cache -q --refresh 是否执行成功,与 dirty1_ 得到的值无关。
- B 语句,第16、17行,是个语句块(block)。若前述 A 执行成功了,则会执行 B 语句,即,若 update-cache 成功,则进一步 diff-cache,应该是查看验证吧。然后用一个 case 语句判断 $dirty2_ ,
- 若 $dirty2_ 为空,忽略,这应该表示前述命令执行成功了,「忽略」这个动作也代表成果,将返回真。
- 若 $dirty2_ 非空,执行 (exit 1),注意是在括号里执行的,在shell里,圆括号即为创建子进程,所以这句并非让当前的shell脚本退出,而是造成一个效果,即返回假。
- 这个短短的 case 语句,真是简洁啊。
- C 语句,第19~21行,当 A 失败或 B 失败时,才执行 C 语句,即,若 update-cache 失败,或 update-cache 成功而 diff-cache 失败,则执行这几句命令,向屏幕显示一些报错消息。(较简单,不说了)
- 第25~27行,是三个条件表达式的综合,意思是,当满足全部三个条件时——$GIT_DIR/HEAD存在且是符号链接,$GIT_DIR/refs存在且是目录,第三个类似——,将返回真,因为这里已经是最后一句了,所以返回值将被调用者看到。即,这是一个跨脚本的判断。
我上面写的代码解释,甚至比代码还要多……。
纵观这段代码,没有一个 if,却实现了大量的判断和分支处理。由于是早期的版本,小圈子里用的,以实用为主,我相信这么写代码不是炫技,而是自然而然地就这么写了,使用这些代码的人呢,也自然而然地就这么看、这么用了,对他们来说,也许没有什么费解的地方。「我亦无他,惟手熟尔」。