了解git裸仓库并利用post-receive自动化部署

前言

【裸仓库】指的是使用 git init --bare 命令得到的仓库,是对这种操作结果的一种直译,这个词对于刚接触 git 软件的小伙伴来说可能是第一次听说,而我也是最近实际操作了几次才渐渐理解,下面解释一下什么是裸仓库,以及为什么要使用它,有理解不对的地方还请大家指正。

普通库和裸仓库

普通库

在解释裸仓库之前,还是先来看看 git init命令创建一个普通仓库的目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@VM-0-3-centos data]# git init simple
Initialized empty Git repository in /data/simple/.git/
[root@VM-0-3-centos data]# cd simple/
[root@VM-0-3-centos simple]# touch README.md
[root@VM-0-3-centos simple]# cd ..
[root@VM-0-3-centos data]# tree -a simple/
simple/
|-- .git
| |-- branches
| |-- config
| |-- description
| |-- HEAD
| |-- hooks
| | |-- applypatch-msg.sample
| | |-- commit-msg.sample
| | |-- post-update.sample
| | |-- pre-applypatch.sample
| | |-- pre-commit.sample
| | |-- prepare-commit-msg.sample
| | |-- pre-push.sample
| | |-- pre-rebase.sample
| | `-- update.sample
| |-- info
| | `-- exclude
| |-- objects
| | |-- info
| | `-- pack
| `-- refs
| |-- heads
| `-- tags
`-- README.md

10 directories, 14 files

通过上述命令操作后可以看到,git init simple 操作之后,创建了一个名为 simple 的库,simple 目录下还有一个 .git 子目录,其中包含了git系统常用的文件,在 .git 目录外是我们的工作区,可以存放我们库中待更新的文件,修改之后可以通过 git addgit commit 等命令更新 .git 中的内容,简单来说普通库就是在工作目录 simple 中还包括一个 .git 目录,下面添加一个文件试试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[root@VM-0-3-centos simple]# git add README.md
[root@VM-0-3-centos simple]# git commit -m"add readme file"
[master (root-commit) 9a9b255] add readme file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README.md
[root@VM-0-3-centos simple]# tree . -a
.
|-- .git
| |-- branches
| |-- COMMIT_EDITMSG
| |-- config
| |-- description
| |-- HEAD
| |-- hooks
| | |-- applypatch-msg.sample
| | |-- commit-msg.sample
| | |-- post-update.sample
| | |-- pre-applypatch.sample
| | |-- pre-commit.sample
| | |-- prepare-commit-msg.sample
| | |-- pre-push.sample
| | |-- pre-rebase.sample
| | `-- update.sample
| |-- index
| |-- info
| | `-- exclude
| |-- logs
| | |-- HEAD
| | `-- refs
| | `-- heads
| | `-- master
| |-- objects
| | |-- 9a
| | | `-- 9b255b81e994fa9af2b9c7ecbd852eb716ad6c
| | |-- e6
| | | `-- 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
| | |-- f9
| | | `-- 3e3a1a1525fb5b91020da86e44810c87a2d7bc
| | |-- info
| | `-- pack
| `-- refs
| |-- heads
| | `-- master
| `-- tags
`-- README.md

16 directories, 22 files
[root@VM-0-3-centos simple]#

添加文件之后,.git 目录中的内容发生了变化,多了3个新的object。

裸仓库

还是先从目录结构入手,我们使用 git init --bare 命令创建一个裸仓库,目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@VM-0-3-centos data]# git init --bare bare.git
Initialized empty Git repository in /data/bare.git/
[root@VM-0-3-centos data]# tree bare.git/ -a
bare.git/
|-- branches
|-- config
|-- description
|-- HEAD
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- prepare-commit-msg.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| `-- update.sample
|-- info
| `-- exclude
|-- objects
| |-- info
| `-- pack
`-- refs
|-- heads
`-- tags

9 directories, 13 files
[root@VM-0-3-centos simple]#

从目录结构来看裸仓库和普通库很像,但是仔细对比你会发现,这个裸仓库相比普通库少了一层目录,库目录 bare.git 内直接就是之前普通库 .git 目录下的内容,也就是说在 git 目录外层没有了工作目录来进行文件的增删改操作,那么我们仿照普通库操作在这个目录下提交一个文件会怎样呢?

