阅读
- 第 1 部分:什么是 Git?
- 第 2 部分:Git 入门
- 第 3 部分:创建你的第一个 Git 仓库
- 第 4 部分:如何在 Git 中恢复旧版本文件
- 第 5 部分:3 个 Git 图形化工具
- 第 6 部分:如何构建你自己的 Git 服务器
- 第 7 部分:如何使用 Git 管理二进制大对象
现在我们将学习如何构建 Git 服务器,以及如何编写自定义 Git 钩子以在特定事件(例如通知)上触发特定操作,以及将你的代码发布到网站。
到目前为止,重点一直是作为用户与 Git 交互。在本文中,我将讨论 Git 的管理以及灵活的 Git 基础设施的设计。你可能会认为这听起来像是“高级 Git 技术”或“只有超级书呆子才读这个”的委婉说法,但实际上,这些任务都不需要超出对 Git 工作原理的中级理解的先进知识或任何特殊培训,在某些情况下,只需要一点 Linux 知识。
共享 Git 服务器
创建你自己的共享 Git 服务器非常简单,在许多情况下都值得付出努力。它不仅确保你始终可以访问你的代码,还为使用 Git 扩展打开了大门,例如个人 Git 钩子、无限数据存储以及持续集成和部署。
如果你知道如何使用 Git 和 SSH,那么你已经知道如何创建 Git 服务器。Git 的设计方式是,当你创建或克隆仓库的那一刻,你已经设置了一半的服务器。然后启用对仓库的 SSH 访问,任何有权访问的人都可以使用你的仓库作为新克隆的基础。
然而,这有点临时。通过一些规划,你可以构建一个设计良好的 Git 服务器,付出大致相同的努力,但具有更好的可扩展性。
首先要做的事情是:确定你的用户,包括当前用户和未来用户。如果你是唯一的用户,则无需进行任何更改,但如果你打算邀请贡献者加入,那么你应该为你的开发人员预留一个专用的共享系统用户。
假设你有一个可用的服务器(如果没有,这并不是 Git 可以解决的问题,但是 Raspberry Pi 3 上的 CentOS 是一个不错的开始),那么第一步是仅使用 SSH 密钥授权启用 SSH 登录。这比密码登录强大得多,因为它对暴力攻击免疫,并且禁用用户就像删除他们的密钥一样简单。
启用 SSH 密钥授权后,创建 gituser。这是所有授权用户的共享用户
$ su -c 'adduser gituser'
然后切换到该用户,并使用适当的权限创建 ~/.ssh 框架。这很重要,因为为了保护你自己,如果你将权限设置得过于宽松,SSH 将默认失败。
$ su - gituser
$ mkdir .ssh && chmod 700 .ssh
$ touch .ssh/authorized_keys
$ chmod 600 .ssh/authorized_keys
authorized_keys 文件保存了你授权在你的 Git 项目上工作的所有开发人员的 SSH 公钥。你的开发人员必须创建他们自己的 SSH 密钥对并将他们的公钥发送给你。将公钥复制到 gituser 的 authorized_keys 文件中。例如,对于一个名为 Bob 的开发人员,运行以下命令
$ cat ~/path/to/id_rsa.bob.pub >> \
/home/gituser/.ssh/authorized_keys
只要开发人员 Bob 拥有与他发送给你的公钥匹配的私钥,Bob 就可以作为 gituser 访问服务器。
但是,你实际上并不想让你的开发人员访问你的服务器,即使只是作为 gituser。你只想让他们访问 Git 仓库。正是出于这个原因,Git 提供了一个有限的 shell,恰如其分地称为 git-shell。以 root 身份运行以下命令以将 git-shell 添加到你的系统,然后将其设置为 gituser 的默认 shell
# grep git-shell /etc/shells || su -c \
"echo `which git-shell` >> /etc/shells"
# su -c 'usermod -s git-shell gituser'
现在 gituser 只能使用 SSH 来推送和拉取 Git 仓库,而无法访问登录 shell。你应该将自己添加到 gituser 的相应组中,在我们的示例服务器中,该组也是 gituser。
例如
# usermod -a -G gituser seth
剩下的唯一步骤是创建一个 Git 仓库。由于没有人会直接在服务器上与之交互(也就是说,你不会 SSH 到服务器并在该仓库中直接工作),因此将其设为裸仓库。如果你想在服务器上使用仓库来完成工作,你将从它所在的位置克隆它,并在你的主目录中工作。
严格来说,你不必将其设为裸仓库;它可以作为普通仓库工作。但是,裸仓库没有*工作树*(即,永远没有分支处于“检出”状态)。这很重要,因为不允许远程用户推送到活动分支(如果你正在 `dev` 分支中工作,而突然有人将更改推送到你的工作区,你感觉如何?)。由于裸仓库不能有活动分支,因此永远不会出现问题。
你可以将此仓库放置在你喜欢的任何位置,只要你想授予访问权限的用户和组可以这样做即可。例如,你不想将目录存储在用户的主目录中,因为那里的权限非常严格,而是存储在公共共享位置,例如 /opt 或 /usr/local/share。
以 root 身份创建一个裸仓库
# git init --bare /opt/jupiter.git
# chown -R gituser:gituser /opt/jupiter.git
# chmod -R 770 /opt/jupiter.git
现在,任何以 gituser 身份验证或属于 gituser 组的用户都可以读取和写入 jupiter.git
仓库。在本地机器上试用一下
$ git clone gituser@example.com:/opt/jupiter.git jupiter.clone
Cloning into 'jupiter.clone'...
Warning: you appear to have cloned an empty repository.
请记住:开发人员必须将其公共 SSH 密钥输入到 gituser 的 authorized_keys 文件中,或者如果他们在服务器上拥有帐户(就像你一样),那么他们必须是 gituser 组的成员。
Git 钩子
运行你自己的 Git 服务器的好处之一是它使 Git 钩子可用。Git 托管服务有时提供类似钩子的界面,但它们不为你提供可以访问文件系统的真正的 Git 钩子。Git 钩子是在 Git 进程期间的某个时间点执行的脚本;当仓库即将接收提交时,或者在它接受提交之后,或者在它接收推送之前,或者在推送之后等等,都可以执行钩子。
这是一个简单的系统:任何放置在 .git/hooks 目录中的可执行脚本,使用标准命名方案,都会在指定的时间执行。脚本应该何时执行由名称决定;pre-push
脚本在推送之前执行,post-receive
脚本在收到提交后执行,依此类推。它或多或少是自文档化的。
脚本可以用任何语言编写;如果可以在你的系统上执行语言的 hello world
脚本,那么就可以使用该语言编写 Git 钩子脚本。默认情况下,Git 附带了一些示例,但没有任何启用。
想看看实际效果吗?入门很容易。首先,如果你还没有 Git 仓库,请创建一个
$ mkdir jupiter
$ cd jupiter
$ git init .
然后编写一个“hello world”Git 钩子。由于我在工作中使用 tcsh
以获得旧版支持,因此我将坚持使用它作为我的脚本语言,但请随意使用你喜欢的语言(Bash、Python、Ruby、Perl、Rust、Swift、Go)代替。
$ echo "#\!/bin/tcsh" > .git/hooks/post-commit
$ echo "echo 'POST-COMMIT SCRIPT TRIGGERED'" >> \
~/jupiter/.git/hooks/post-commit
$ chmod +x ~/jupiter/.git/hooks/post-commit
现在测试一下
$ echo "hello world" > foo.txt
$ git add foo.txt
$ git commit -m 'first commit'
! POST-COMMIT SCRIPT TRIGGERED
[master (root-commit) c8678e0] first commit
1 file changed, 1 insertion(+)
create mode 100644 foo.txt
这就是你的第一个功能正常的 Git 钩子。
著名的 push-to-web 钩子
Git 钩子的一个流行用途是自动将更改推送到实时生产 Web 服务器目录。这是一种摆脱 FTP、保留对生产环境中内容的完整版本控制以及集成和自动化内容发布的好方法。
如果做得正确,它会非常出色地工作,并且在某种程度上,这正是 Web 发布应该一直以来的方式。它就是那么好。我不知道最初是谁提出的这个想法,但我第一次听说它是在我的 Emacs 和 Git 导师 Bill von Hagen 在 IBM 那里。他的文章仍然是对该过程的权威介绍:Git 改变了分布式 Web 开发的游戏规则。
Git 变量
每个 Git 钩子都获得一组与触发它的 Git 操作相关的不同变量。你可能需要也可能不需要使用这些变量;这取决于你要编写什么。如果你想要的只是一个通用的电子邮件,提醒你有人推送了某些内容,那么你不需要具体信息,甚至可能不需要编写脚本,因为现有的示例可能对你有效。如果你想在电子邮件中看到提交消息和提交作者,那么你的脚本就会变得更加苛刻。
Git 钩子不是由用户直接运行的,因此弄清楚如何收集重要信息可能会令人困惑。实际上,Git 钩子脚本就像任何其他脚本一样,以与 BASH、Python、C++ 和任何其他脚本相同的方式从 stdin
接受参数。不同之处在于,我们没有自己提供输入,因此要使用它,你需要知道要期望什么。
在编写 Git 钩子之前,请查看 Git 在你项目的 .git/hooks 目录中提供的示例。例如,pre-push.sample 文件在注释部分中指出
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
# If pushing without using a named remote those arguments will be equal.
#
# Information about commit is supplied as lines
# to the standard input in this form:
# <local ref> <local sha1> <remote ref> <remote sha1>
并非所有示例都那么清楚,并且关于哪个钩子获得哪个变量的文档仍然有点稀疏(除非你想阅读 Git 的源代码),但如果有疑问,你可以从 其他用户的试验 在线学习很多东西,或者只是编写一个基本脚本并回显 $1
、$2
、$3
等等。
分支检测示例
我发现生产实例中的一个常见需求是,根据受影响的分支触发特定事件的钩子。以下是如何解决此类任务的示例。
首先,Git 钩子本身不受版本控制。也就是说,Git 不跟踪自己的钩子,因为 Git 钩子是 Git 的一部分,而不是你的仓库的一部分。因此,监督提交和推送的 Git 钩子最合理的做法是位于你的 Git 服务器上的裸仓库中,而不是作为你的本地仓库的一部分。
让我们编写一个在 post-receive 时运行的钩子(即,在收到提交之后)。第一步是识别分支名称
#!/bin/tcsh
foreach arg ( $< )
set argv = ( $arg )
set refname = $1
end
这个 for 循环读取第一个参数 ($1
),然后再次循环以使用第二个参数 ($2
) 的值覆盖它,然后再用第三个参数 ($3
) 的值覆盖它。在 Bash 中有一种更好的方法可以做到这一点:使用 read
命令并将值放入数组中。但是,这是 tcsh,变量顺序是可预测的,因此可以安全地破解它。
当我们有了正在提交的内容的 refname
时,我们可以使用 Git 来发现分支的人类可读名称
set branch = `git rev-parse --symbolic --abbrev-ref $refname`
echo $branch #DEBUG
然后将分支名称与我们想要作为操作基础的关键字进行比较
if ( "$branch" == "master" ) then
echo "Branch detected: master"
git \
--work-tree=/path/to/where/you/want/to/copy/stuff/to \
checkout -f $branch || echo "master fail"
else if ( "$branch" == "dev" ) then
echo "Branch detected: dev"
Git \
--work-tree=/path/to/where/you/want/to/copy/stuff/to \
checkout -f $branch || echo "dev fail"
else
echo "Your push was successful."
echo "Private branch detected. No action triggered."
endif
使脚本可执行
$ chmod +x ~/jupiter/.git/hooks/post-receive
现在,当用户提交到服务器的 master 分支时,代码将被复制到生产目录中,提交到 dev 分支的代码将被复制到其他地方,而任何其他分支都不会触发任何操作。
创建一个 pre-commit 脚本同样简单,例如,检查是否有人试图推送到他们不应该推送的分支,或者解析提交消息以查找批准字符串等等。
Git 钩子可能会变得很复杂,并且由于通过 Git 施加的抽象级别,它们可能会令人困惑,但它们是一个强大的系统,允许你在你的 Git 基础设施中设计各种操作。它们值得涉足,即使只是为了熟悉这个过程,如果你是认真的 Git 用户或全职 Git 管理员,则值得掌握它们。
在本系列的下一篇也是最后一篇文章中,我们将学习如何使用 Git 管理非文本二进制大对象,例如音频和图形文件。
13 条评论