文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

您将会阅读:

接下来我们将学习如何建立一个Git 服务器,和如何根据声明的动作和特定事件触发编写自定义Git hooks(例如通知),以及如何发布你的代码到网站服务器。

截至现在,我们一直作为一个用户关注如何与Git进行交互。在这篇文章中我们将讨论如何管理Git,如何建立一个灵活的Git基础架构。你可能会感觉听起来像是一个“Git高级技术”或者“看这个假如你是一个超级书呆子”之类的说法,但是实际上这些任务并不需要那些所谓高级知识以及超出任何需要理解Git是如何工作特殊的培训,和在某些情况下一些关于Linux的知识。

第 1 段(可获 2.04 积分)

共享Git server

创建一个你自己的共享Git server非常简单,但是你可能会遇到各种问题。不光是因为确认是否对你的代码有相应的权限,还是通过个人Git hooks扩展Git,以及限制数据存储大小和持续集成、持续部署。

如果你知道如何使用Git 和 SSH,那么你应该明白如何创建一个Git serer。Git设计的初衷,就是可以立即创建一个代码仓库并clone它。如果你已经建立了服务器,并且使得SSH允许对当前仓库有操作权限,任何人可以通过clone它,使用你的仓库作为基础仓库。

第 2 段(可获 1.5 积分)

尽管如此 这里还是给你一点特别的建议。提前规划一个架构良好、设计合理的Git server会使得你事倍功半。

首先:识别有哪些用户,包括已经存在的用户和即将参与的用户。如果你是这个仓库唯一的用户那么就不需要更改设置,但是如果你希望以后能邀请其他贡献者一起共同参与项目,那么你需要为这些开发者建立一个专用的共享系统用户。

假设你拥有一个可用的服务器(如果没有,也不是什么问题,因为Git甚至可以运行在一个搭载了CentOS系统的3代树莓派上),然后第一步你使用SSH 公钥认证并允许SSH登陆系统,并且禁止用户删除它们的公钥,因为这样可以有效防止你的系统遭到暴力攻击。

第 3 段(可获 1.78 积分)

一旦你拥有了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秘钥并把自己的公钥发送给你。你需要拷贝这些公钥并挨个保存到gituser 用户对应的authorized_keys 文件中。举个例子,一个名叫Bob的开发者,运行下面的命令:

第 4 段(可获 1.43 积分)
$ cat ~/path/to/id_rsa.bob.pub >> \ 
/home/gituser/.ssh/authorized_keys

一旦开发者Bob自己的私钥与发送给你的公钥匹配,Bob就拥有了作为gituser用户的对服务器的操作权限。

尽管如此,我们仍然不希望赋予开发者对服务器操作的权限,甚至是gituser用户也不可以,你只希望赋予他们操作Git 仓库的权限。鉴于这个特殊的原因,Git 提供了一个适当的,被称为 git-shell有限shell命令运行这些命令,允许root用户添加 git-shell 到Linux系统,并赋予gituser用户使用特殊的git-shell命令来执行相应的操作。

# grep git-shell /etc/shells || su -c \
"echo `which git-shell` >> /etc/shells"
# su -c 'usermod -s git-shell gituser'

 

第 5 段(可获 1.09 积分)

现在作为 gituser用户可以在不使用shell登陆系统的情况下,只使用SSH 推送和更新 Git 仓库。你应当添加自己的用户到gituser所处的用户组,如果你的服务器也有gituser用户组的话。

举个例子:

# usermod -a -G gituser seth

剩下的步骤也就只有创建一个Git 仓库了。一旦没人会直接影响你的服务器(除非你想不通过SSH连接服务器,直接在仓库上工作),创建一个裸仓(译者注:git init --bare)。如果你希望使用在服务器上创建的远程仓库,那么你需要clone该仓库到你本地的工作路径下。

第 6 段(可获 1.44 积分)

严格地说,你可以不用非得建立一个裸仓; 在一个普通仓库也可以。然而,由于裸仓没有*working tree*(这是一个没有分支处于`checkout`状态的工作区)。这点非常重要,因为远程用户没有推送到当前活动分支的权限(你肯定不喜欢正处于`dev`分支开发的时候,有人向你的工作空间推送代码吧?)。也正因为这个原因,一个裸仓没有活动分支,我们就不会面临这个问题。

你可以把仓库放在任何你希望存放仓库的地方,你只需要保证用户和用户组对这个仓库有适合的权限就可以。最好不要把仓库存放在用户自己的工作区,因为权限要求是很严格的,你可以存放在一些共享目录,例如/optor /usr/local/share。

 

 

第 7 段(可获 1.89 积分)

使用root用户创建一个裸仓,并赋予gituser合适的权限:

# 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.

记住一点:开发者必须把自己的公钥加入gituser用户的authorized_keys文件中,或者他们就是服务器的用户(如果可以的话),并且这个用户必须成为gituser用户组的一员。

第 8 段(可获 0.9 积分)

Git hooks(钩子)

一个非常棒的事情是如果你运行自己的Git serer 可以使用Git hooks(钩子)。Git托管服务有时提供类似钩子的接口,但是他们不会给Git hooks对应文件系统的真正的使用权限。一个Git hook脚本是一个通过指向Git进程的可执行脚本;一个hook当某种事件被触发的时候执行,比如收到一个提交,或者在接受提交之后,或者接受一个推送,或者推送完毕,诸如此类的事件。