1
2
3
4
5
6
7
[root@VM-0-3-centos data]# cd bare.git/
[root@VM-0-3-centos bare.git]# touch README.md
[root@VM-0-3-centos bare.git]# git add README.md
fatal: This operation must be run in a work tree
[root@VM-0-3-centos bare.git]# git status
fatal: This operation must be run in a work tree
[root@VM-0-3-centos bare.git]#

通过操作发现这个裸仓库不允许增删改库内的文件,甚至连 git status 这种命令都无法使用,统一提示了 fatal: This operation must be run in a work tree 这句话,告诉用户这些命令都必须在工作区内操作,既然不能修改,那么这个裸仓库就是“只读”的,那么它还有什么用呢?

虽然裸仓库不允许直接修改,但是可以作为服务端远程仓库,在本地克隆这个远程仓库之后再进行修改,这也是最常见的应用方式,总结来说,普通库和裸仓库的区别就是:普通库拥有工作目录,并且工作目录中可以存放正常编辑和提交的文件,而裸库只存放这些文件的commit记录,不允许用户直接在上面进行各种git操作。

使用裸仓库

前面提到裸仓库不能直接修改,但是我们可以采取修改克隆后库文件的方式达到更新的目的,下面列举两种常见的方式:

使用 git remote add 方式关联

