欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何构建你自己的 Git 服务器 博客分类: 其他开源框架 构建Git 服务器 

程序员文章站 2024-02-11 19:13:46
...

现在我们开始学习如何构建一个Git服务器,如何编写自定义的Git钩子(hook)来针对某些事件(比如,通知事件)触发特定的动作以及如何将你的代码发布到网站上。

直 到现在,我们关注的还是作为一个用户如何与Git交互。本文将对Git管理以及如何设计一个灵活的Git基础设施进行讨论。你可能认为这听起来有点像“高 级Git技术”或“只有学霸才能阅读”的委婉说法,但实际上,这些任务只需要你对Git如何工作有一个中级水平的理解,有些情况下还需要一点Linux知 识,而不需要什么高级知识,也不需要任何特别的训练。

共享Git服务器

创建你自己的共享Git服务器相当简单,很多情况下也值得为此努力。它不仅可以确保你总能访问自己的代码,而且便于通过扩展增强Git的功能,这些扩展包括个人Git钩子、无限制数据存储以及持续的集成与部署等。

 

假如你知道如何使用Git和SSH,那么你已经知道如何创建一个Git服务器了。设计Git的方式,你创建或者克隆一个仓库时,你已经创建了一半服务器。使能SSH访问仓库,并且任何访问你仓库的人都可以使用你的回购协议作为一个新克隆的基础。

但是,会有一个小特设。有些计划你可以构建关于同样数量的精心设计的Git服务器,但是可以具有更好扩展性。

首先是:识别你的用户,包括闲杂与未来的。假如你是唯一用户,那么无需任何改变,但是如果你邀请国外的贡献者了,那么你应该为开发者搭建一个贡献共享系统平台。

假 定你有一个可用的服务器(如果不是,Git无法解决这个问题,但是运行在Raspberry Pi 3 的CentOS将会是一个良好开端),第一步是采用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持有能匹配他发送给你的公共密钥的私有密钥时,他就能以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连上服务器然后直接在资源库中进行操 作), 这使其成为了一个基础的资源库。如果你想要把服务器上的资源库用起来,就要将其从它所在的地方克隆到自己的home目录中去。

严格来说,你并不用使其成为一个基础资源库,它还是可以作为一个普通的资源库来操作的。不过,一个基础资源库是没有*工作树(working tree)* (也就是说,不会有分支会处在”checkout“状态)。这很重要,因为远程用户不会被允许向一个活动分支进行推送 (你是不会想在一个”dev“分支工作时突然有人将变更推送到你的工作空间的?)。因为基础资源库不能有活动分支,那就不会有问题发生了。

你可以将资源库放到任何你想要放置的地方, 只要你想赋予权限的用户和组也能访问到它就行了。你不会想将目录存储到一个用户的home目录的,因为这里的权限相当地严格, 而是要放在一个通用共享的位置,例如 /opt or /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进程中的某个时刻执行。在一个仓库(repository)接 受一个提交(commit)之前,或者收到一个提交之后,或者接收一个推送(push)之前,或者收到一个推送之后等时刻执行一个钩子。

这个系统很简单:任何可执行的脚本都存放在.git/hooks目录中,使用标准的命名方案,并且在某个指定的时刻执行。脚本执行的时间由名字来决定;pre-push脚本在推送之前执行,post-receive脚本在收到一个提交之后执行,诸如此类。它基本上属于自文档(self-documenting)。

可以使用任何语言编写钩子脚本;如果你能在你的系统上运行某种语言的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钩子。

著名的推送到web 钩子

一个流行的Git钩子用法是自动推送改变部分到工作生产中的web服务器目录。这是一个伟大构建FTP的方式,保留开发环节的全版本控制,并且整合、自动化发布内容。

如果正确执行,它将会工作运行良好,在某一种程度来说,一直应该做的是考虑如何网络发布。它是不错的。我不知道最初是谁想出这主意的,但是我第一次是从 EMacs和来自IBM公司的Git-mentor、Bill Von Hagen那里听到的。他的文章仍然是对这个过程起决定性作用的介绍:Git 改变分布式Web开发规则。

Git变量

每个Git钩子获取一组不同的Git动作触发它的相关变量。你可能会用到这些变量,也可能用不到。这取决于你所写的作品。 如果你想要的是一个普通的邮件 来通知你,有人推了东西。那么你不需要细节,也不需要脚本,因为你可以套用现有的样板。如果你想在邮件里浏览别人提交的信息和作者,那么对你的脚本要求更 高。

Git钩子并不是用户直接运行的,所以要理解透如何获取这些混乱却重要的信息。事实上,一个Git钩子脚本与其他任何脚本类似,像BASH、 Python、C++或者其他脚本一样的方式接受来自stdin的参数。不同的是,我们不会提供自己入参,所以使用它时要弄清楚你想要的是什么(参数)。

编写一个Git钩子之前,可以进入到你的项目目录.git/hooks查看Git提供的范例。例如,下面是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的源代码),不过若有疑问,你可以通过trials of other users进行更多地了解,或者编写一个简单的脚本,输出$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