在git服务器上,通常无法直接获得客户端push上来的代码。客户端push上来的代码会被压缩之后存放到objects/
目录下,而不是直接存储为文件的形式。那么,有没有方法可以获得push上来的代码呢?当然有,只需要这 3 3 3 个命令:
git ls-tree
git diff-tree
git show
下面来依次讲解这 3 3 3 个命令。
准备工作
为了方便,我将git服务器和客户端都存放在本地。
新建两个文件夹client
、server
,表示git
客户端和服务器。
mkdir client
mkdir server
创建客户端仓库,并设置origin
地址:
cd client
git init
git remote add origin ../server
创建服务器裸仓库:
cd server
git init --bare
git ls-tree
该命令用于列出某一次提交的所有文件。
用法:
$ git ls-tree
用法:git ls-tree [<选项>] <树对象> [<路径>...]
-d 只显示树
-r 递归到子树
-t 当递归时显示树
-z 条目以 NUL 字符终止
-l, --long 包括对象大小
--name-only 只列出文件名
--name-status 只列出文件名
--full-name 使用文件的全路径
--full-tree 列出整个树;不仅仅当前目录(隐含 --full-name)
--abbrev[=<n>] 用 <n> 位数字显示 SHA-1 哈希值
即:
git ls-tree [<选项>] <提交ID>
示例:
在客户端上创建若干个文件
cd client
touch 1
echo "114514" > 2
mkdir d
touch d/3
echo "1919810" > d/4
然后提交、push:
git add .
git commit -m "1145141919810"
git push origin master
再切换到服务器目录执行ls-tree
:
cd ../server
git ls-tree HEAD
这将显示最新一次提交(HEAD
)的文件列表。
输出结果如下:
$ git ls-tree HEAD
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1
100644 blob fbb8c85a6e13028d79c94a902fb90118d9b3b75e 2
040000 tree b800fad10873594aa94418355acf20265a2548ad d
注意到这条命令显示了文件的权限、类型、哈希值、和文件名。
诶,为什么这条命令只显示了文件夹d
,没显示d/3
和d/4
啊?
使用ls-tree -r
可以递归显示。
$ git ls-tree -r HEAD
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1
100644 blob fbb8c85a6e13028d79c94a902fb90118d9b3b75e 2
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 d/3
100644 blob 66b577f30658867f61b1c93fb9cd604618553e4f d/4
我只想要文件名怎么办?当然可以ls-tree -r HEAD | awk '{print $4}'
,但是有更简单的方法。
$ git ls-tree -r --name-only HEAD
1
2
d/3
d/4
--name-only
可以只显示文件名。
git diff-tree
只显示变化的文件,和ls-tree
用法很像。可以通过执行git diff-tree
(不加参数)或者git help diff-tree
查看帮助。
示例:
切换回client
目录,删掉文件1
,创建文件d/5
。
cd ../client
rm 1
touch d/5
git add .
git commit -m 1
git push origin master
然后在server
目录执行git diff-tree -r HEAD
:
cd ../server
git diff-tree -r HEAD
结果:
$ git diff-tree -r HEAD
af992cc27b020f86c8cba0822a81418c524216ad
:100644 000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 D 1
:000000 100644 0000000000000000000000000000000000000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 A d/5
显然,这条命令显示了变化的文件列表,原来的权限、哈希值,现在的权限、哈希值,以及变化的状态。
git show
用法:
不加任何参数,可以查看提交日志。
$ git show
commit af992cc27b020f86c8cba0822a81418c524216ad (HEAD -> master)
Author: wchengk09 <[email protected]>
Date: Thu Aug 10 09:58:12 2023 +0800
1
diff --git a/1 b/d/5
similarity index 100%
rename from 1
rename to d/5
git show <提交ID>:<文件>
,查看某一次提交的文件内容。
$ git show HEAD:d/4
1919810
注意到上面的命令显示了最新一次提交当中d/4
的内容。
正题:如何获取客户端push上来的文件
显然,有两种方法:
- 执行
git ls-tree
,获取到文件列表后,再依次git show
。 - 执行
git diff-tree
,获取到变化的文件列表后,再依次处理变化的文件。
本文以第一种方法为例,写一个脚本。它会将push上来的代码存放到src
目录下。
#!/bin/bash
cd /path/to/your/server/repository
mkdir -p src
# 先清理目录(以防git push删除了某些文件)
rm -rf src/*
# 遍历文件列表
for i in $(git ls-tree --name-only -r HEAD); do
# 保证文件所在的目录存在
(echo $i | grep / > /dev/null) && mkdir -p src/$(echo $i | rev | cut -d "/" -f 2- | rev)
# 显示文件
git show HEAD:$i > src/$i
done
代码中的(echo $i | grep / > /dev/null) && mkdir -p src/$(echo $i | rev | cut -d "/" -f 2- | rev)
什么意思呢?就是说,如果文件$i
的文件名包含/
,说明$i
在一个目录下,直接git show
重定向到对应目录会报No such file or directory
,所以需要mkdir -p
创建目录。$(echo $i | rev | cut -d "/" -f 2- | rev)
表示$i
所在的目录 (如果$i
为path/to/xxx
,则$(echo $i | rev | cut -d "/" -f 2- | rev)
为path/to
)
如何在客户端push时自动运行这个脚本?
可以设置git hooks
。
git hooks
可以在客户端执行某些操作时自动执行脚本。
其中post-receive
脚本是客户端push
完成之后执行的脚本。
我们只需要将刚才的脚本移动到server
目录下的hooks
子目录中,然后重命名为post-receive
,最后添加可执行权限即可:
cd server/hooks
cp <刚才那个脚本的路径> ./post-receive
chmod +x post-receive
这样,客户端push的时候,所有的代码都会导出至src
目录下。