这种方式需要我们先在本地初始化一个普通库,再使用 git remote add 命令建立关联(PowerShell命令行操作,git命令是相同的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
PS-Win D:\data\maingit\test> git init barebyremote
Initialized empty Git repository in D:/data/maingit/test/barebyremote/.git/
PS-Win D:\data\maingit\test> cd .\barebyremote\
PS-Win D:\data\maingit\test\barebyremote> git remote add origin root@82.156.125.196:/data/bare.git
PS-Win D:\data\maingit\test\barebyremote> new-item README.md


目录: D:\data\maingit\test\barebyremote

Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2022/6/12 16:51 0 README.md


PS-Win D:\data\maingit\test\barebyremote> git add .\README.md
PS-Win D:\data\maingit\test\barebyremote> git commit -m"add readme file"
[master (root-commit) f1c41db] add readme file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README.md
PS-Win D:\data\maingit\test\barebyremote> git push -u origin master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 223 bytes | 223.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To 82.156.125.196:/data/bare.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
PS-Win D:\data\maingit\test\barebyremote> git log -3
commit f1c41db4699f71e9750d8d6aa2c01875ac6d4a14 (HEAD -> master, origin/master)
Author: albert <albert@163.com>
Date: Sun Jun 12 16:51:34 2022 +0800

add readme file
PS-Win D:\data\maingit\test\barebyremote>

使用 git clone 直接克隆

使用克隆方式时,按照普通库来操作就可以(PowerShell命令行操作,git命令是相同的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PS-Win D:\data\maingit\test> git clone root@82.156.125.196:/data/bare.git barebyclone
Cloning into 'barebyclone'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.
PS-Win D:\data\maingit\test> cd .\barebyclone\
PS-Win D:\data\maingit\test\barebyclone> git log -3
commit f1c41db4699f71e9750d8d6aa2c01875ac6d4a14 (HEAD -> master, origin/master, origin/HEAD)
Author: albert <albert@163.com>
Date: Sun Jun 12 16:51:34 2022 +0800

add readme file
PS-Win D:\data\maingit\test\barebyclone> ls


目录: D:\data\maingit\test\barebyclone


Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2022/6/12 16:57 0 README.md

为什么要使用裸仓库

既然裸仓库相比于普通库只是少了工作目录,那么我们直接用普通库作为远程仓库可不可以呢?结论是可以,但是不建议,我们来实际操作一下,利用刚刚的建立的 simple 作为远端库,我们在本地clone后修改,再上传看看会遇到什么问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
PS-Win D:\data\maingit\test> git clone root@82.156.125.196:/data/simple simple
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.
PS-Win D:\data\maingit\test> cd .\simple\
PS-Win D:\data\maingit\test\simple> git log -3
commit 9a9b255b81e994fa9af2b9c7ecbd852eb716ad6c (HEAD -> master, origin/master, origin/HEAD)
Author: albert <albert@example.com>
Date: Sun Jun 12 15:53:30 2022 +0800

add readme file
PS-Win D:\data\maingit\test\simple> new-item .gitignore


目录: D:\data\maingit\test\simple


Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2022/6/12 17:20 0 .gitignore


PS-Win D:\data\maingit\test\simple> git add .\.gitignore
PS-Win D:\data\maingit\test\simple> git commit -m"add gitignore file"
[master b5a679f] add gitignore file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 .gitignore
PS-Win D:\data\maingit\test\simple> git push
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 263 bytes | 263.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0)
remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: error: is denied, because it will make the index and work tree inconsistent
remote: error: with what you pushed, and will require 'git reset --hard' to match
remote: error: the work tree to HEAD.
remote: error:
remote: error: You can set 'receive.denyCurrentBranch' configuration variable to
remote: error: 'ignore' or 'warn' in the remote repository to allow pushing into
remote: error: its current branch; however, this is not recommended unless you
remote: error: arranged to update its work tree to match what you pushed in some
remote: error: other way.
remote: error:
remote: error: To squelch this message and still keep the default behaviour, set
remote: error: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
To 82.156.125.196:/data/simple
! [remote rejected] master -> master (branch is currently checked out)
error: failed to push some refs to 'root@82.156.125.196:/data/simple'
PS-Win D:\data\maingit\test\simple>

克隆之后正常的修改和提交都没有问题,但是 git push的时候报错,原因提示 ! [remote rejected] master -> master (branch is currently checked out),提示当前的 master 分支是检出状态,不允许直接推送。

仔细想想就会有些思路,普通库实际上包含两份数据的,一份在 .git 目录中以object形式存在,一份在工作目录中以源文件形式存在,我们每次使用 git 命令,可以保证工作目录内文件和 .git 目录数据是一致的,但是如果将普通库作为远端时,在下游提交数据时,远端库中的 .git 目录会直接更新,但是工作区却不知道此时谁在用,不能直接更新覆盖,这就造成了数据不一致的情况。

如果非得使用普通库作为服务端仓库,那么可以参照上面报错的建议,在采用额外方式保证一致性的同时,修改服务端库的 receive.denyCurrentBranch 这个git配置项,或者将服务端分支切换到一个无人使用的分支上,这样下游端就可以直接推送了。

1
2
3
4
5
6
7
[root@VM-0-3-centos data]# cd simple/
[root@VM-0-3-centos simple]# git checkout -b unless
Switched to a new branch 'unless'
[root@VM-0-3-centos simple]# git branch -a
master
* unless
[root@VM-0-3-centos simple]#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PS-Win D:\data\maingit\test\simple> pwd

Path
----
D:\data\maingit\test\simple


PS-Win D:\data\maingit\test\simple> git push
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 263 bytes | 263.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0)
To 82.156.125.196:/data/simple
9a9b255..b5a679f master -> master
PS-Win D:\data\maingit\test\simple>

自动化部署

利用 post-receive 进行自动化部署的原理就是,git 本身提供了一些脚本接口,在某些 git 操作发生时,会调用预定脚本执行命令,相当于给 git 用户开放了接口,我们可以修改 post-receive 脚本,在修改提交后自动部署最新内容,进一步实现自动化集成。

因为前面已经介绍了很多有关裸仓库的知识,接下来我只叙述操作步骤,看了之前的介绍,这部分内容应该没什么难度了。

需求

服务端建立裸仓库,在接收到新的提交时,自动将项目部署到/data/publish/game 目录下

操作步骤

服务端远端操作

建立裸仓库 /data/repo/game.git,对应部署目录是 /data/publish/game

1
2
3
4
5
6
[root@VM-0-3-centos data]# mkdir -p /data/repo
[root@VM-0-3-centos data]# mkdir -p /data/publish/game
[root@VM-0-3-centos data]# cd repo/
[root@VM-0-3-centos repo]# git init --bare game.git
Initialized empty Git repository in /data/repo/game.git/
[root@VM-0-3-centos repo]#