这是一个简单的系统:你只需要在.git/hooks目录下用任何可执行脚本,使用标准的命名方案,替换掉对应的脚本,在特定时间它就会被执行。当一个脚本命名为特定名称且被执行的时候,是根据自身描述来约定何时去执行,比如一个pre-push脚本会在push 动作前执行,一个post-receive脚本会在一个提交被接收到后执行,以此类推。

第 9 段(可获 1.94 积分)

hook脚本可以用任何语言编写;如果在服务器上你可以执行某个语言的hello world脚本,那么你就可以使用这个脚本编写一个Git hook程序。默认情况下,Git提供了一部分没有可执行权限的实例。

你想看看这些实例吗?很简单。首先如果你还没有Git仓库你可以通过下面的命令创建一个:

$ mkdir jupiter
$ cd jupiter
$ git init .

接下来编写一个“hello world” Git hook。我经常在工作使用tcsh,现在依然它来编写脚本,当然你也可以使用你偏爱的脚本来代替它编写hook(例如:Bash, Python, Ruby, Perl, Rust, Swift, Go):

第 10 段(可获 1.33 积分)
$ 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 hook脚本了。

著名的 push-to-web hook脚本

一个普遍使用Git hook的场景是通过hook可以自动推送更新到一个运行的生产环境服务器目录。这是一个替代FTP的更好的工作方式,因为我们不需要把整个版本发布到生产环境,而采用集成和自动化方式部署到生产环境。

第 11 段(可获 0.75 积分)

如果这一切都正确执行了,hook出色的完成了任务,与此同时web应用也完成了部署。者真的很棒。我不清楚这个想法最初是谁提出来的,但是我第一次是在IBM,从我的 Emacs和 Git 导师,Bill von Hagen那里听到的。他的文章Git changes the game of distributed Web development 至今也是对这一想法权威的介绍。

Git 变量

每一个Git hook 都接收从Git 动作传递过来的不同的变量集,并通过它们触发相应的操作。你可以使用或者不使用这些内置的变量;他们依赖于你编写的脚本。如果你所需要的仅仅是当有人推送什么东西的时候发送一封提醒邮件,那么你根本无需特别声明,你所要做的只不过是在已经存在的hook脚本中稍加修改就可以实现这个功能。当你希望在邮件中看到诸如提交的详细信息和是谁提交的这样的功能的时候,脚本就要相对复杂点了。

第 12 段(可获 2.09 积分)

Git hooks不会直接通过用户去执行,所以要了解hook到底收集了哪些重要信息会比较让人困惑。实际上,一个Git hook脚本和其他的语言,例如BASH, Python, C++的脚本基本类似,通过stdin接受参数非常容易。所不同的是,我们无法提供输入的信息,你只需知道期望他们执行什么操作即可。

在你编写一个Git hook脚本前,最好参考当前项目路径下的.git/hooks 提供的hook实例脚本。举个例子,下面是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>

 

第 13 段(可获 1.23 积分)

并非所有的hook实例都很清晰,并且文档中对于hook获取哪些变量并不详尽(除非你想自己阅读Git的源码),但是毫无疑问,如果你能学习关于 trials of other users的在线文档,或者编写一些输出类似 $1$2$3的命令行参数的脚本,就可以了解更多深入hook原理细节。

分支检测的例子

这里有一个比较普遍的需求就是在生产环境中,通过hook触发一个具体的事件基于哪个本地分支发生了变更。下面的实际例子实现了如何解决类似的任务。

首先,Git hooks是不受我们控制和版本管理的。那意味着,Git不能跟踪自己的hook是否发生了变更,因为Git hook是Git的一部分,但是不是代码仓库的一部分。因为这个原因,一个Git hook脚本容易跟踪一个裸库,也就是Git 远程仓库上提交和推送这样的动作,而不是仅仅作为一个本地仓库的一部分。

第 14 段(可获 2 积分)

让我们编写一个hook脚本,运行在 post-receive(这是一个在提交后接受到Git服务器返回信息的事件)的动作触发上。

#!/bin/tcsh

foreach arg ( $< )
  set argv = ( $arg )
  set refname = $1
end

脚本会循环读取从第一个参数($1)值到并第二个参数($2)值,和接下来的参数三($3)的值,并追加覆盖前面的值。这是一个很棒的通过Bash执行的方式:使用read命令并把输入值保存到一个数组里。通过这种方式tcsh脚本可以按照变量顺序获得可能的分支变化,这是安全的通过hack的方式获取我们想要的信息。

第 15 段(可获 1.11 积分)

当我们获得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

 

第 16 段(可获 0.46 积分)

赋予脚本可执行权限:

$ chmod +x ~/jupiter/.git/hooks/post-receive

现在当用户本地提交到远程master分支的时候,一份代码被拷贝到生产环境对应的目录下,与此同时一个dev分支也获得了相同的拷贝,其他的分支不会被触发操作。

这是一个简单的实现,在如下场景中hook起作用的实例,例如,检查是否有人向不允许他推送的远程分支推送代码,或者向提交信息中写入验收信息,诸如此类。

Git hook可以很复杂,并且通过抽象级别的流程增强Git的功能,所以Git hooks仍然是一个强大的系统,他允许在你的Git架构中设计对应相应的流程。这些都值得你去研究,不过取决于想熟悉哪些流程,和重要的Git 用户或全职的Git 管理员需要精通的Git技能。

在接下来的系列文章中,我们将学会如何使用Git 管理非文本的二进制数据,例如视频和图形文件。 

第 17 段(可获 2.21 积分)

文章评论

凭海临风
这个文章应该比较老了,不过里面涉及到的有关Git hook的内容可以参考下