新建 /data/repo/game.git/hooks/post-receive 脚本,可以拷贝 post-receive.sample 进行修改,脚本内编写内容如下:

1
2
3
4
5
6
7
8
9
# 指定部署目录
DIR=/data/publish/game
git --work-tree=${DIR} clean -fd
# 强制检出
git --work-tree=${DIR} checkout --force
# 运行启动脚本
cd ${DIR}
chmod 755 start.sh
./start.sh

客户端本地操作

本地项目普通库目录结构如下,启动脚本为 start.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
albert@home-pc MINGW64 /d/data/maingit/test/game (master)
$ tree game/ -a
game/
├── .git
│   ├── COMMIT_EDITMSG
│   ├── config
│   ├── description
│   ├── HEAD
│   ├── hooks
│   │   ├── applypatch-msg.sample
│   │   ├── commit-msg.sample
│   │   ├── fsmonitor-watchman.sample
│   │   ├── post-update.sample
│   │   ├── pre-applypatch.sample
│   │   ├── pre-commit.sample
│   │   ├── pre-merge-commit.sample
│   │   ├── prepare-commit-msg.sample
│   │   ├── pre-push.sample
│   │   ├── pre-rebase.sample
│   │   ├── pre-receive.sample
│   │   └── update.sample
│   ├── index
│   ├── info
│   │   └── exclude
│   ├── logs
│   │   ├── HEAD
│   │   └── refs
│   │   └── heads
│   │   └── master
│   ├── objects
│   │   ├── 53
│   │   │   └── dd8b65afe02329eb73cbe142b9359ffd2c4c70
│   │   ├── 68
│   │   │   └── 31f81503989c192a10b47ecf48bc6bfe7c2cf4
│   │   ├── 81
│   │   │   └── aaa9093e1d32996c53766fa5f943e3ea6c79b0
│   │   ├── e6
│   │   │   └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
│   │   ├── info
│   │   └── pack
│   └── refs
│   ├── heads
│   │   └── master
│   └── tags
├── README.md
└── start.sh

16 directories, 27 files
albert@home-pc MINGW64 /d/data/maingit/test/game (master)
$ cat start.sh
cp README.md test.txt

与远端裸仓库建立关联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
albert@home-pc MINGW64 /d/data/maingit/test/game (master)
$ git remote add origin root@82.156.125.196:/data/repo/game.git

albert@home-pc MINGW64 /d/data/maingit/test/game (master)
$ git push -u origin master
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (4/4), 286 bytes | 286.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To 82.156.125.196:/data/repo/game.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

至此自动化部署环境已建立,当本地 game 仓库推送更新时,远端服务器会自动更新部署

快速回顾

文中主要命令收于此节,方便自己后期快速查找操作

  • 服务端远程新建裸仓库
1
2
cd /data/repo
git init --bare game.git
  • 本地库与远端库建立关联
1
git remote add origin root@82.156.125.196:/data/repo/game.git
  • 新建或修改 hooks 目录下 post-receive 脚本
1
2
3
4
5
6
DIR=/data/publish/game
git --work-tree=${DIR} clean -fd
git --work-tree=${DIR} checkout --force
cd ${DIR}
chmod 755 start.sh
./start.sh

总结

  • 裸仓库是一个只包含提交记录,没有工作目录的仓库,适合用来做服务端远程仓库
  • 裸仓库不能直接在仓库中执行修改文件的git命令,可以在客户端克隆之后修改之后再进行提交
  • 自动化部署利用了git服务器提供的脚本接口,当新的推送达到时会调用 post-receive 脚本
  • 配置自动化部署环境时需要注意,如果没有配置ssh免密码登陆,需要在push代码的时候输入密码
  • 另外自动化部署时要注意各个文件及目录的权限,因为要运行脚本,要保证推送用户有足够的运行权限

==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

每个人都有自己的选择,很多看似突如其来的决定,往往都是深思熟虑后的结果,每个人在自己的旅途中不断的分类、选择、分类、选择,无法逃离的坚持到最后一刻~

2022-6-12 20:19:30

Albert Shi wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客