Gitolite-精要-全-

Gitolite 精要(全)

原文:annas-archive.org/md5/ffd602d8fbdeab4bbf0981e25f2604d3

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

Gitolite 是一个非常流行的 Git 仓库服务器细粒度访问控制工具。它速度快、低调且占用资源小,但提供了多项功能,使得管理员的工作变得更加轻松。

本书帮助你快速掌握 Gitolite。它易于阅读,从基本的安装过程到高级功能(如镜像)都讲解得通顺流畅。特别是更强大和复杂的功能是逐步构建的,帮助你更直观地理解它。任何正在使用或考虑使用 Gitolite 的人都将从本书中受益。

本书内容简介

第一章,Gitolite 入门,向你展示了 Gitolite 的实用性,以及一些基本的访问控制规则和一些高级功能的示例。它还教你如何创建一个 Gitolite 的测试实例,帮助你安全地进行尝试。

第二章,安装 Gitolite,讨论了 Gitolite 的安装和基本管理任务,如添加新用户和创建新仓库。

第三章,用户与 Gitolite,讨论了用户如何看待 Gitolite 管理的系统,以及他们开始使用它所需了解的内容。它还提供了关于如何查找和修复 ssh 密钥问题的有用信息。

第四章,添加与删除用户,详细介绍了用户管理以及添加用户时背后发生的事情。还讨论了特殊情况,如拥有多个密钥的用户或需要完整 shell 命令行的用户。

第五章,管理仓库,讲解了如何添加新仓库,以及如何将现有仓库纳入 Gitolite 控制之下。

第六章,入门访问控制,展示了 Gitolite 大多数基本的访问控制功能,包括各种类型的访问规则和配置文件语法。

第七章,高级访问控制与配置,深入探讨了个人分支、设置 Git 配置变量和 Gitolite 选项等高级功能。它还讨论了如何使 Gitolite 影响 gitweb 和 git-daemon 的操作。

第八章,允许用户创建仓库,讨论了 Gitolite 可能最受欢迎和最重要的功能之一。它讲解了如何允许用户创建自己的仓库,访问规则是如何工作的,以及仓库创建者如何允许其他人访问仓库。

第九章,自定义 Gitolite,展示了管理员如何利用 Gitolite 的自定义功能为他们的站点增加独特的功能,例如命令和触发器。

第十章,理解 VREFs,讲解了 Gitolite 在用户推送到仓库时使用任意因素来决定允许或拒绝的能力,通常只需要写几行代码。

第十一章,镜像,探讨了 Gitolite 的一个功能,该功能在拥有全球各地开发者共同工作的多站点大规模设置中非常有用。Gitolite 镜像功能非常灵活,本章将帮助你开始充分利用它。

这本书所需的内容

你将需要一个兼容 POSIX 的 "sh" 的 Unix 系统,Git 版本 1.7.8 或更高版本,以及 Perl 5.8.8 或更高版本。你还需要安装 Openssh 服务器(或兼容的 ssh 服务器)。

本书的读者群体

本书非常适合任何希望安装和使用 Gitolite 的人。已经在使用它的人,若希望超越基础并了解其更强大的功能,也会发现许多有用的信息和见解。

约定

在本书中,你将看到多种文本样式,它们区分了不同类型的信息。以下是一些样式示例及其含义的解释。

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入以及 Twitter 账号会如下所示:“将一个非裸仓库转换为裸仓库的一种方式是使用 --bare 选项进行克隆。”

所有命令行输入或输出会如下所示:

repo    gitolite-admin
 RW+     =   adam
repo    testing
 RW+     =   @all

新术语重要词汇 会以粗体显示。你在屏幕上看到的词语,比如菜单或对话框中的词汇,会像这样出现在文本中:“点击 下一步 按钮会将你带到下一个页面。”

注意

警告或重要说明会以类似这样的框显示。

提示

提示和技巧会像这样显示。

读者反馈

我们始终欢迎读者的反馈。让我们知道你对这本书的看法——你喜欢什么,或者可能不喜欢什么。读者反馈对我们来说非常重要,有助于我们开发出你真正能从中获得最大收益的书籍。

若要向我们发送一般反馈,只需发送电子邮件至 <feedback@packtpub.com>,并在邮件主题中提及书名。

如果你在某个领域有专业知识,并且有兴趣写作或为一本书提供贡献,请参阅我们的作者指南,网址为 www.packtpub.com/authors

客户支持

既然你已经拥有了一本 Packt 书籍,我们提供了一些帮助,让你能最大化地利用你的购买。

勘误

虽然我们已经尽最大努力确保内容的准确性,但错误还是会发生。如果您在我们的书籍中发现错误——可能是文本或代码中的错误——我们将非常感激您向我们报告。这样,您可以帮助其他读者避免困扰,并帮助我们改进书籍的后续版本。如果您发现任何勘误,请访问 www.packtpub.com/submit-errata 提交,选择您的书籍,点击 勘误提交表单 链接,并填写勘误的详细信息。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站,或添加到该书标题下的勘误列表中。任何现有的勘误都可以通过访问 www.packtpub.com/support 查看。

盗版

网络上的版权资料盗版问题是一个持续存在的挑战,涉及所有媒体形式。在 Packt,我们非常重视保护我们的版权和许可。如果您在互联网上发现任何我们作品的非法复制,无论形式如何,请立即向我们提供该位置地址或网站名称,以便我们采取相应措施。

如果您发现疑似盗版的资料,请通过 <copyright@packtpub.com> 联系我们,并提供相关链接。

感谢您帮助我们保护作者的权益,使我们能够为您提供有价值的内容。

问题

如果您在书籍的任何方面遇到问题,可以通过 <questions@packtpub.com> 联系我们,我们将尽力为您解决。

第一章:开始使用 Gitolite

Git 是目前最流行的版本控制系统之一,过去几年里,数千个新旧项目开始使用它。你可能也使用过它,并很快意识到,Git 本身在访问控制方面并没有做太多工作。你需要一个既简单快速安装又能满足未来需求的灵活强大的访问控制系统。

本章将介绍 Gitolite 的概念,以及为什么你可能需要它。它展示了几个基本功能的示例,并向你展示了如何安全地尝试 Gitolite。假设你已经有一定的 Git 基础知识,并且曾经在本地和远程仓库中使用过 Git。

常见的访问控制需求

Git 服务器管理员面临一定的挑战。Git 的普及意味着有成千上万的开发者并不真正熟悉 Git,因此他们可能会运行一些 Git 命令,导致对 Git 仓库产生不可逆转或高度破坏性的变化。此外,Git 本身对此没有太多帮助;它的访问控制仅适用于整个仓库,无法做到更细粒度的控制。

例如,大多数项目中的主分支代表着最稳定的代码。然而,初级开发者可以轻易地运行一个命令,如 git push origin +master,(将开发者的本地分支推送到服务器),从而覆盖团队其他成员几周或几个月的辛勤工作。具有较深 Git 专业知识的人可能能够恢复丢失的提交,但这无疑需要时间和精力。

更糟糕的是,Git 的命令语法有时会使情况变得更糟。例如,删除主分支的命令与正常的推送命令只有一点点区别,即 git push origin :master(注意那个额外的冒号?)。

因此,最常见的需求是防止以下类型的事故:覆盖(或回退)一个或多个提交,删除一个分支或标签。

Git 本身确实提供了一定的保护。你可以将配置项 receive.denyDeletesreceive.denyNonFastForwards 设置为 true。不幸的是,这种方式有点粗暴——现在任何人都无法删除或回退 任何 分支!

在有多个仓库和多个开发者的大型设置中,你可能还会担心是否允许每个人访问所有仓库。或者某些角色(例如测试工程师)可能不需要对仓库进行写操作;只读访问就足够了。在一定程度上,这个问题可以通过谨慎应用 Unix 权限和用户/组权限来解决。如果你熟悉 POSIX ACL,也许可以使用它来处理。

然而,使用 POSIX ACL 和用户/组权限存在一些缺点:

  • 每个 Git 用户在服务器上需要一个对应的 Unix 用户 ID。

  • 管理访问权限只能通过使用 usermodsetfacl 命令来实现。

  • 检查当前的权限集并不是一件简单的事。你需要运行多个命令,并手动将它们的输出进行关联。

  • 审计权限变更是不可行的,因为系统不保留历史记录。

这些缺点要求即使管理几个仓库和用户也需要大量精力,而即使是一个规模适中的配置,也会迅速变得难以管理。

Gitolite 的访问控制示例

我们将看到 Gitolite 如何使得访问控制变得简单。首先,来看一个示例,在这个示例中,初级开发人员(我们称他们为 Alice 和 Bob)应该被禁止回滚或删除任何分支,而高级开发人员(Carol 和 David)则可以这样做:

提示

我们将在后续章节中更详细地看到这一点,但 Gitolite 使用一个纯文本文件来指定配置,这些访问规则也被放置在该文件中。

repo foo
 RW    =  alice bob
 RW+   =  carol david

你可能猜到,RW 代表读写权限。第二条规则中的 + 代表 强制,就像在 push 命令中一样,允许你回滚或删除分支。

现在,假设我们希望让初级开发人员拥有一些特定的分支,他们可以回滚或删除,可以理解为一种“沙盒”环境。以下命令将帮助你实现这一点:

 RW+  sandbox/  =  alice bob

Alice 和 Bob 现在可以推送、回滚或删除任何名称以 sandbox/ 开头的分支。

在仓库级别进行访问控制甚至更简单,或许你已经猜到它是什么样子了:

repo foo
 RW+     =   alice
 R       =   bob

repo bar
 RW+     =   bob
 R       =   alice

repo baz
 RW+     =   carol
 R       =   alice bob

如你所见,你有三个用户,每个用户对三个仓库的访问权限不同。使用文件系统的权限机制或 POSIX ACLs 来实现这一点是可行的,但设置起来相当繁琐,而且审计/审核也会很麻烦。

Gitolite 强大功能的示例

访问控制示例展示了 Gitolite 最常用的功能——仓库和分支级别的访问控制,但 Gitolite 当然有更多功能。在这一节中,我们将简要了解其中的一些功能,同时注意到还有许多功能等待你在阅读本书时发现。

创建用户组

Gitolite 允许你创建用户或仓库的组,以提高便利性。回想一下 Alice 和 Bob,我们的初级开发人员。假设你有几个规则需要 Alice 和 Bob 被提及。显然,这样做太麻烦了;每当有新开发人员加入团队时,你就得修改所有规则以添加他或她。

Gitolite 通过以下命令来实现这一点:

@junior-devs    =  alice bob

后来,它通过以下命令来实现这一点:

repo foo
 RW                    =  @junior-devs
 RW+                   =  carol david
 RW+  sandbox/         =  @junior-devs

这使得你可以在配置文件顶部的一个地方添加初级开发人员,而不需要在多个地方添加。更重要的是,从管理员的角度来看,它作为规则本身的优秀文档;当使用描述性组名而不是实际用户名时,推理这些规则不是更容易吗?

个人分支

Gitolite 允许管理员为每个开发者提供一组独特的分支,称为个人分支,只有该开发者才能创建、推送或删除。这是一种非常方便的方式,可以快速备份正在进行的分支,或者共享代码进行初步审核。

我们已经看到沙箱区域是如何定义的:

 RW+  sandbox/  =  alice bob

然而,这并不能阻止一个初级开发者不小心删除另一个开发者的分支。例如,Alice 可能会删除 Bob 推送的名为sandbox/bob/work的分支。你可以使用特殊的词USER作为目录名来解决这个问题:

 RW+  sandbox/USER/  =  alice bob

这就像你为每个用户单独指定了权限,像这样:

 RW+  sandbox/alice/   =  alice
 RW+  sandbox/bob/     =  bob

现在,Alice 被允许推送的分支集合仅限于以 sandbox/alice/ 开头的分支,她不再能推送或删除名为 sandbox/bob/work 的分支。

个人仓库

使用 Gitolite 后,管理员可以选择允许用户创建自己的仓库,除了管理员自己创建的仓库。对于这个示例,忽略语法(稍后的章节中会解释),现在只关注功能:

repo dev/CREATOR/[a-z].*
 C       =  @staff
 RW+     =  CREATOR

这允许 @staff 组的成员创建符合指定模式的仓库名称,模式意味着 dev/<用户名>/<以小写字母开头的任何内容>。例如,一个名为 alice 的用户将能够创建如 dev/alice/foodev/alice/bar 的仓库。

Gitolite 和 Git 控制流程

从概念上讲,Gitolite 是一个非常简单的程序。为了了解它如何控制 Git 仓库的访问,我们首先来看看在正常的 git 操作中(比如 git fetch)控制是如何从客户端流向服务器的,假设使用的是普通的 ssh

Gitolite 和 Git 控制流程

当用户执行git clonefetchpush 时,Git 客户端会调用 ssh,并传递一个命令(根据用户是读取还是写入,命令为 git-upload-packgit-receive-pack)。本地的 ssh 客户端将这个命令传递给服务器,并且假设认证成功,该命令将在服务器上执行。

安装了 Gitolite 后,ssh 守护进程不会直接调用 git-upload-packgit-receive-pack。相反,它会调用一个名为 gitolite-shell 的程序,改变控制流程如下:

Gitolite 和 Git 控制流程

首先,请注意Git 客户端端没有任何变化;变化仅发生在服务器端。实际上,除非发生访问违规并需要向用户发送错误信息,否则用户可能甚至不知道已经安装了 Gitolite!

第二,注意 Gitolite 的 shell 程序与 git-upload-pack 程序之间的红色链接。如果 Gitolite 确定用户没有对相关仓库的适当访问权限,则不会发生此调用。此访问检查适用于读取(即 git fetchgit clone 命令)和写入(git push)操作;尽管对于写操作,后续会有更多检查。

尝试 Gitolite

在不对系统中的其他任何部分产生永久性影响的情况下,尝试 Gitolite 非常简单。Gitolite 提供了一整套完整的测试脚本,官方支持的尝试 Gitolite 的方法只需使用其中几个测试脚本来自动安装和设置 Gitolite。

在这个过程结束时,你将拥有一个已设置好并准备使用的 Gitolite 版本。你还将拥有一个“admin”用户和六个“normal”用户,使用这些你可以尝试 Gitolite 的大部分功能(除了像镜像这样的高级功能)。

准备设置

要尝试 Gitolite,你需要以下内容:

  • 一台 Unix(Linux、BSD、HP-UX、AIX、Solaris 等)服务器

  • 在服务器上安装 Git 1.7.1 或更高版本

  • 在服务器上安装 Perl 5.8.8 或更高版本

  • 在服务器上安装并运行一个兼容 OpenSSH 的 SSH 守护进程

  • 需要 root 权限来创建一个新的临时用户进行测试

本书编写时,Git 1.7.1 已经发布超过三年,Perl 5.8.8 也比这更早,因此几乎任何较新的 Linux 或 BSD 系统都应该可以正常工作。

安装并设置一个测试实例

在具备先决条件后,以下是如何获得一个 test 实例来试用 Gitolite 的步骤:

  1. 以 root 用户登录(使用你的操作系统/发行版要求的任何命令),然后创建一个新的临时用户。你可以随意命名,但我们这里使用 gitolite-test请不要使用现有的用户!

  2. gitolite-test 用户登录。

  3. 从官方仓库获取 Gitolite 源代码,使用 git clone git://github.com/gitolite/gitolite

    如果你的服务器无法直接访问互联网,你可以从其他 Gitolite 源代码的克隆中获取这个。只需将你在前面 clone 命令中使用的 URL 替换掉。

  4. 使用以下命令切换到刚才克隆的目录:

    cd gitolite
    
    
  5. 使用以下命令在测试模式下安装并设置 Gitolite:

    env GITOLITE_TEST=y prove t/ssh*
    
    
  6. 返回到 HOME 目录:

    cd
    
    

这将运行两个测试脚本。你会看到有关创建 authorized_keys 文件的警告,可以忽略它,最后会显示一条消息,说明“所有测试成功”,并附带一些测试运行的统计信息。

在该过程结束时,你应该拥有以下内容:一个“管理员”用户(名为 admin)和六个普通用户(名为 u1u6)。这些用户是通过 ssh 功能进行模拟的。如果你熟悉 ssh,可以查看 ~/.ssh/config 文件,了解如何实现这一点。

玩转 Gitolite

现在,你可以使用上一节中的 Gitolite 测试设置。以下是一些命令示例,并附带说明,帮助你开始使用:

克隆特殊的 gitolite-admin 仓库:

$ git clone admin:gitolite-admin 
Cloning into 'gitolite-admin'... 
remote: Counting objects: 8, done. 
remote: Compressing objects: 100% (4/4), done. 
remote: Total 8 (delta 1), reused 0 (delta 0) 
Receiving objects: 100% (8/8), done. 
Resolving deltas: 100% (1/1), done. 

检查克隆的内容:

$ cd gitolite-admin/ 
$ ls -a 
.  ..  conf  .git 
$ ls -a conf 
.  ..  gitolite.conf 

编辑 conf/gitolite.conf 文件,并添加以下几行,指示 Gitolite 创建一个名为 bar 的新仓库,并允许用户 u1u2 对该仓库拥有所有权限:

repo bar
 RW+  =  u1 u2

保存文件后,添加更改(git add),然后提交文件(git commit):

$ git add conf/gitolite.conf 
$ git commit -m 'added repo bar' 
[master 1111cee] added repo bar 
 1 file changed, 3 insertions(+) 
$ git push 
Counting objects: 7, done. 
Delta compression using up to 4 threads. 
Compressing objects: 100% (2/2), done. 
Writing objects: 100% (4/4), 338 bytes | 0 bytes/s, done. 
Total 4 (delta 1), reused 0 (delta 0) 
remote: Initialized empty Git repository in /home/gitolite-test/repositories/bar.git/ 
To admin:gitolite-admin 
 f226f28..1111cee  master -> master

如你所见,我们刚刚创建了一个名为 bar 的新仓库。如果你仔细查看输出,可能会注意到,在 git push 命令的常见输出中,有一行显示在服务器上初始化了一个空的 Git 仓库。这非常有用,因为你无需登录服务器来创建仓库——Gitolite 会为你处理这个过程。

让我们来看一下访问权限。运行 ssh 命令连接到服务器并提供 info 命令,将显示你有权限访问的仓库:

$ ssh admin info 
hello admin, this is gitolite-test@server running gitolite3 v3.5.3.1-6-g5bdc750 on git 1.8.3.1 

 R W  gitolite-admin 
 R W  testing 
$ ssh u1 info 
hello u1, this is gitolite-test@server running gitolite3 v3.5.3.1-6-g5bdc750 on git 1.8.3.1 

 R W  bar 
 R W  foo 
 R W  testing 
$ ssh u3 info 
hello u3, this is gitolite-test@server running gitolite3 v3.5.3.1-6-g5bdc750 on git 1.8.3.1 

 R W  foo 
 R W  testing 

上面的命令显示了你可以访问的仓库列表,并且对每个仓库,显示你是可以读取和写入该仓库,还是仅具有只读访问权限。

提示

关于命令和 URL 语法的说明

请记住,我们是在同一个 Unix 用户(即 gitolite-test)下运行 Gitolite 服务器,并模拟七个不同的用户。因此,你可以使用 git clone admin:gitolite-adminssh u1 info 等命令。在实际设置中,你将代表自己,服务器将位于其他地方。命令将是 git clone gitolite-test@server:gitolite-adminssh gitolite-test@server info

总结

在这一章,我们了解了为什么 Gitolite 很有用,查看了访问控制规则的示例,并对它的一些功能有了初步了解。我们还学习了 Gitolite 的基本概念,并创建了一个 Gitolite 测试实例,以便安全地进行尝试。

在下一章,我们将正确安装 Gitolite 并学习 Gitolite 管理的基本知识。

第二章:安装 Gitolite

前一章展示了如何设置一个我们可以称之为沙盒安装的 Gitolite,这适合用来实验软件并熟悉它。我们通过一个脚本完成了这一过程,隐藏了大部分安装细节,这样你就能快速进入重点内容

在本章中,我们将实际进行 Gitolite 的安装。我们将从服务器上的前提条件开始,然后进入实际的安装步骤。到本章结束时,你应该已经完成 Gitolite 的安装,并且它能够支持用户,安全地为他们提供仓库,并按照你指定的限制强制执行访问控制。

Gitolite 用户与托管用户

Gitolite 提供对多个 Gitolite 用户的访问,只使用服务器上的一个实际用户 ID。在开始安装和设置 Gitolite 之前,了解背后实际发生的事情以及如何实现这一点是很有用的。

Gitolite 使用一个名为托管 用户的 Unix 用户来为许多 Gitolite 用户提供仓库访问。托管用户可以是系统上的任何有效用户,但通常是 git 或 gitolite。这个是 Gitolite 在服务器上使用的唯一用户 ID,Gitolite 将其文件、自己的配置以及它管理的仓库放在此用户的主目录下。

Gitolite 可以在一台服务器上支持成千上万的 Gitolite 用户。这些用户在服务器操作系统看来并不是真实用户,他们无法访问服务器的 shell 命令行。然而,Gitolite 用户确实可以访问服务器上的一些仓库,以便他们对这些仓库执行 Git 命令。

区分不同用户

Gitolite 使用 ssh 进行用户身份验证。然而,尽管 ssh 通常允许使用密码或 ssh 密钥对进行身份验证,Gitolite 要求使用密钥对进行身份验证;不接受密码。

每个 Gitolite 用户在其桌面或笔记本电脑上都有一个 ssh 密钥对。一个密钥对由两个文件组成,通常称为id_rsa(私钥)和id_rsa.pub(公钥)。

公钥文件包含一行非常长的文本;以下是一个简化的示例:

ssh-rsa AAAAB3NzaC1[...]LBkU1XGGPnX adam@lab1.example.com

该密钥实际上太长,无法在此处打印,因此我们从中间删除了大约 350 个字符,并用省略号替代,但这仍然可以让你对其外观有一个大致的了解。

区分不同用户

上图展示了当用户连接到 Gitolite 服务器以访问 Git 仓库时发生的事件顺序,以及如何启用这个顺序。首先,每个用户将他们的公钥发送给 Gitolite 管理员。当 Gitolite 管理员将这些用户添加到 Gitolite 时,Gitolite 会将公钥添加到一个名为 .ssh/authorized_keys 的文件中,存放在主机用户的主目录中。然后,它在每一行的开头添加一个类似如下的字符串(对于用户 Adam)以及其他用户的相似字符串:

command="/home/gitolite/bin/gitolite-shell adam",[...] ssh-rsa [...]

这第一步是启用访问控制的关键。它是一次性操作,只有在管理员添加或删除用户时才需要重复。请注意 command 选项,其中包含一个程序名(gitolite-shell 的完整路径),以及其参数(用户名,在本示例中为 adam)——稍后会涉及到这一点。

第二步展示了当 Bob 尝试连接到服务器时发生了什么。Bob 运行 ssh 命令,无论是直接运行还是通过他的本地 git 客户端,形式为克隆、抓取或推送命令。服务器上的 ssh 守护进程处理连接尝试。Bob 的 ssh 客户端将提供一个公钥,而 ssh 守护进程将在授权密钥文件中查找该公钥,最终找到它。在我们的示例中,它在第二行找到了匹配项。

接下来,ssh 守护进程注意到在授权密钥文件中匹配行的命令选项。这告诉 ssh 守护进程,应该运行该选项中提到的命令,而不是客户端请求的程序,并包括提供的任何参数。这意味着 gitolite-shell 程序将以 Gitolite 用户名(在我们的示例中为 Bob)作为第一个参数来执行。这样,gitolite-shell 程序就知道是谁在连接。

提示

对于那些想知道 git 客户端实际想要的原始命令是什么的人,ssh 守护进程将其存储在一个名为 SSH_ORIGINAL_COMMAND 的环境变量中,并将其传递给 gitolite-shell 程序,后者知道如何处理它。

准备服务器

Gitolite 可以安装在任何 Unix 服务器上,包括 Linux、任何 BSD 系统以及一些传统的 Unix 服务器,如 AIX 和 HP-UX。话虽如此,以下是安装要求:

  • 一个具有 POSIX 兼容的 sh(shell)的 Unix 系统

  • Git 版本 1.7.1 或更高版本

  • Perl 5.8.8 或更高版本

  • OpenSSH 5.0 或更高版本

  • 如前所述的专用 Unix 用户,主目录必须位于支持符号链接的文件系统中,并允许可执行文件(即,未挂载 noexec 挂载标志)

    提示

    理想情况下,您应该使用一个全新的用户 ID,该用户 ID 没有任何现有的文件或目录,除了新创建的用户所获得的文件(例如 bashrc 或类似文件)。这样可以确保任何剩余的文件不会干扰。

获取 Gitolite 源代码

下一步是获取 Gitolite 的源代码。如果你的服务器可以连接到互联网,最简单的方法是运行git clone git://github.com/sitaramc/gitolite

提示

如果你无法直接访问互联网,可以通过其他机器进行操作。例如,你可以在一台可以连接互联网的服务器上运行前面的命令。然后,从那台中介服务器,你可以将整个 Gitolite 代码库压缩,传输到 Gitolite 服务器并解压。

安装代码

第一步是将源代码放在你希望的位置。Gitolite 的设计方式是它不需要 root 权限(除了创建托管用户时),因此你可以(通常应该)将其放在 Gitolite 托管用户的主目录中的某个位置。为了便于讨论,我们将选择$HOME/bin,因为这个目录通常包含在用户的 PATH 设置中。

以托管用户身份登录,并运行以下命令:

cd $HOME
mkdir -p $HOME/bin
gitolite/install --to $HOME/bin

对于熟悉诸如make prefix=/usr/local install等命令的人来说,从概念上讲,这与之并没有太大区别。

设置 Gitolite

现在代码已经放在正确的位置,我们需要进行设置。设置过程包括为 Gitolite 安装的主管理员添加一个 ssh 公钥。在本书中,我们假设管理员的名字是 Adam,因此他的 Gitolite 用户名将是adam,但在跟随过程中,请将文中提到的 Adam 或adam替换为你自己的名字。

ssh 是一个强大而复杂的工具。为了简化本章内容,我们将描述一组肯定有效的步骤,并提供适当的假设和限制。这些限制并非绝对必要,但它们确实有助于简化我们的过程,并消除潜在的麻烦点。如果你对 SSH 非常熟悉,可能会很容易绕过这些限制。

创建 ssh 密钥对

管理员需要首先在他们的主要工作站上为自己创建一对 ssh 密钥。在许多情况下,可能已经有一对 ssh 密钥,可能是为其他目的生成的。你应该在$HOME/.ssh目录下查找名为id_rsaid_rsa.pub的文件。如果没有找到此类文件,你可以通过运行ssh-keygen命令来生成一对密钥。

理想情况下,在生成 ssh 密钥对时,你应该选择一个强密码来保护你的私钥。为了避免每次都输入密码,你可以使用ssh-agent命令或其衍生工具,如 keychain 包。然而,这些细节超出了本书的范围。

同样,如果你之前创建了一个非默认的密钥类型(即,除了 RSA 协议 2(默认)以外的其他类型),那么可以假设你知道自己在做什么。Gitolite 应该能很好地支持 DSA 和 ECDSA 密钥对,但可能不支持 RSA 协议 1 密钥。

运行设置命令

现在你已经在工作站上拥有了密钥对,你需要将公钥(公钥!)传输到服务器上 Gitolite 托管用户的主目录中。你可以使用scp命令来完成这项操作,例如scp .ssh/id_rsa.pub git@host:adam.pub。你也可以使用其他任何方法,例如 rsync、sftp,甚至是 USB 闪存驱动器。只要文件能够到达并被重命名为adam.pub,方式并不重要。

给 ssh 专家的一个警告:不要轻易使用像ssh-copy-id这样的命令自动将这个密钥添加到 Gitolite 托管用户的授权密钥文件中。

一旦你复制了文件,你就准备好执行实际的设置命令,命令如下:

gitolite setup -pk adam.pub

这个命令应该会输出类似于以下的内容:

Initialized empty Git repository in /home/gitolite-test/repositories/gitolite-admin.git/ 
Initialized empty Git repository in /home/gitolite-test/repositories/testing.git/ 
WARNING: /home/gitolite-test/.ssh/authorized_keys missing; creating a new one 

你可以忽略关于授权密钥文件被创建的警告——这是第一次执行此操作时非常正常的情况。至此,Gitolite 的安装和设置已经完成。

检查你新的 Gitolite 服务器

很少有 Gitolite 管理任务需要登录到服务器并使用命令行。大多数日常维护任务(特别是添加用户和仓库)都是通过对一个名为gitolite-admin的特殊仓库进行更改,然后将这些更改推送到服务器来完成的;也就是说,管理员必须执行以下操作:

  1. 克隆gitolite-admin仓库。

  2. 添加一些文件或更改现有的文件。

  3. 提交更改。

  4. 将更改推送到服务器(管理员是被允许向gitolite-admin仓库推送的用户)。当推送完成后,Gitolite 在服务器端会调用特定的脚本来执行请求的更改。

你应该能够通过运行git clone git@server:gitolite-admin从工作站克隆gitolite-admin仓库。Git 将使用 ssh 连接到服务器上的"git"用户。默认情况下,它会查看你的$HOME/.ssh目录,找到你的 ssh 密钥对,并将公钥提供给服务器进行身份验证。之后,操作基本按照前面关于区分用户的部分继续进行,Gitolite 会为你提供访问该仓库的权限。

你现在应该能看到git clone操作成功后的常见消息,你可以输入cd gitolite-admin来查看下载的内容:

$ cd gitolite-admin 
$ ls -a 
.  ..  conf  .git  keydir 
$ ls -a conf keydir 
conf: 
.  ..  gitolite.conf 

keydir: 
.  ..  adam.pub 

你可以查看公钥存储的位置。请注意,Gitolite 对你的 Gitolite 用户名的定义完全来自于keydir目录中公钥文件的名称。这就是为什么当你从工作站复制id_rsa.pub文件时,必须将其命名为adam.pub

小贴士

ssh 专家可能会注意到,公钥文件中的注释字段会被忽略;尽管有很多人似乎认为它有更高的用途,但将其用于改变系统行为违背了“注释”一词的传统含义。

添加用户

虽然我们将在后续章节详细介绍如何添加用户,但你可能希望立刻添加一个测试用户。假设你要添加 Bob;以下是你可以这样做的步骤:

  1. 获取他的公钥,并将其重命名为bob.pub

  2. 将其复制到上面看到的keydir目录(也就是你本地克隆的gitolite-admin仓库中)。

  3. 添加文件、提交并推送。

添加仓库

查看conf/gitolite.conf文件中的内容,显示如下:

$ cat conf/gitolite.conf 
repo gitolite-admin 
 RW+     =   adam 

repo testing 
 RW+     =   @all 

要添加一个新仓库,编辑该文件并添加一个类似之前添加的仓库行,后面跟着一个访问规则行,暂时按照之前显示的语法进行。保存文件,添加它,提交更改并推送提交。你应该立即看到来自远程 Git 的成功推送响应,同时还会看到类似以下的内容:

remote: Initialized empty Git repository in /home/gitolite-test/repositories/t2.git/ 

这表示新仓库已经可以使用。

总结

在本章中,我们安装了 Gitolite,了解了特殊的gitolite-admin仓库及其内容,并添加了一个新用户和一个新仓库。在下一章中,我们将讨论 Gitolite 管理的 Git 仓库服务器对用户的展示效果以及他们能进行的操作。

第三章:您的用户与 Gitolite

既然我们已经成功安装了 Gitolite,接下来就该讨论用户如何与 Gitolite 管理的系统互动,以及他们需要了解什么才能开始使用它。这将使您能够让一些高级用户开始使用该系统,同时在我们继续深入学习 Gitolite 的过程中,您仍然能够继续了解更多内容。作为管理员,您将需要做出很多决策,诸如仓库和分支的命名规范、访问规则的严格程度等,以及许多您在后续学习中会了解到的其他方面。这些高级用户可以通过反馈或作为决策的参考帮助您。

访问 Git 仓库

在讨论如何访问 Gitolite 管理的仓库之前,我们首先需要了解 Git 仓库通常是如何访问的,即当您不使用 Gitolite 时。

Git 服务器、SSH 和 HTTP

我们首先回顾一下用户如何访问普通 Git 服务器。Git 仓库使用 URL 作为定位符,因此当用户克隆、拉取或推送到远程仓库时,都是通过合适的 URL 完成的。Git URL 与其他 URL 并没有太大区别,而 git-clone 的 man 页面中有一节专门介绍它们,您可以看到所有可以使用的语法变体。

然而,对于需要认证的远程访问,实际上只有两个协议是值得关注的:SSH 和 HTTP。在这两者中,基于 SSH 的访问更为常见,因为它设置快捷且简单;即使是在刚安装好的 Unix 系统上,通常也不需要额外的配置就能让它工作。

如之前主页所述,SSH URL 的一般形式为 ssh://[user@]host.xz[:port]/path/to/repo.git。当您输入一个 URL,如 ssh://git@server.example.com/repo 以访问远程 Git 仓库时,通常会要求您输入密码,除非已经为访问远程主机配置了 SSH 密钥对。一旦授权访问,SSH 守护进程会在远程端运行相应的 Git 命令,与本地端的 Git 客户端进行通信。

访问 Gitolite 仓库

在上一节的背景知识基础上,我们准备好了解当用户通过 Gitolite 访问 Git 仓库时,情况会发生哪些变化。

注意

本节将包含大多数管理员需要提供给用户的基本材料,或者向他们解释如何访问 Gitolite 服务器。然而,根据用户对 SSH 和相关主题的熟悉程度,您可能需要通过补充信息、示例或特定站点的说明来扩展这些内容。

SSH 密钥对

最显著的变化是不再支持密码访问;用户必须使用密钥对并将公钥发送给管理员,以便将其添加到 Gitolite 中。如果未执行此操作,Gitolite 无法识别该用户。

如果他们还没有 ssh 密钥对,应该在自己的工作站上生成一个。

你的用户需要使用ssh-keygen命令来创建密钥对。如果他们选择了默认选项,这将生成两个文件,分别是包含私钥的id_rsa和包含公钥的id_rsa.pub。在 Windows 系统中,命令会返回文件创建的完整路径,而在 Unix 系统中,文件会存储在$HOME/.ssh中。

注意

理想情况下,用户应为私钥设置密码短语以增强安全性,并使用ssh-agent以方便使用;然而,这些内容超出了本书的范围。任何与 ssh 相关的文本或网站应提供足够的详细信息,OpenSSH 软件包附带的文档也会有所帮助。

然后,他们会将公钥(文件名以.pub结尾)发送给你,以便你可以将他们添加为用户。

仓库命名

第二个变化是,每个仓库的名称将是你作为管理员在conf/gitolite.conf文件中创建的名称,我们在前一章的最后部分简要地看到过。实际上,Gitolite 会在托管用户账户中的$HOME/repositories下创建所有的仓库,但只有你(管理员)需要知道这一点。就用户而言,要访问在配置文件中列出的repo foo仓库,使用的 URL 就是git@server:foo(或者更长形式的ssh://git@server/foo)。

此外,请注意,仓库名称后面的.git对于 Git 命令(即clonefetchpushls-remotearchive)来说是可选的。Git 本身无论有无.git都能正常工作,因此为了保持一致,Gitolite 也做了相同的处理。然而,在与 Gitolite 交互时,比如运行涉及仓库的 Gitolite 命令时,你必须使用裸名称(即没有.git结尾的名称),这是 Gitolite 在错误信息或输出信息中显示的名称。

从 Gitolite 获取信息

一旦你的用户访问了 Gitolite,他们可能会希望查看自己可以访问哪些仓库。最简单的方法是运行info命令,这是所有远程用户都可以使用的:

$ ssh git@server info 
hello adam, this is git@server running gitolite3 v3.5.3.1-7-g5f88a09 on git 1.8.3.1 
 R W  gitolite-admin 
 R W  t2 
 R W  testing

这会告诉你你的 Gitolite 用户名是什么(在此案例中是adam),你可以访问哪些仓库,以及你是否被允许读取和写入,或者只能读取但不能写入仓库。除此之外,该命令还会告诉你服务器上运行的 Gitolite 和 Git 的版本,这可能会有帮助。

Gitolite 命令

info命令当然不是唯一可以提供给用户的命令;还有其他一些命令。如你从前面的部分所猜测的,运行 Gitolite 命令的通用格式就是ssh git@server command-name command-arguments,其中参数当然是可选的。

方便的是,Gitolite 还提供了一个命令来列出所有可用的命令:

$ ssh git@server help 
hello adam, this is gitolite3 v3.5.3.1-7-g5f88a09 on git 1.8.3.1 

list of remote commands available: 

 desc 
 help 
 info 
 perms 
 writable

如你所见,这为远程用户提供了他们可以运行的命令列表。(其中一些命令只能在后续章节中解释)

此外,如果你在 Gitolite 托管用户的命令行下运行 gitolite help,你将获得所有可用命令的列表,而不仅仅是启用了远程访问的命令。

获取命令的帮助

获取命令的帮助很简单。每个 Gitolite 命令在调用时如果只带有 -h 参数,都会返回一条帮助信息。例如,info 命令的帮助信息如下:

$ ssh git@server info -h 

Usage:  gitolite info [-lc] [-ld] [<repo name pattern>] 

List all existing repos you can access, as well as repo name patterns you can 
create repos from (if any). 

 '-lc'       lists creators as an additional field at the end. 
 '-ld'       lists description as an additional field at the end. 

The optional pattern is an unanchored regex that will limit the repos 
searched, in both cases.  It might speed up things a little if you have more 
than a few thousand repos. 

如前所述,某些选项涉及到 Gitolite 中我们尚未遇到的功能,等到相关内容讲解时,这些选项会变得更加清晰。

排查 SSH 问题

当你开始为系统添加第一个用户时,可能会遇到一些与 SSH 相关的问题。本节将简要讨论这些问题,并解释如何识别和解决它们。

授权,而非身份验证

首先,我们需要一些基本定义。身份验证是验证你是否是你所声称的人。授权是确定你想做什么并决定是否允许你执行该操作的过程。授权发生在身份验证之后(系统只能在确认你身份之后,才能决定你是否被允许做某事!)。

Gitolite 只关注授权;它不进行身份验证。身份验证交由 SSH 服务器或 Web 服务器来处理。

提示

本书不涉及 HTTP 模式;请参考 Gitolite 的在线文档来使用该模式。

一旦 SSH 服务器验证了用户身份,它会使用 SSH 授权密钥文件($HOME/.ssh/authorized_keys)中的 command 选项来调用 Gitolite 并传递用户名。Gitolite 随后决定该用户是否被允许访问该仓库。

重复的公钥

如果用户的公钥在设置 Gitolite 之前已经是授权密钥文件的一部分(可能是为了允许他获得登录 shell 并运行 Unix 命令),那么该密钥将在授权密钥文件中出现两次——一次是原始的,另一次是带有 command 选项以及 Gitolite 在 keydir 目录中为每个公钥添加的其他选项。

然而,如果授权密钥文件中出现相同的密钥两次,ssh 服务器只会查看第一次出现的密钥。同时,Gitolite 会尽力确保已经拥有正常访问权限的密钥继续保持这种权限,因此它会将 Gitolite 行放在比默认访问权限更严格的行之后。意味着有 shell 访问权限的 Gitolite 主机用户将无法使用 Gitolite,因为该密钥不会触发 Gitolite。他们需要为 Gitolite(即仓库)访问创建并使用不同的 ssh 密钥对。接着,他们需要在客户端管理这两个密钥对,可能会使用 $HOME/.ssh/config 来帮助管理。进一步解释 ssh config 文件以及如何帮助你选择使用哪个密钥超出了本书的范围。然而,几乎任何一本关于 ssh 的书籍,以及你系统上 ssh 的主页面,都应该提供这些信息。

诊断公钥问题

诊断公钥问题的最佳方法,如上一节所述,是运行 Gitolite 随附的 sshkeys-lint 程序。以下是一个例子,故意创建了两个公钥问题。第一个问题是我们重复使用了已经有 shell 访问权限的密钥,将其作为 u2.pub 添加到 Gitolite 中。第二个问题是我们将文件 u5.pub 复制为 u6.pub。在这些更改之后,运行 sshkeys-lint 命令的输出如下:

$ gitolite sshkeys-lint 
sshkeys-lint: ==== checking authkeys file: 
sshkeys-lint: WARNING: authkeys line 5 (user u2) will be ignored by sshd; same key found on line 1 (shell access) 
sshkeys-lint: WARNING: authkeys line 9 (user u6) will be ignored by sshd; same key found on line 8 (user u5) 
sshkeys-lint: ==== checking pubkeys: 
sshkeys-lint: admin.pub maps to user admin 
sshkeys-lint: u1.pub maps to user u1 
sshkeys-lint: u2.pub maps to shell access 
sshkeys-lint: u3.pub maps to user u3 
sshkeys-lint: u4.pub maps to user u4 
sshkeys-lint: u5.pub maps to user u5 
sshkeys-lint: WARNING: u6.pub appears to be a COPY of u5.pub 
sshkeys-lint: u6.pub maps to user u5 

3 warnings found 

如你所见,该命令列出了潜在问题,首先是在授权密钥文件($HOME/.ssh/authorized_keys)中,然后是在 Gitolite 拥有的公钥中。

SSH 最佳实践

我们现在已经了解了如何排查 ssh 问题。然而,最好从一开始就避免这些问题,一个避免问题的好规则是:不要给任何用户提供服务器的 shell 访问权限。即使是你作为管理员,也应该登录到其他用户 ID,运行 su - git,然后在需要在 Gitolite 主机用户的命令行上执行操作时提供密码。除非你非常熟悉 ssh,否则应该让授权密钥文件中的所有密钥都由 Gitolite 管理。这样可以消除大多数常见的 ssh 密钥问题。

总结

在本章中,我们学习了如何将用户添加到新的 Gitolite 安装中,以及如何查找和修复 ssh 密钥问题。在下一章中,我们将讨论如何创建新的仓库。

第四章:添加与移除用户

Git 支持两种用于一般用途的远程传输方式——ssh 传输和 HTTP 传输。在这两者中,ssh 传输更容易设置,因为大多数系统已经安装、配置并准备好接受经过身份验证的连接的 ssh 服务器。这通常不适用于 HTTP,即便有可用的 HTTP 服务器,你仍然需要为 Gitolite 配置它。

因此,我们将探讨如何使用 ssh 传输添加和移除服务器上的用户。早些章节已经向你展示了如何添加用户的基本方法。现在是时候深入探讨一些细节和特殊情况了。

添加用户

严格来说,Gitolite 并不知道用户来源。如果你回忆一下前一章关于身份验证和授权的部分,你会了解到 Gitolite 本身并不进行身份验证,它将这个任务交给了 ssh 服务器(或者可能是 HTTP 服务器)。然而,Gitolite 确实帮助进行基于 ssh 的身份验证,因为这是 Gitolite 最常见的使用方式,并且服务器及其配置在大多数情况下都是标准且可预测的。

提个警告:不要在服务器上手动添加新用户。Gitolite 的用户、仓库和访问规则是通过修改一个名为gitolite-admin的特殊仓库,并将这些更改推送到服务器来维护的,正如在第二章 安装 Gitolite 中所解释的那样。因此,几乎你所做的所有操作都会在gitolite-admin仓库的克隆版本中进行。

要添加一个用户,例如 Alice,首先获取她的公钥(通常在她的工作站上是$HOME/.ssh/id_rsa.pub)。然后,将其复制到名为keydir的目录中(该目录位于你本地的gitolite-admin仓库中),并使用用户名作为文件名的基础名(例如,用户 alice 对应的文件名是alice.pub)。最后,通过 git add keydir/alice.pub 添加密钥,接着提交并推送。

这里有一些额外的注意事项,以确保你能够正确地执行此操作:

  • 所有公钥文件必须以.pub结尾,并且必须采用 OpenSSH 的默认格式。

  • 用户名必须以字母或数字字符开头,之后可以包含字母、数字、连字符、下划线、句点和 @ 符号。有效的用户名示例有sitaramsitaram.chamartysitaramc@gmail.com等。

背后原理

以下是服务器上 Gitolite 处理用户和密钥的方式。这些操作是通过gitolite-admin仓库中的一个 post-update 钩子触发的,Gitolite 会自动安装这个钩子。

  1. Gitolite 会读取授权密钥文件($HOME/.ssh/authorized_keys),并记录所有非 Gitolite 密钥(即未来自 Gitolite 的密钥)。

  2. 然后,它会读取gitolite-admin仓库中keydir目录下的所有公钥。这是递归的;你可以在任意级别的子目录中存放公钥。

  3. 在读取每个公钥时,它会将其与已经处理过的每个公钥进行比较,包括非 Gitolite 密钥。如果匹配,它会打印一个警告,说明 ssh 服务器将忽略授权密钥文件中该密钥的第二个及之后的出现。

  4. 然后,它会将 ssh 选项添加到每个公钥行。这些添加的选项是 no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty。这些安全选项通常建议用于任何打算为交互式或 shell 用户提供服务的 ssh 服务器。

  5. command 选项被添加,指向已安装的 gitolite-shell 位置,并附加一个额外的参数:Gitolite 用户名。用户名通常是公钥文件的基础名称(即去掉任何目录前缀和 .pub 后缀);如果这个规则不完全适用,请参阅后面关于具有多个密钥对的用户的章节。

如果你还记得在第二章中的图 1(为了方便,下面已重新显示),安装 Gitolite,这确保了当用户访问服务器并提供相应的公钥进行认证时,他的连接会在 ssh 守护进程成功认证后,传递给 gitolite-shell 程序,并将他的 Gitolite 用户名作为命令行参数传递。

幕后

具有多个密钥对的用户

有些用户有多个密钥对。例如,他们在工作中使用笔记本电脑和台式机。有些人可能还在家里有另一台机器,或者直接在服务器上工作。

你可能认为在每台机器上使用相同的密钥对是最好的——毕竟,它们都属于同一个用户——但这并不是一个好主意。私钥被泄露的风险随着安装它的机器数量的增加而增加,这可不是一件好事。

因此,Gitolite 允许用户拥有多个公钥。实际上,有两种方法可以将多个公钥文件关联到同一个 Gitolite 用户。

第一种方法是使用子目录。以下是 gitolite-admin 仓库的示例树结构,展示了 keydir 子目录中的一组密钥:

具有多个密钥对的用户

如你所见,有两个名为 carol.pub 的文件。这两个文件都会在授权密钥文件中生成如前所述的行,用户名设置为 carol。无论她使用哪个密钥,Gitolite 都会将认证后的 Gitolite 用户名视为 carol,并据此授权她的访问权限。

第二种允许多个公钥的方法是使用位置后缀。位置后缀是一个 @ 符号,后跟一个由字母数字字符、连字符或下划线组成的单个单词。例如,如果 Alice 有一个用于她笔记本电脑的密钥和一个用于她桌面的密钥,她可能会将密钥发送给你,分别为 alice@laptop.pubalice@desktop.pub

提示

位置后缀不能包含句点,否则看起来像是电子邮件地址。就 Gitolite 而言,名为 alice@laptop.pub 的公钥文件属于名为 alice 的用户,但名为 alice@lap.top.pub 的文件属于用户 alice@lap.top,这是一个在语法上完全有效的电子邮件地址。

给一些用户提供 shell 访问权限

您的一些用户可能确实需要以托管用户身份登录到服务器并使用 shell 命令行。如果这仅适用于一两个用户,处理这个问题最简单的方法是让他们使用两把不同的密钥——一把用于 Gitolite 访问,另一把用于 shell 访问。第二把密钥将在服务器上的授权密钥文件中手动安装,并且不会包含 Gitolite 密钥的 command 和其他选项。

提示

非 Gitolite 密钥必须在授权密钥文件的开始位置添加,或者至少在标记行 # gitolite start 之前添加。在 Gitolite 的开始和结束标记行之间添加的密钥将在下次推送 gitolite-admin 仓库时被删除。

然而,这需要在服务器端和客户端都仔细处理第二个密钥。一些用户可能不愿意学习如何在他们那边处理多个密钥,以及如何为每次访问提供正确的密钥。

还有另一种处理此问题的方法,使用单个密钥。以下是您需要做的事情,以便为任何用户提供 shell 访问权限。

首先,确定哪些用户需要此访问权限,并列出这些用户。用户名应与 gitolite.conf 文件中使用的纯用户名一致,例如 alicebob。不要使用 alice.pubalice@laptop.pub 或任何类似的变种。

接下来,将所有这些用户名添加到服务器上的一个简单文件 $HOME/.gitolite.shell-users 中。每行只写一个用户名,不要在前后留空格。

然后,编辑服务器上的文件 $HOME/.gitolite.rc,并取消注释以下行:

# SHELL_USERS_LIST          =>  "$ENV{HOME}/.gitolite.shell-users",

# 'Shell',

最后,运行以下命令:

gitolite trigger POST_COMPILE

在 Gitolite 之外管理密钥

如果您有不同的密钥管理方法并且更喜欢使用它,则不必让 Gitolite 管理密钥。例如,您可能使用基于 LDAP 的 ssh 守护进程,它为多个企业应用程序和系统集中管理用户的密钥,因此您希望利用这一点来管理 Gitolite 用户。

Gitolite 只要确保这些要点被涵盖,就可以与任何方法正常工作:

  • SSH_ORIGINAL_COMMAND 环境变量应包含客户端发送的原始 Git 命令。通常,这将是类似 git-upload-pack 'repo.git'(包括单引号)或对于推送操作,git-receive-pack 'repo.git' 的形式。

    当 ssh 配置指定某种强制命令时,ssh 守护进程会自动设置此变量(有关如何强制执行命令的详细信息,请参阅 ssh 文档)。

  • 运行的命令应该是 gitolite-shell 程序的完整路径,无论你将其安装在哪里。例如,它可能是 /home/git/gitolite/src/gitolite-shell

  • 该程序应该只接受一个参数:ssh 认证用户的 Gitolite 用户名。

从 LDAP 获取用户组信息

Gitolite 使得能够在企业中有限地使用已经可用的用户数据。

最常见的使用场景是企业已经有一个包含所有用户及其在组织中角色的 LDAP 数据库。管理员希望利用这些信息来减少为用户授权 Gitolite 仓库的工作量。如果管理员能够仅通过指定组名来设定权限,而 Gitolite 能够自动识别出用户属于哪些组,那将是非常理想的。

Gitolite 可以方便地查询 LDAP 数据库并以某种方式获取所需的信息。解决方案涉及编写一个辅助程序,该程序执行以下操作:

  • 接受一个 Gitolite 用户 ID 作为第一个(也是唯一的)参数。

  • 如果需要,以某种方式将用户 ID 转换为适合 LDAP 查询的格式。例如,可能需要为查询添加特定的组件。

  • 使用此用户 ID 作为查询参数,查询 LDAP 数据库以获取用户详细信息。执行此操作的程序需要具备查询 LDAP 数据库的凭证和权限。

  • 一旦查询返回,提取用户的角色/组信息。

  • 如果需要,再将这些组信息转换为你在 Gitolite 访问规则中使用的名称。

  • 打印出结果组的列表,所有内容一行显示,且由空格分隔。

程序执行完毕后,将其保存为 $HOME/bin/ldap-query-groups 并进行测试。该程序只需要一个命令行参数并将结果打印到终端,因此对 Gitolite 来说这样做并没有实际危害。

测试完毕后,编辑 Gitolite 的 rc 文件($HOME/.gitolite.rc),并在 %RC 变量的定义中某处添加以下行(包括结尾的逗号),最好靠近顶部:

GROUPLIST_PGM           =>  "$ENV{HOME}/bin/ldap-query-groups",

删除用户

删除 Gitolite 用户相对简单。只需从 gitolite-admin 仓库的一个克隆中删除所有与该用户相关的密钥(如果该用户有多个密钥,参见前面的章节)。这应使用 git rm 命令,而不是普通的 rm 命令。

然后,提交更改并推送。

你可以通过几种不同方式确认删除操作。首先,你刚刚删除的密钥应该不再出现在服务器的 $HOME/.gitolite/keydir 目录下。其次,如果你在授权密钥文件($HOME/.ssh/authorized_keys)中搜索该用户,应该找不到该用户。

总结

在本章中,我们了解了如何添加和删除用户,以及如何处理一些偶尔会出现的其他特殊需求。在下一章中,我们将讨论如何添加和删除代码库。

第五章:管理仓库

使用 Gitolite 的一个优点是你不需要手动创建新仓库、设置权限等。Gitolite 会根据gitolite-admin仓库中的特定文件(conf/gitolite.conf)的内容为你完成所有这些操作。本章将展示如何将新仓库添加到 Gitolite,并且如何将现有仓库纳入 Gitolite 的管理。

添加仓库

要添加一个新仓库,你需要在gitolite-admin仓库的克隆中执行以下操作。

首先,编辑conf/gitolite.conf文件。这个文件应该已经有一些内容,例如:

repo    gitolite-admin
 RW+     =   adam
repo    testing
 RW+     =   @all

这基本上意味着只有名为adam的用户有权限对gitolite-admin仓库进行更改,所有用户都有权限对测试仓库进行更改。

要添加一个新仓库,你需要添加一个repo行,并至少添加一条访问控制规则。例如:

repo    my-repo-1
 RW+     =   adam

这将创建一个名为my-repo-1的仓库,使得adam是唯一能够读取或写入它的用户。

如果没有添加访问规则,仓库将不会被创建。例如,如果你有如下内容:

repo    my-repo-1
repo    my-repo-2
 RW+     =   adam

那么my-repo-1仓库既不会被创建,也不会以任何方式被 Gitolite 识别。

但是,你可以在repo行中指定多个仓库名称,所以下面的命令完全是可以的:

repo    my-repo-1 my-repo-2
 RW+     =   adam

添加现有的仓库

如前一节所述,添加一个仓库将会在服务器上创建一个空的裸仓库。你可以通过推送你喜欢的任何内容来填充这个仓库。这肯定是将现有内容纳入 Gitolite 管理的一种方式,只要这些仓库之前没有自己的 Git 钩子。

然而,大多数站点将有几个现有的仓库需要纳入 Gitolite 管理,而通过创建一个空仓库并从工作站推送内容的方式可能会非常慢,特别是当内容已经在服务器上时。本节将告诉你如何快速轻松地做到这一点。

提示

始终建议有备份,这样在按照描述的步骤操作时如果发生意外情况,你可以恢复。

首先,确保你查看的现有仓库是裸仓库。裸仓库是没有工作树的仓库。你不能可靠地推送到一个附带工作树的仓库,因此服务器上的仓库必须始终是裸仓库。通常,通过给git initgit clone命令传递--bare选项来创建裸仓库。本章后面的部分会描述如何将非裸仓库转换为裸仓库。

接下来,将裸仓库移动或复制到$HOME/repositories,这是 Gitolite 期望找到它所管理的所有仓库的位置。在进行此操作时,请确保仓库的名称以.git结尾。

注意

在常见的 Git 使用中,裸仓库通常以.git结尾命名,而非裸仓库没有扩展名。然而,在 Gitolite 中,结尾的.git是必要的,不再仅仅是一个约定。

一旦所有仓库就位,运行gitolite setup命令。

最后,在gitolite-admin仓库的克隆中,将这些仓库添加到conf/gitolite.conf文件中,如前所述,保存更改,添加、提交并推送。

常见问题及故障排除

在某些情况下,你可能需要修改前面描述的程序。本节将描述你可能遇到的一些问题以及如何解决它们。

所有权和权限

Gitolite 期望$HOME/repositories中的所有文件和目录都归 Gitolite 托管用户所有,并且该用户被允许对所有文件进行写操作。如果此条件未得到满足,Git 和 Gitolite 都会受到影响。

这种条件被违反的最常见原因是管理员以 root 身份复制了一些文件(比如一些现有的仓库)。当文件以 root 身份复制时,通常不会赋予它们所在目录的所有者和组 ID,而是原始所有者或执行复制操作的用户的 ID。

你可以通过在相关仓库上运行ls -alR来查看;如果所有文件和目录的所有者和组不是git(托管用户),那么你需要修改该仓库的所有权。

为了解决这个问题,请运行以下命令:(与前面章节一样,我们假设 Gitolite 托管用户是 git;如果不是,请相应替换。)

# as root
cd ~git
chown -R git:git repositories
chmod -R u+rwX repositories

如果没有所有权问题,但由于某种原因你确实遇到了权限问题,你可以省略chown命令,并以git身份执行其余操作(也就是说,你无需以 root 身份登录)。

将非裸仓库转换为裸仓库

裸仓库是没有工作区的仓库。相反,在非裸仓库中包含在特殊的.git目录中的文件和目录,会直接放置在仓库的顶级目录下。

将非裸仓库转换为裸仓库的一种方式是使用--bare选项进行克隆。以下命令是实现这一操作的最通用方式:

git clone –bare /somewhere/repo ~/repositories/repo.git

然而,这会将源代码的完整副本复制到目标位置,如果仓库非常大,这可能会成为一个问题。但如果源仓库和目标仓库位于同一文件系统中,Git 提供了一个非常有用的优化,你可以利用这个优化。只需在clone命令中添加-l选项即可:

git clone –bare -l ~/somewhere/repo ~/repositories/repo.git

-l选项告诉 Git 使用硬链接,而不是将文件复制到新仓库中,这几乎是瞬间完成的,无论仓库的大小如何。由于使用硬链接(而不是符号链接),在克隆完成后,你可以自由删除源仓库,它不会对新创建的目标仓库造成任何影响。

无论哪种方式,到此为止,你需要将源仓库中hooks目录下已存在的钩子文件复制到目标仓库的hooks目录中。因为clone方法不会携带钩子。

提示

Git 专家会意识到,将一个非裸仓库(如my-repo)转换为裸仓库的另一种方法是将.git目录提升一级,并重命名为my-repo.git。即通过运行命令mv my-repo/.git my-repo.git。此时,可以删除旧的my-repo目录。像往常一样,在删除任何东西之前,确保有备份。

Gitolite 与更新钩子

Gitolite 的推送操作访问控制机制涉及钩入 Git 的update钩子机制(查看githooks的手册页面,了解 Git 提供的各种钩子)。

因此,如果你的现有仓库已经有了一个更新钩子,运行之前描述的gitolite setup将会删除这个现有的钩子,并用 Gitolite 自己的更新钩子替代它。

Gitolite确实提供了一种机制,允许你的旧update钩子也能够运行,但它需要由 Gitolite 来运行。为此有一个支持的机制,称为VREF,将在第七章《高级访问控制与配置》中简要描述,并在第十章《理解VREFs》中详细说明。

总结

本章向你展示了如何添加自己的代码库,并介绍了一些在将现有代码库引入 Gitolite 控制时可能出现的常见问题。上一章已经涵盖了添加用户的内容,因此我们现在可以开始查看访问控制了。访问控制是 Gitolite 存在的原因,下一章将(终于!)向你展示一些基本的访问控制语法、规则及其他细节。

第六章:开始使用访问控制

到现在为止,你已经知道如何将新用户添加到 Gitolite 系统中,以及如何创建新仓库或将现有仓库纳入 Gitolite 的控制。现在是时候将这些内容结合起来,了解访问控制,这是大多数网站安装 Gitolite 的主要原因。

基本访问控制示例

任何 Git 管理员首先想到的访问控制方面,是防止某些人访问仓库的能力。我们将从一些简单的例子开始,然后再描述语法。这里是一个非常简单的规则集,适用于一个名为foo的仓库:

repo foo
 R    =  alice
 RW   =  bob
 RW+  =  carol

这些规则规定了 Alice、Bob、Carol 以及其他任何 Gitolite 用户可以对该仓库执行哪些操作。正如你从 Alice 的简单 R 权限中可能猜到的(或者换句话说,正是因为她的权限字符串中没有W),她仅被允许读取该仓库,这意味着她可以使用git clonegit fetchgit ls-remote命令,但不能以任何形式使用git push

Bob 和 Carol 都被允许向仓库推送更改。和之前一样,你可能从他们权限行中的W字符中猜到了这一点。可能不那么显而易见的是 Carol 权限字符串中+字符的意义。这个额外的字符允许 Carol 进行强制推送,即使新对象不是旧对象的后代,她也可以将新对象推送到引用中。在 Git 术语中,这叫做非快进推送,即使在没有访问控制系统的普通 Git 使用中,也需要用户明确采取操作才能成功。如果你不熟悉这个概念,请参考 man git-push和其他 Git 文档。

选择+字符是因为它提醒你在git push命令中使用+来选择性地强制推送某些引用(refs)。(有关更多细节,请参考git push命令 man 页面中的refspec字段描述)。尽管我们在本章中不会遇到这种用例,但为了完整性,我们还是应该提到,+字符还允许用户删除引用,尽管在git push命令中对应的语法并没有使用+符号。

对于许多安装来说,这个基本的例子已能满足所有需求——即区分那些只能读取但不能写入的人,并且在允许写入的人中,防止某些人进行危险的强制推送(或者更糟的是,删除一个重要的分支或标签)。

基本分支级别访问控制

下一个示例将展示另一个非常常见的需求:限制某些人(也许是初级开发者或实习生)只能推送特定命名空间中的分支。这是一个相当常见的情况,其中最重要的分支(例如 master,或者也许是 next,或者你工作流中使用的任何分支命名)只能由受信任的开发者修改,这些开发者大概已经审核了代码并认为它是可以接受的。初级开发者和/或实习生编写的代码通常不会被视为自动可接受,通常很有必要以某种方式对他们进行沙盒限制。

提示

有趣的是,这个特定的用例似乎更多地与开发者的信任和成熟度有关,而非单纯的安全问题。然而,在控制或防止此类行为方面,意外覆盖主分支和恶意覆盖主分支之间没有区别。

这是一个实现此类限制的访问控制规则集示例:

repo foo
 R          =  alice
 RW+        =  bob
 RW+  dev/  =  carol

这允许 Carol 读取该仓库并向其推送,但它阻止她推送任何除以dev/开头的分支之外的内容。相比之下,Bob 的访问规则在权限字段后没有指定任何内容,因此他可以推送任何分支。

配置文件的词法语法

现在我们已经看到了几个有用且可立即使用的示例,是时候查看conf/gitolite.conf文件的词法语法了(在 Gitolite 中,通常称其为配置文件)。

配置文件是一个纯文本文件,其整体语法遵循一些简单的规则。

Gitolite 用户名(例如alicebob等)必须以字母数字字符开头,并包含字母数字字符、句点、连字符、下划线或@符号。如果存在@符号,它后面必须跟着一个域名(即包含至少一个句点的内容)。这使得可以使用电子邮件地址作为用户名,这在组织中有多个 John Smith 时非常有用!

仓库名称必须以字母数字字符开头,并包含字母数字字符、句点、连字符、下划线或斜杠。然而,它们不能以斜杠开头或以斜杠结尾。

允许使用注释;语法与 shell、perl 等相同。任何以#符号开头的内容都被视为注释并被丢弃。

提示

默认情况下不允许使用续行(C 风格)。但是,通过编辑$HOME/.gitolite.rc文件(我们将在第七章《高级访问控制与配置》中详细讨论),你可以启用此功能。

访问控制规则的语法

访问控制列表有一个简单的结构。简而言之,它的结构如下:

repo <one or more repos or repo-groups>
 <permission>    <zero or more refexes>  =  <one or more users or user-groups>

一个配置文件有一个或多个repo行。每个 repo 行包含单词repo,后跟一个或多个仓库名称或仓库组名称(我们将在本章稍后讨论组)。这些仓库或组名称必须都在同一行。

每个repo行后面跟着一个或多个适用于该仓库或组的访问规则。访问规则包括以下内容:

  • 一个权限字段(例如,R、RW 等)

  • 零个或多个引用模式(引用模式在下一节会详细讲解,但你已经见过的一个例子是dev/

  • 一个=符号作为分隔符

  • 最后是一个用户或用户组名称的列表

分支级别的访问控制和引用模式

本节将讨论 Gitolite 中一个非常重要的部分——指定谁可以对哪个分支或标签进行更改。作为历史背景,缺乏这个功能,正是当时最流行的 Git 服务器管理工具无法提供这一功能的唯一原因,进而促使了 Gitolite 的诞生。

提示

本节假设读者对正则表达式有一定了解。

要理解分支级别的访问控制以及如何在 Gitolite 中指定它们,我们应该首先快速回顾 Git 本身的相关概念,以便更容易理解。

Git 使用ref一词来指代分支和标签。分支名称通常为refs/heads/某个名称,而标签名称则为refs/tags/某个名称。当用户推送到 Gitolite 管理的仓库时,Gitolite 帮助你判断是否允许该用户更新正在更新的引用

因此,为了允许 Bob 推送名为 master 的分支,你可以写下:

RW+  refs/heads/master  =  bob

当用户推送到仓库时,Git 会将推送的引用名称提供给 Gitolite。Gitolite 随后会将该引用与访问规则行中的每个引用模式(refex)进行匹配。如果匹配成功,并且用户也出现在=符号的右侧,那么该推送将被允许。

然而,由于分支比标签更频繁地进行访问控制,Gitolite 假设任何不以refs/开头的内容都应被视为以refs/heads/为前缀。因此,你可以直接这样写:

RW+  master    =  bob

这将被视为refs/heads/master

严格来说,前述数据允许 Bob 推送任何以master开头的分支名称。要将规则限制为仅master,实际上应该将引用模式写为master$。但在实际应用中,这种情况很少需要。

控制标签同样简单。假设你想允许用户推送任何以字母 v 开头,后跟数字,后面可选地跟着其他内容的标签。以下是该规则:

RW+  refs/tags/v[0-9]  =  bob

正则表达式模式[0-9]表示从09之间的任意字符(包括 0 和 9)。

Gitolite 仅在开头锚定这个正则表达式,而不是在结尾。在正则表达式的术语中,这意味着在开始处隐式添加了一个 ^ 字符,但结尾处并没有添加 $ 字符。这使得 Bob 可以推送类似 v1v2.2v3.4.5 等标签,但不能推送 new-v1next-v2

因此,我们来定义一下refex:refex 是一个正则表达式(即正则表达式),用于匹配正在推送的引用。

使用拒绝规则

到目前为止,我们看到的规则允许你指定用户可以推送哪些分支或标签。然而,我们通常需要能够指定,例如,允许用户推送所有分支,除了 master

为了做到这一点,我们需要使用 Gitolite 所称的拒绝规则。以下是你如何为用户实施上述限制的示例:

-         master    =  bob
RW+                 =  bob

当 Gitolite 尝试确定是否允许推送某个分支时,它按顺序检查规则。当 Bob 将名为 next(即 refs/heads/next)的分支推送到此仓库时,第一个规则完全不匹配,因为 refs/heads/master 不是与 refs/heads/next 字符串匹配的模式。然后 Gitolite 检查下一个规则。由于此规则没有指定 refex,因此所有引用都将匹配,并且由于权限字段为 RW+,推送被允许。

然而,当 Bob 推送 refs/heads/master 时,引用匹配第一个规则中的正则表达式,由于该规则是拒绝规则,因此推送被拒绝。

细心的读者会注意到,规则的顺序在这里非常重要。如果我们交换这两个规则的顺序,那么我们想要实施的限制将会被移除。首先遇到的规则将是:

RW+      =  bob

这不指定 refex,因此会匹配所有引用。因此,操作可以继续进行。换句话说,拒绝规则根本不会被检查!

权限字段

我们现在已经看到了四种最常见的权限示例,因此总结它们是个好主意。

R 权限允许用户读取(克隆、获取)仓库,但不允许推送。RW 允许用户推送,但仅在快速前进推送或创建新分支时允许。非快速前进推送和删除是不允许的。而 RW+ 也允许非快速前进推送和删除。

只要你的规则集仅包含这三种类型的规则,它们的顺序并不重要。然而,正如你在上一节中看到的,当你使用第四种规则——拒绝规则(由单个减号表示)时,规则的顺序就变得很重要。

提示

虽然这些是最常用的权限,但并非全部。还有一些其他权限将在第七章的写操作类型一节中出现,高级访问控制和配置

定义用户和仓库组

Gitolite 允许你定义用户组或仓库组以便于管理。语法非常简单,并且不区分用户组和仓库组。例如,看看前一节中的拒绝规则示例:

-         master    =  bob
RW+                 =  bob

假设,现在不仅仅是 Bob,你还有几个用户必须被阻止推送到 master 分支,也许是因为他们都是初级开发者。一个方法是将他们每个用户的用户名添加到两条规则的后面,紧接着 Bob 的用户名,像这样:

-          master    =  bob carol dave
RW+                  =  bob carol dave

但这样会变得繁琐,而且如果有更多规则应用到同一组人身上,情况只会更糟。

然而,使用组时,你可以这样做:

@junior-devs    =  bob carol dave

-          master    =  @junior-devs
RW+                  =  @junior-devs

如你所见,这要方便得多,而且也更不容易出错。更重要的是,组名通常可以作为规则集的重要文档——未来的管理员可能不知道 Bob、Carol 和 Dave 是谁,也许会困惑他们为什么会被限制,但组名如果选得好,就能让一切变得非常清晰。

你也可以为仓库使用组名,例如:

@foss-repos  =  git linux apache gcc

repo @foss-repos
 R  =  @all

虽然在这种情况下,可能看起来不那么重要,因为组名仅替代了仓库名中的一次出现,但它仍然更简洁。而且,就像用户组名一样,仓库组名也能作为这些仓库性质的附加文档。

与大组合作

有时候你需要一个包含多个成员的组——成员数量远超一个行内可以轻松编辑的数量。为了使这更容易(并且因为 Gitolite 默认不允许续行),Gitolite 将每个组的定义视为累积的。这意味着,如果该组已经在之前定义过,新的成员将被添加到其中,而不是替换现有的成员列表。这使得你可以这样写:

@foss-repos  =  git
@foss-repos  =  linux
@foss-repos  =  apache

这将与我们在本节前面看到的单行定义产生相同的效果。

特殊的 @all 组

Gitolite 还有一个特殊的内置组叫 @all,它指代所有的仓库或所有的用户,具体取决于它的使用位置。一个常见的用途是允许某些特权用户访问所有仓库,可能是如下所示:

repo @all
 R            =  @QA-leads
 RW+          =  @dev-leads

另一个示例,让我们考虑一个只有 Adam 和 Dave 被允许推送 master 分支的情况。你可能会这样做:

repo foo
 RW+  master         =  adam dave
 -         master    =  @all

这里有一个拒绝规则,所以再次提醒,规则的顺序很重要。如果我们将拒绝规则放在前面,那么——因为 Adam 和 Dave 隐式地是 @all 的成员——他们也会被拒绝推送到主分支的权限。

包含语句

你还可以将访问规则和组定义分拆到多个文件中,并将它们包含到主配置文件(在你的 gitolite-admin 仓库中的 conf/gitolite.conf)中。例如,你可以将所有组信息(即哪些用户是哪些组的成员)保存在一个单独的文件中并包含它。语法非常简单,下面是一个示例:

include "groups.conf"

这将会在 conf 目录下查找一个名为 groups.conf 的文件,并在此处包含其内容。

高级用户可能注意到,这个命令也支持通配符。例如,你可能在 conf 目录下的一个子目录中有几个单独的配置文件,名为 foss。如果你不想单独列出它们,你可以这样写:

include "foss/*.conf"

规则汇总和委托

Gitolite 允许你将仓库的访问规则拆分为多个不一定是连续的部分。然后,它会将所有这些部分(按读取顺序)组合在一起,并将合并后的规则集应用到相关的仓库中。

这种行为有很好的应用场景。将组、包含语句和规则汇总结合起来,使得 Gitolite 管理比之前更为简便。以下是一个示例,带有一些注释,帮助你理解可以做哪些操作。

include "groups.conf"
# contains definitions of all groups used in the rest of the conf file.  All
# membership changes happen here

include "foss.conf"
# contains rules for open source repositories.  For example, "R = @all" is
# pretty much expected for such repositories.  There may be other rules
# specific to different FOSS repositories that may also be specified here

include "boss.conf"
# contains rules that define what kind of access management has to the
# development repositories.  For example, some of the managers may have
# read access to all repositories, so something like
#    repo @all
#      R  =  scott douglas
# is probably quite useful.

# repo-specific rules
# At this point you could have repository specific rules that do not fit neatly
# into any of the previous include files.  For example:
repo git
 RW+  =  linus junio

如你所见,仓库可以出现在主配置文件加载的任何或所有包含文件中。如果我们要求每个仓库的所有规则都集中在一个地方,那么以这种方式组织规则就变得不可能。

总结

我们现在已经了解了 Gitolite 的大多数基本访问控制功能,包括各种类型的访问规则、配置文件的语法,以及一些便捷的功能来简化管理。下一章,我们将讨论一些高级访问控制功能,如个人分支和VREFs

第七章:高级访问控制与配置

上一章中描述的基本访问控制方法足以满足大多数站点的需求,许多站点甚至没有进一步的配置。然而,Gitolite 有许多更多的功能,等待那些需要它们的用户。本章将介绍其中一些高级功能。每个功能我们都会尝试描述一个实际场景,说明该需求,然后解释该功能如何满足这个需求。

修改 rc 文件

Gitolite 的许多高级功能和配置选项是通过编辑rc 文件来管理的。该文件名为.gitolite.rc,通常位于 Gitolite 托管用户的主目录下。

该文件有很多注释,一般来说,查看文件时很容易理解每一部分内容的作用。

文件的大部分内容都在一个顶层定义中,形式如下:

%RC = (
...several variables defined...
)

如果你熟悉 Perl,可能会意识到这其实是一个 Perl 哈希,但并不需要懂 Perl 就能编辑这个文件。

文件中定义了几个简单的变量,例如:

 UMASK                           =>  0077,

当 Gitolite 文档(或本书)告诉你编辑 rc 文件中的变量时,最好先查找一下这些变量——大多数重要的变量已经存在于文件中,只是可能被注释掉,等待取消注释并根据需要编辑其值。

%RC块中的一个变量是名为ENABLE的列表变量,其定义如下:

 ENABLE => [
 ...several features listed...
 ]

一个示例功能如下:

 'info',

这将启用info命令。

同样,大多数功能已经在这里列出,但可能被注释掉。

为用户提供个人分支

当项目中有多个开发者时,通常需要共享仍在开发中的代码,以便进行评论、讨论、初步测试等。显而易见的解决方案是,每个开发者将代码推送到 Gitolite 服务器上的一个分支,并告知同事该分支的名称。可以为此创建一个专门的分支命名空间,使所有开发者都能访问,具体如下:

repo foo
 RW+    sandbox/  =  @developers

这种方法工作得很好,但如果在沙箱命名空间内的分支命名没有严格控制,可能会导致某个开发者不小心覆盖或删除另一个开发者的分支。

理想情况下,所需的应该是如下的方式:

 RW+    sandbox/alice/  =  alice
 RW+    sandbox/bob/    =  bob

依此类推,为每个需要访问仓库的用户添加相应的配置。

显然,这种方式并不具备可扩展性——如果你这样做,你必须为每个用户添加一行。事实上,这实际上是一种倒退,因为我们突然失去了将用户按组管理的所有优势,因为我们被迫为每个开发者使用单独的规则。

这种情况促使了 Gitolite 中所谓的个人分支功能的开发。该功能基于一个简单的理念,最好的方式是通过以下示例规则来描述:

 RW+    sandbox/USER/  =  @developers

其理念是,USER 这个词被斜线包围,代表了来自连接信息的认证用户名,只要该用户在右侧列出(或该用户所属的组被列出)。因此,如果用户 ID alice@developers 组的成员,并且 Alice 尝试访问该仓库,那么此规则有效地变成如下:

 RW+    sandbox/alice/    =  alice

这允许 Alice 向她自己的沙箱分支写入;即以 sandbox/alice/ 开头的分支名称。请注意,这不允许一个名为 sandbox/alice 的分支——沙箱是指一组分支,而不仅仅是一个分支。

由于 Gitolite 不允许按分支限制读取权限,因此所有属于 @developers 组的用户都有权限读取该仓库,这意味着他们可以读取彼此的开发分支,但只能对自己的沙箱分支进行写操作(推送)。

写操作类型

到目前为止,我们在谈论权限时仅限于 RWRW+ 权限。前者允许用户创建新的分支和标签,并对分支进行快速前进推送,而后者允许用户进行非快速前进推送和重写标签,并且删除分支和标签。

提示

这是最常见的情况,且该语法适用于绝大多数访问控制需求。对现有标签的推送,即使新的提交是当前标签指向的提交的后代,仍然被视为非快速前进推送。这是因为,与分支不同,标签并不意味着可以被移动;一旦写入,标签应该是固定的,永远不会更改。

然而,在某些情况下,你可能需要显式指定创建分支的权限,将其与推送新提交的权限区分开。类似地,你可能希望显式指定删除分支或标签的权限,将其与推送非快速前进提交的权限区分开;或者,你可能希望同时指定两者。

为了实现这一点,Gitolite 在权限字段中引入了扩展语法,让你可以明确指定创建和/或删除权限。在适用于仓库的任何规则中使用这些扩展权限,会限制正常 RWRW+ 权限在创建或删除引用方面的权限。

这种扩展语法包括以下新权限RWCRW+CRWDRW+DRWCDRW+CD。当一个规则指定了包含 C 的权限并添加到仓库时,该仓库的 RWRW+ 规则将不再允许创建分支或标签。类似地,当任何规则指定了包含 D 的权限时,该仓库的 RW+ 规则将不再允许删除引用。为了便于讨论,我们可以分别将其称为显式创建显式删除模式。

将这些规则总结成表格形式非常有用,以便在需要使用这些特殊权限模式时快速参考。下表展示了在三种模式下,执行给定操作所需的最小权限“字符”(严格来说,还有第四种模式,即同时使用显式创建和显式删除模式,但这只是两者的组合):

默认模式 显式创建模式 显式删除模式
创建新分支 RW RWC RW
创建新标签 RW RWC RW
快进推送现有分支 RW RW RW
非快进推送分支 RW+ RW+ RW+
覆盖现有标签 RW+ RW+ RW+
删除分支 RW+ RW+ RWD
删除标签 RW+ RW+ RWD

允许 Gitweb 和 Git-daemon 访问

Gitweb(以及在较小程度上,git-daemon)是流行的工具,提供对 Git 仓库的只读访问。Git-daemon 提供完全无认证的 Git 仓库访问,适用于开源或类似项目。Gitweb 在网页浏览器中显示仓库、分支、提交历史以及更多细节。Gitweb 本身不进行任何认证,而是依赖于网页服务器来认证用户。

Gitweb 和 git-daemon 通过不同的方式来确定哪些仓库可以对客户端开放。对于 gitweb,允许的仓库列表以特定格式(最简单的形式是每行一个仓库名称)放在一个特定文件中,该文件位于可配置的位置(具体细节请参见 gitweb 文档)。另一方面,git-daemon 会在每个仓库内部查找名为 git-daemon-export-ok 的文件,以确定该仓库是否可以对客户端开放。当然,两个工具在使用之前都需要进行一次性的配置设置。特别是,gitweb 可能甚至不会作为 Gitolite 托管用户运行,而一次性的设置可能涉及允许其读取 Gitolite 托管用户拥有的文件。

虽然 Gitolite 无法帮助配置这些工具的首次设置,但它肯定能帮助管理访问列表。它通过指定两个特殊的 Gitolite 用户名:gitwebdaemon 来实现。这些用户没有公钥,因此他们不像 Adam、Alice 或 Bob 等示例中的实际用户。然而,他们决定了哪些仓库可以通过 gitweb 和 git-daemon 访问。

这个概念非常简单。如果你希望一个仓库可以通过 gitweb 读取,你就给 gitweb 用户 权限。类似地,如果你希望仓库可以通过 git-daemon 访问,你就给 daemon 用户 权限。下面是一个同时进行两者操作的例子:

repo foo
 R  =  gitweb  daemon

当然,你可以使用已定义的任何仓库组名称,甚至是特殊的组名@all,而不是指定每个仓库。

当 gitolite-admin 仓库被推送时,Gitolite 会检查每个仓库,看看这些特殊用户是否被授予了读取权限。对于每个这样的仓库,Gitolite 会在之前提到的项目列表文件中添加该仓库的名称,如果gitweb用户可以读取它,并且如果daemon用户可以读取它,则会创建名为git-daemon-export-ok的文件。此外,如果你之前允许了访问权限,然后又决定删除它,Gitolite 将删除项目列表文件中的条目,和/或删除git-daemon-export-ok文件(视情况而定)。

定位项目列表文件

我们之前提到过,Gitweb 会查阅一个包含允许的仓库列表的文件,而 Gitolite 会创建这个文件。当然,Gitweb 和 Gitolite 必须一致地知道这个文件的位置。默认情况下,Gitolite 将其放在$HOME/projects.list,但如果需要,可以将其更改为任何其他位置;只需在$HOME/.gitolite.rc文件中添加一行,如下所示,当然要替换为你想要的路径。

GITWEB_PROJECTS_LIST => "/path/to/projects.list",

注意

上述语句的语法包括末尾的逗号。这一行必须放在标记为rc variables used by various features的部分中。

Unix 权限和 umask

Gitweb 由你的 Web 服务器(如 Apache)调用,而 Web 服务器几乎肯定是以与你的 Gitolite 托管用户不同的用户 ID 运行的。根据你的操作系统和 Web 服务器的不同,可能是名为apachewww-data或其他名称的用户。

这意味着你需要确保该用户能够读取它所需要的文件(主要是项目列表文件,以及$HOME/repositories下的所有文件)。通常有两种方法来做到这一点。最简单的方法是做如下操作:

  1. 确定你的 Gitolite 托管用户所属的主要组(通常与用户名相同)。你可以通过以 Gitolite 托管用户身份登录服务器后运行id命令来查找。在大多数系统中,它与用户 ID 相同,因此为了方便讨论,我们假设它是git

  2. 确定 Web 服务器运行时使用的 Unix 用户 ID。为了讨论方便,我们假设它是apache

  3. 使用usermod命令将apache用户添加到git组中。(你需要以 root 身份在服务器上运行此命令。)具体的语法可能根据你的操作系统和版本有所不同。例如,在 Red Hat 系统上的命令是usermod -G git apache

  4. UMASK值从默认的0077更改为0027,在$HOME/.gitolite.rc文件中进行修改。

  5. 手动修复现有文件。这是一次性操作,所需操作是因为umask只会影响新创建文件的权限,而不会影响已有文件的权限。以 Gitolite 托管用户身份登录,并运行命令chmod -R g+rX $HOME/projects.list $HOME/repositories

处理此问题的第二种方法是以 Gitolite 托管用户身份运行 gitweb。大多数 Web 服务器提供机制,允许在与 Web 服务器运行的用户 ID 不同的用户 ID 下运行特定程序,例如 Apache Web 服务器中的suexec功能。配置这些功能超出了本书的范围;有关更多信息,请查看你使用的 Web 服务器的文档。

指定 Git 配置值和 Gitolite 选项

如果你熟悉 Git,你可能知道git config命令,它允许你设置仓库选项。对于服务器端(裸)仓库,可能有用的配置变量示例包括core.logAllRefUpdatesreceive.fsckObjects,以及各种与性能相关的配置变量。(有关详细信息,请参阅git-config的手册页)

Gitolite 旨在允许几乎所有管理操作都远程进行,因此期望管理员登录到服务器并运行git config命令是不合理的。因此,Gitolite 允许管理员在配置文件中指定配置设置,如下所示:

repo foo
 RW+                            =  adam
 config    receive.fsckObjects  =  true

注意

然而,在使用此功能之前,你需要在rc文件($HOME/.gitolite.rc)中启用你希望使用的配置键。请在该文件中查找包含GIT_CONFIG_KEYS一词的行,并编辑其值,默认值为空,将其修改为包含你计划使用的配置键的以空格分隔的列表。

一般来说,语法是config sectionname.keyname = value。在服务器上,这会作为git config sectionname.keyname value执行。Gitolite 支持git config命令的其他任何形式,尤其是带有多个值的键。

删除 git-config 键

不幸的是,一旦 Gitolite 创建了一个 git config键,仅仅从配置文件中删除该行,在下次推送时并不会从仓库配置文件中删除该键。这是因为你可以选择直接在服务器上添加键(或者你的仓库在迁移到 Gitolite 管理时可能继承了一些有用的设置)。Gitolite 没有简单的方法来区分你手动创建的config键和在配置文件中删除的键;也就是说,虽然可以做到,但这很复杂且容易出错。

因此,Gitolite 需要以下语法,以便明确从服务器上的仓库配置文件中删除一个config键:

 config  sectionname.keyname  =  ""

这是使用 Gitolite 删除config键的唯一方法。

替换仓库名称

有时候你需要在多个仓库中使用相同的配置设置,但只需要改变仓库名称。显而易见的方法似乎是这样做:

repo foo
 config hooks.mailinglist = foo-commits@example.tld
 config hooks.emailprefix = "[foo] "
repo bar
 config hooks.mailinglist = bar-commits@example.tld
 config hooks.emailprefix = "[bar] "

但,当然,这完全无法扩展!

Gitolite 允许你做以下操作:

repo foo bar
 config hooks.mailinglist = %GL_REPO-commits@example.tld
 config hooks.emailprefix = "[%GL_REPO] "

Gitolite 在实际应用配置行时,将令牌%GL_REPO扩展为每个仓库的名称。

提示

别忘了,在 repo 行中,你可以有一个或多个仓库组,或者仓库和组的组合。如果需要,你还可以使用 @all

重写配置值

有时,你可能希望将一个配置值应用到所有仓库,除了其中一个或两个。例如,考虑前面提到的 hooks.mailinglist config 变量,假设对于大多数仓库,该设置是正确的,但某个特定仓库需要一个完全不同的邮件列表。

Gitolite 允许你按以下方式操作。首先添加适用于所有仓库的一般设置。然后,针对特定仓库添加特定设置。Gitolite 会确保对于任何仓库,最后看到的配置设置会被使用(因此,这些语句的顺序非常重要):

repo @all
 config hooks.mailinglist = %GL_REPO-commits@example.tld
 config hooks.emailprefix = "[%GL_REPO] "
repo foo
 config hooks.mailinglist = foo-list@users.example.tld

如果你希望提到的特殊仓库甚至没有相应的配置条目在其配置文件中,你甚至可以对最后一行使用空值。

Gitolite 选项

类似于 Git 的配置键和值,Gitolite 也有自己的一套内部选项,这些选项用于以某种方式修改其默认行为或提供某些功能可能需要的额外信息。例如,如果你使用 Gitolite 的镜像功能(在第十一章,镜像中讨论),你需要为每个仓库指定哪个服务器是主服务器,哪些是从服务器。这些服务器名称是通过 Gitolite 选项指定的,如下所示:

repo foo
 option mirror.master    =   mars 
 option mirror.slaves    =   phobos 

当镜像代码运行时,它会查询 Gitolite 以获取这些选项的值,从而完成其任务。

Gitolite 选项的行为与之前展示的 config 项目类似,后来的条目会覆盖之前的值。

将 deny 规则应用于读访问

在上一章中,当我们讨论 deny 规则时,我们仅在写操作的上下文中展示了这些规则,用于控制某个分支或标签是否可以被推送。

默认情况下,在检查读访问时不会检查 deny 规则。所以像以下这样的内容:

@junior-devs = alice bob carol
repo foo
 -             =  bob
 RW+           =  @junior-devs

即使 deny 规则首先出现,它也不会阻止 Bob 至少读取仓库(也就是使用 git clonegit fetch)。

然而,在这种情况下,也可以使 Gitolite 对 deny 规则生效。这是通过指定一个名为 deny-rules 的 Gitolite 选项来实现的,如下所示:

repo foo
 -                   =  bob
 RW+                 =  @junior-devs
 option deny-rules   =   1

现在,Bob 甚至无法克隆该仓库。

注意

同样重要的是要理解,对于读访问,指定实际的分支作为 deny 规则,如下所示:

 -          master    =  bob
 RW+                  =  @junior-devs

与规则中没有分支相同。这是因为 Git(因此 Gitolite)在进行读访问控制时不区分分支,所以任何指定的 refex 都会被忽略。

deny-rules 选项将防止写访问,方式与防止读访问相同。允许用户写入他们无法读取的内容是没有意义的!

理解 VREFs

前一章向我们介绍了refex的概念,它是一个正则表达式,用于匹配正在推送的 ref。这样我们就可以根据推送的 ref(分支或标签)的名称来决定是否允许或拒绝推送。

然而,我们可能有其他标准来决定是否允许或拒绝推送。最常见的例子是是否更改了特定的文件,如果有,则拒绝推送。

VREF是一种特殊类型的 refex;其中“V”代表“虚拟”。这是一种不会匹配 Git 已知的 ref(即分支名称或标签名称)的 refex。相反,它会尝试与推送的提交或提交序列的某些其他特征进行匹配。

这是一个示例。假设你正在运行一个项目,其中构建系统,尤其是Makefile,是一个非常关键的组件,并且已经经过精心调试,可以很好地与产品构建和销售的所有环境兼容。因此,你确实不希望除了最有经验的人以外的任何人修改这个文件(以及可能与之相关的其他文件)。因此,你希望防止你的初级开发人员推送对其的更改。你可以这样做:

repo foo
 RW+                      =  @developers
 -  VREF/NAME/Makefile    =  @junior-developers

这基本上就是你需要做的。首先,检查推送的 ref(在这个例子中,我们通过允许所有开发人员——包括初级开发人员——推送任何分支来简化了这一过程)。一旦这个操作成功,VREFs 会按照规则列表中出现的顺序进行检查。

一个 VREF 规则有一个简单的结构,由单词VREF组成,后跟 VREF 的名称(在这个例子中是NAME,它是一个检查已更改文件和目录名称的 VREF),然后是某种类型的参数。更改的文件名列表会与该参数进行匹配,如果找到匹配项,就像你尝试推送一个分支并且 refex 匹配一样,这意味着规则行中的权限会决定接下来发生什么。在这个例子中,这将导致推送被拒绝。

这为你简单地预览了 VREF 特性。第十章,理解 VREFs,将详细讲解 Gitolite 这个强大特性的更多细节。

总结

本章我们讨论了 Gitolite 的一些高级特性,如个人分支、Git 配置变量、Gitolite 选项等等。下一章将重点介绍 Gitolite 的另一个非常强大且受欢迎的特性——允许用户在不需要向 conf 文件中添加任何内容的情况下创建自己的仓库。

第八章:允许用户创建仓库

直到现在,我们看到的一切表明只有管理员能够创建新仓库。他可以通过授予一些受信任的用户读取和写入gitolite-admin仓库的权限来分担这项工作,但这也只是暂时的。

在某些环境中,用户可能需要更多的灵活性。你可能有一些用户,他们不需要是管理员,即他们不希望或不需要管理所有仓库,但他们确实希望能够创建自己的仓库并控制对这些仓库的访问。事实上,我们希望管理员的角色仅在初期设置时出现,之后就不需要再对 Gitolite 的conf文件进行更改。

如果有多个用户需要这样做,那么思考如何在保持管理员通常方式创建的仓库的必要安全性的同时,允许这种操作似乎是个好主意。

在本章中,我们将解决引言中描述的问题。我们将从一些帮助我们解决部分问题的功能开始讨论,然后逐一添加缺失的部分,最终构建出完整的解决方案。

将仓库放入子目录

解决方案的第一部分是,如同第一章中所暗示的,Gitolite 允许你将仓库分组到子目录中,就像在文件系统中管理文件一样。例如,你可以将所有正在管理的开源项目放到名为foss的子目录下,像这样:

repo foss/apache
 ...access rules for the apache repo...
repo foss/linux
 ...access rules for the linux repo...
...etc...

我们可以利用这一点来解决当前的问题。假设我们有用户 Alice 和 Bob,并且我们希望允许他们创建和管理仓库。我们可以想出一种方法,使得 Alice 的仓库位于一个名为dev/alice的子目录下,Bob 的仓库同样会位于dev/bob中。

仓库通配符

仓库通配符是描述一系列可能的仓库名称的正则表达式。例如,repo dev/alice/[a-z].*这一行表示所有仓库名称以dev/alice/开头,后跟一个字母字符,后面可选跟随其他内容。仓库dev/alice/foo符合条件,但dev/alice/123则不符合,dev/alice也不符合。

提示

由于需要正确表示诸如gtk+c++这样的仓库名称,如果+字符是仓库名称中唯一的正则表达式元字符,它将被视为普通的仓库,而不是仓库通配符。要指定foo.+,你应该改为使用foo..*。你也可以使用[f]oo.+—方括号的存在告诉 Gitolite 它是一个正则表达式。

Gitolite 允许repo行使用通配符而不是单个仓库名称。这为我们的解决方案迈出了下一步;我们现在可以写:

repo dev/alice/[a-z].*
    RW+       =  alice
    RW        =  bob
    R         =  @all
repo dev/bob/[a-z].*
    RW+       =  bob
    RW        =  alice
    R         =  @all

这表示 Bob 可以将分支推送(但不能回滚或删除)到 Alice 所有的仓库,反之亦然,而且系统中其他经过认证的用户可以克隆这两组仓库。

可惜的是,通配符仓库规范本身并不会真正创建任何仓库,因为这个模式本身可以匹配数十亿个可能的仓库名称!

创建通配符仓库

现在是时候介绍 Gitolite 提供的额外语法的第一部分,以帮助解决我们在本章开始时提到的问题。正如我们所指出的,通配符规范行并不会真正创建 Alice 或 Bob 需要的仓库。

为了实现这个目标,我们在访问规则规范中添加了一行新的内容:

repo dev/alice/[a-z].*
 C    =  alice
 ...other rules stay the same...
repo dev/bob/[a-z].*
 C    =  bob
 ...other rules stay the same...

提示

这与我们在《第七章》高级访问控制与配置部分看到的 创建分支 权限有所不同。这里描述的是单个字母 C,而另一个只能作为 RWRW+ 的修饰符存在。

这个访问规则表示,通过克隆或推送一个匹配名称的仓库,Alice 可以导致仓库在服务器上被创建! 换句话说,Alice 可以运行以下命令:

$ git clone  git@host:dev/alice/my-new-repo
Cloning into 'my-new-repo'...

Initialized empty Git repository in /home/git/repositories/dev/alice/my-new-repo.git/

warning: You appear to have cloned an empty repository.

这就像管理员添加了一组新的规则,内容如下:

repo dev/alice/my-new-repo
 RW+       =  alice
 RW        =  bob
 R         =  @all

只是管理员不需要这么做!

别忘了在前面的输出中看到Initialized empty...这一行;这是服务器的反馈,告诉你这个克隆操作创建了一个全新的仓库!此外,如果 Alice 执行 info 命令,她可能会看到如下内容:

$ ssh git@host info 

hello alice, this is git@host running gitolite3 v3.5.3.1-7-g31d11b9 on git 1.8.3.1

 C  dev/CREATOR/..*
 R W    dev/u1/my-new-repo
 R W    testing

它显示的是刚刚创建的仓库。

提示

一个缺点是,一个简单的拼写错误就可能导致创建无用的仓库。如果你希望防止这种情况发生,可以编辑 $HOME/.gitolite.rc 并取消注释 create 命令以及 no-auto-create 选项。然后你的用户可以运行更明确的 create 命令,例如:ssh git@host create dev/alice/my-new-repo

赋予其他用户访问权限

到目前为止,我们只是硬编码了权限—Alice 和 Bob 可以互相访问对方的仓库,其他人可以读取这两个仓库。这显然不够灵活,Alice 可能希望某些仓库可以被 David 写入,某些仓库不允许 @all 读取,等等。

从表面上看,这似乎是一个难题,因为这意味着 Alice 可能需要对规则本身进行更改或添加,因此直接或间接地触及到 Gitolite 的 conf 文件。

Gitolite 解决这些问题的方式是允许管理员定义角色,然后让用户指定她希望将哪些用户分配到每个角色。以下是使用 Gitolite 默认定义的角色名称的示例:

repo dev/alice/[a-z].*
 C          =  alice
 RW+        =  alice
 RW         =  WRITERS
 R          =  READERS

READERSWRITERS是 Gitolite 中预定义的角色名称。请注意,角色名称本身并没有特殊的含义,关于角色的访问权限完全由管理员决定。

目前,然而,这还不完整。规则并没有明确说明 Bob 是一个写作者(因此在 Alice 的仓库上拥有RW权限),或者其他人(@all)可以读取它。

这时perms命令就派上用场了。以下是 Alice 如何使用它将 Bob 添加到WRITERS角色的方式:

ssh  git@host perms dev/alice/my-new-repo + WRITERS bob

类似地,要将@all添加到READERS角色,她将运行以下命令:

ssh  git@host perms dev/alice/my-new-repo + READERS @all

如果她希望检查当前仓库的角色分配情况,可以运行。

ssh  git@host perms -l dev/alice/my-new-repo

它将恭敬地打印:

READERS @all
WRITERS bob

你应该立刻看到的一个优势是,现在角色分配是按仓库来划分的。也就是说,Alice 可以为她拥有的其他仓库设置完全不同的角色分配。事实上,如果她没有在某个仓库上运行perms命令,其他人将无法访问该仓库——它将变成她的私人仓库。或者,如果她愿意,也可以为每个角色添加多个用户。perms命令一次只能添加一个用户到一个角色。因此,Alice 可能需要多次运行这个命令。如果她添加了某个用户,现在又想移除他,也是可以的。与所有 Gitolite 命令一样,perms支持-h参数来提供用法说明。

泛化规则集

现在我们将查看每个用户当前的规则集,并讨论如何将其泛化到任意数量的用户。此时,如果你还记得上一节的内容,规则看起来如下:

repo dev/alice/[a-z].*
 C         =  alice
 RW+       =  alice
 RW        =  WRITERS
 R         =  READERS

你可能会注意到,除了前面三行中的alice被替换成 Bob 外,这实际上正是你为 Bob 的仓库所需要的。显然,要求为每个可能需要此功能的用户重复这一点是没有意义的!

允许用户创建并(在某种程度上)管理自己的仓库的解决方案的最后部分是CREATOR关键字。这里是经典的示例:

repo dev/CREATOR/[a-z].*
 C         =  alice bob carol dave
 RW+       =  CREATOR
 RW        =  WRITERS
 R         =  READERS

注意我们所做的更改。首先,C权限行现在列出了所有被允许创建和管理自己仓库的用户,正如我们之前所描述的那样。在这个规则集中,只有这四个用户可以执行此操作。或者,你也可以用某个已定义的组名替换这四个用户名,甚至使用@all来允许所有经过身份验证的用户使用创建私有仓库并选择性地向其他人开放的功能。

接下来,仓库名称模式包含CREATOR而不是aliceRW+行也是如此。对于尚不存在的仓库,这些实际上被视为试图创建仓库的用户的名字。对于已存在的仓库,它被视为创建该仓库的用户的名字,并且该信息会被记录和追踪。

提示

当仓库不存在时,Gitolite 关心的唯一权限是允许创建仓库的C权限。符合规则右侧的任何人都可以创建匹配模式的仓库。要注意的一个错误是将C = CREATOR放入其中,而不是实际用户或用户组的列表。因为如上所述,这会被视为尝试创建仓库的用户的名称,这允许任何经过身份验证的用户创建这样的仓库。如果这是你想要的,最好明确说明,实际上使用@all而不是CREATOR;后者只是一个副作用,不是受支持的行为。

向你的用户解释野生仓库

当然,你的用户并不需要所有这些解释!事实上,此功能的一个目标是使 Gitolite 用户(而不是 Gitolite 管理员)不必负担学习RWRW+deny规则等细微差别。

到目前为止,我们一直在使用的示例是经典示例:它包含一个所有者(只有该所有者被允许回滚或删除分支的用户),一组作者(可以推送/创建但不能回滚/删除),以及一组读者根本不能推送。

唯一剩下的事情就是向用户解释她被允许创建哪些仓库(大多数用户对正则表达式不是很熟悉,因此最好保持模式足够简单,可以用英语解释),以及她可以向每个用户列表添加或删除人员。

列表名称(在我们的示例中是READERSWRITERS)应当提供,并解释为代表每个列表中的用户可以做什么。

最后,展示三个示例perms命令用于维护这些用户列表:

  • ssh git@host perms dev/alice/repo + WRITERS dave 以添加一个用户

  • ssh git@host perms dev/alice/repo - WRITERS dave 以移除一个用户(请注意减号而不是加号)

  • ssh git@host perms -l dev/alice/repo 列出当前用户列表

只使用野生仓库进行管理

如果你考虑我们在本章中一直在工作的示例,它不允许用户信任任何其他人拥有RW+权限;如果需要回滚或删除分支,必须是所有者自己执行。

我们可以通过将RW+权限行更改为以下内容来纠正这一点:

 RW+    =  CREATOR TRUSTED

因此,定义一个名为TRUSTED的新角色(或者说用户列表)。当然,为了使其生效,作为管理员的你需要登录服务器,并编辑$HOME/.gitolite.rc文件,在ROLES哈希下的角色列表中添加这个新角色。然后你可以告诉你的用户,有一个名为TRUSTED的第三个用户列表,她可以用来指定她想要允许回滚或删除分支或标签的用户。

既然我们已经开始走这条路,那么我们可以继续走得更远,直到最终我们得到的东西基本上就是 Gitolite 的一次性设置,管理员在设置完后几乎不需要再进行任何维护。这在大多数用户相对自主的网站上非常有用。

下面是一个完整的规则集示例。为了方便复制和使用,我们在规则集的描述中添加了注释,这样你也可以复制它们:

# completely private repo; no sharing even possible
repo private/CREATOR/..*
 C          =  @all
 RW+        =  CREATOR

# public template; anyone can read but writes only by owner
repo public/CREATOR/..*
 C          =  @all
 RW+        =  CREATOR
 R          =  @all

# a controlled repo with 3 roles allowing RW+, RW, and R
repo controlled/CREATOR/..*
 C         =  @all
 RW+       =  CREATOR
 RW+       =  TRUSTED
 RW        =  WRITERS
 R         =  READERS

# a "corporate" type template with managers, testers, etc.
# Junior devs cant write 'master', can't rewind.  Testers
# (and *only* testers) can push versioned tags.  Managers
# can read any repo.
repo corporate/CREATOR/..*
 C               =  @all
 RW+             =  CREATOR
 RW refs/tags/v[0-9]  =  TESTERS
 -  refs/tags/v[0-9]  =  @all
 RW+             =  SENIOR_DEVS
 -  master       =  JUNIOR_DEVS
 RW              =  JUNIOR_DEVS
 R               =  MANAGERS

删除不再需要的仓库

使用上一节中的示例,管理员的工作负担大大减轻(尽管这是以失去一些控制权为代价的)。然而,仍然有一个功能是你的用户最终会需要的:删除那些已经完成任务的仓库。

为了允许用户删除他们创建的仓库(无需提及用户不能删除其他任何内容!),管理员需要通过取消注释 $HOME/.gitolite.rc 文件中相应行的命令来启用 D 命令。然后用户可以运行 D 命令删除仓库。

默认情况下,仓库会被锁定,防止意外删除,因此每次删除实际上分为两个步骤——unlock 子命令和 rm 子命令:

ssh  git@host D unlock dev/alice/my-new-repo
ssh  git@host D rm dev/alice/my-new-repo

总结

这一章可能是本书中最重要的一章,因为它讲解了 Gitolite 的一个非常流行的功能。虽然在需要严格控制和可审计性的网站上不适用,但在大多数其他站点中非常有用,不仅能为管理员节省大量时间,而且用户也不需要再等待管理员处理他们急需的事项。

在下一章中,我们将讨论 核心 Gitolite 和 非核心 Gitolite 的区别,查看 Gitolite 附带的一些非核心程序,并讨论如何通过添加我们自己的非核心代码来自定义 Gitolite。

第九章:定制 Gitolite

到目前为止,很明显 Gitolite 是一个管理服务器上 Git 仓库的强大工具。然而,最强大的工具允许管理员添加适合其站点独特需求的功能,因此不能期望将其添加到产品本身。例如,考虑 Git 本身,其 hooks 机制(详见 man githooks)包含了几个预定义的 hooks,用户可以在其仓库上安装这些 hooks 来定制 Git 在提交、变基、推送等生命周期的各个点上的行为。实际上,Gitolite 之所以能够执行分支级别的访问控制(而不仅仅是仓库级别的访问控制),完全是通过使用 Git 的 update hook 实现的。

核心与非核心 Gitolite

Gitolite 比仅仅允许您根据特定位置的需求定制它要进一步。实际上,Gitolite 已经 配备了几个可选功能的定制。其中一些定制已启用,默认情况下启用,而其他一些则被禁用,尽管只需快速编辑 $HOME/.gitolite.rc 即可启用它们。

因此,Gitolite 将 核心非核心 Gitolite 代码区分开来。如果你查看过 Gitolite 源代码树(如果你克隆了 Gitolite 源代码,则位于 src 目录下),你会注意到顶层有几个目录和一些文件。在这些目录中,Gitolite 认为以下目录包含非核心代码:commandssyntactic-sugartriggerslib/Gitolite/TriggersVREF。其他所有内容都被视为 核心

这种区分还有助于决定是否添加新功能。如果该功能需要更改核心 Gitolite,将需要更加仔细地考虑和思考,即使如此,只有在确实需要该变更的多个用户时才会发生。然而,实际上,Gitolite 的定制功能如此强大,以至于几乎不需要对核心 Gitolite 进行任何更改。

非核心代码的类型和示例

Gitolite 允许为您的站点开发四种类型的定制。这可能听起来有些吓人,但实际上大多数人只使用其中的两种。我们现在将逐一描述它们。

Commands

Gitolite 允许远程用户在服务器上运行特定的命令,格式为 ssh git@host command-name。命令在远程使用前需要启用;请参阅 第八章 中关于对 rc 文件进行更改的部分,允许用户创建仓库。有一种看法是将其视为为用户提供一个非常受限制的 shell,只允许执行特定的命令。

我们已经在第八章中遇到过一些 Gitolite 命令,比如 permsD 命令,以及之前章节中的 infohelp。Gitolite 一共提供了二十多个命令,虽然默认情况下,只有五个命令可以用于远程调用。更多命令列出了在 $HOME/.gitolite.rc 中,但由于被注释掉,因此默认是禁用的。只需删除该行的注释标记即可启用它们。

然而,Gitolite 自带的许多命令并不适用于远程使用,因此它们不会出现在 $HOME/.gitolite.rc 中(即便是注释掉的形式也没有)。这些命令是作为服务器端脚本或其他非核心程序的辅助工具使用的。最方便的这些命令之一是 access 命令,其帮助信息如下:

Usage:  gitolite access [-q] <repo> <user> <perm> <ref>

Print access rights for arguments given.  The string printed has the word
DENIED in it if access was denied.  With '-q', returns only an exit code
(shell truth, not perl truth -- 0 is success).

 - repo: mandatory
 - user: mandatory
 - perm: defauts to '+'.  Valid values: R, W, +, C, D, M
 - ref:  defauts to 'any'.  See notes below

Notes:
 - ref: Any fully qualified ref ('refs/heads/master', not 'master') is fine.
 The 'any' ref is special -- it ignores deny rules (see docs for what this
 means and exceptions).

Batch mode: see src/triggers/post-compile/update-git-daemon-access-list for a
good example that shows how to test several repos in one invocation.  This is
orders of magnitude faster than running the command multiple times; you'll
notice if you have more than a hundred or so repos.

正如你所看到的,这在编写自己的代码时非常有用,尤其是在你需要检查用户对一个或多个仓库的访问权限时。

在服务器上运行 gitolite help 将列出所有可用的命令;而运行 ssh git@host help 将列出所有 远程 可用的命令。此外,Gitolite 还自带了几个命令,它们是 Gitolite 内部实现的,实际上是 "核心" 的一部分。运行 gitolite -h 可以获取这些命令的列表及简要描述。

所有 Gitolite 命令在使用 -h 单一参数调用时都会返回用法信息。如果你编写自己的命令,遵循这一约定是个好主意。

以下是 Gitolite 中一些现有命令的列表,并附带简要描述:

  • access:此命令打印或测试用户在仓库上的访问权限。当你编写自己的命令时,这非常有用。请参阅下面 fork 命令的描述,了解一个例子。

  • D:此命令允许用户删除他们创建的仓库(参见第八章,允许用户创建仓库)。

  • desc:此命令显示或设置用户创建的仓库的描述。

  • fork:此命令会在服务器上创建一个仓库的分支。它会复制一个仓库并创建一个新的仓库,内容相同。它会检查确保读取者对源仓库有读取权限,并且允许创建目标仓库(参见第八章,允许用户创建仓库)。此命令使用 -l 选项调用 git clone,因此运行非常快速。(如果没有此命令,用户的替代做法是先克隆源仓库,再用该仓库来创建并推送目标仓库。对于大型仓库,这可能需要花费较长时间。)

  • git-config:此命令打印(或测试是否存在)仓库中的 "config" 配置项。

  • help:此命令会打印出所有可用命令的列表。

  • info:此命令打印你的用户名、git/gitolite 的版本号,以及你可以访问的所有仓库。

  • perms:列出或设置用户创建的仓库的权限。

在后续部分中,我们将看到如何创建你自己的命令。

语法糖

语法糖脚本是一种定制方式,大多数人很少、甚至从未需要编写或遇到它们。它们在管理员希望在 Gitolite 的访问控制语言中添加一些额外的、纯粹与语法相关的功能时非常有用。在这种情况下,可以编写一个语法糖助手脚本,将管理员编写的内容转换为 Gitolite 可解析的格式。

Gitolite 附带了一些语法糖助手脚本。例如,其中一个是允许在 Gitolite 的 conf 文件中使用 C 风格的续行符,因为 Gitolite 通常不允许这样做。另一个是提供简单的宏功能。

触发器

可以说,Gitolite 最强大的定制功能是触发器功能。Gitolite 触发器相当于 Git 的钩子。正如 Git 提供了在各个点运行的钩子(例如,pre-commitpre-receivepost-receive 等),类似地,Gitolite 的触发器也会在 Gitolite 管理的推送或获取的生命周期中的特定点运行。

然而,Git 钩子和 Gitolite 触发程序之间是有区别的。Git 定义了几个钩子,并要求你的钩子代码必须精确命名为其中之一(例如,post-receiveupdate)。而 Gitolite 则允许你定义一组触发程序,当触发点到达时,它会按顺序调用这些程序。只有触发点的名称是固定的。当然,这也意味着,你的程序可以使用任何你喜欢的名称。

从定制角度来看,重要的触发点是 INPUTPOST_CREATEPOST_COMPILE,尽管还支持多个其他触发点。

INPUT 触发器的目的是以某种方式操作输入参数或环境。由于子程序无法影响父程序的环境,INPUT 触发器需要用 Perl 编写,并作为模块安装在 lib/Gitolite/Triggers 中(与任何语言编写的普通程序不同,后者是安装在 triggers 目录中的)。使用 INPUT 触发器的功能示例包括为某些用户提供完全的 shell 访问权限,以及允许仓库具有别名。

提示

本章将提及许多本书范围之外的非核心特性。请参阅 Gitolite 的在线文档获取详细信息。

POST_CREATE 触发点用于在新仓库创建后执行任何需要完成的整理或报告任务。例如,Gitolite 使用这个触发点来运行代码,在用户创建野性仓库时更新 gitweb 和 git-daemon 的访问列表。

POST_COMPILE触发点帮助您在推送gitolite-admin存储库时执行附加任务。此触发点与 Gitolite 一起提供的程序数量最多相关联。其中大多数与 ssh 密钥有关,或者更新 gitweb 和 git-daemon 的访问列表。

虚拟引用

可用的最后一种非核心定制类型是 Gitolite 根据 Gitolite 称为虚拟引用的东西做出访问决策的能力。执行此操作的脚本称为VREFs;它们复杂且重要到足以使下一章完全专门介绍它们。

编写您自己的非核心代码

编写您自己的代码来添加特定于您站点的功能相当容易。例如,假设我们希望每次开发人员创建一个wild repository时都向管理员发送电子邮件。我们假设标准的 Unix 实用程序存在且可用。特别是,我们假设 Unix 邮件命令可用。此命令从标准输入获取消息,并从命令行参数获取主题和收件人数据,然后发送邮件,因此非常适合我们的目的。

由于这是需要在创建存储库时运行的操作,因此需要将其添加到POST_CREATE触发器列表中。根据 Gitolite 文档,当创建一个 wild repository 时,每个POST_CREATE触发器列表中的程序都将被调用,第二个参数是刚刚创建的存储库的名称,而第三个参数是创建它的用户的名称。(如果为空,则这不是一个 wild repository 的创建,而是通过管理员将存储库添加到 Gitolite conf文件并推送更改。)

因此,这段代码可以简化为以下形式:

#!/bin/bash
[ -n $3 ] && echo | mail -s "new repo $2 created" admin_group@example.com

现在我们已经编写了这段代码,我们需要将其放在 Gitolite 可以找到并在正确时间使用它的地方。

我们决定创建一个名为$HOME/local的新目录,用于保存所有我们的本地定制。在此目录中,我们添加一个名为triggers的子目录,并在其中放置此脚本,命名为new-repo-alert。(不要忘记对脚本执行chmod +x!)

现在,我们编辑 Gitolite 的rc文件($HOME/.gitolite.rc)。在这个文件中,我们找到一行注释掉的定义LOCAL_CODE变量的行,但恰好指向我们选择放置自定义的地方,所以我们只需取消注释。

然后我们在LOCAL_CODE变量之后立即添加以下代码行:

POST_CREATE => [
 'new-repo-alert',
],

您是否注意到在右括号后的尾随逗号?这就是您需要做的全部。从现在开始,每当用户创建一个新的“wild”存储库时,将执行new-repo-alert脚本。

第二个例子,我们将创建一个小命令。我们使用的例子将允许用户使用git count-objects命令检查存储库的大小。我们的命令将默认使用-v选项运行,因为这是最通用和有用的。

为此,创建一个名为$HOME/local/commands的目录,并在该目录下放置一个名为count-objects的脚本。确保脚本是可执行的(chmod +x)。该脚本的代码如下:

#!/bin/bash

repo=$1

gitolite access -q $repo $GL_USER W any || {
 echo Sorry $GL_USER, you are not authorized
 exit 1
}

cd $GL_REPO_BASE/$repo.git
git count-objects -v

这段代码的有趣之处不在于实际的count-objects命令。最通用的,也最符合你需求的命令是gitolite access命令,其使用信息我们在前面的章节中已经看到过。在这里,我们使用它来确保执行该命令的用户至少拥有目标仓库的写权限,然后再允许命令运行。

最后,将此命令添加到rc文件的ENABLE命令列表中,最好放在COMMANDS部分。

提示

请注意,count-objects是一个无害的命令,因此可能不需要保护。然而,如果你稍微扩展使用场景,允许用户启动git gc操作,甚至是git fsck,你就需要更加小心了。这些命令中的一些在被多次执行或被多个人同时执行时,处理起来可能不太好。确保你的命令做一些速率限制或序列化。

其他命令需要提供参数。如果你的脚本接受用户的参数,一定要在执行命令前对其进行清洗。一个写得不小心的命令可能会破坏 Gitolite 的所有访问控制!

从这两个例子中可以看出,向你的网站添加新功能最重要的方面是决定何时以及如何调用该功能——它应该是用户命令,还是一个在特定时刻触发的操作,或许是一个能影响整体命令结果的VREF,等等。在某些情况下,它甚至可能是一个组合,例如,一个命令和一个 VREF 一起工作。极端的例子是,Gitolite 的镜像功能完全作为非核心代码编写,它作为一个命令实现,并且一个 Perl 模块被添加到INPUTPRE_GITPOST_GIT触发器列表中。

摘要

在本章中,我们了解了如何定制 Gitolite 或向其添加特定于你站点的新功能。这是一个相当复杂的话题,但如果你动手实践,开始编写程序,你很快就会对这一概念感到得心应手,并且对这个功能的强大之处有非常好的体会。

下一章将重点讲解VREF,这是一个强大的功能,能够实现更细粒度的访问控制,以及基于除 Gitolite 通常使用的因素外的访问控制。

第十章:理解 VREFs

我们在第七章,高级访问控制与配置 中简要介绍了 VREF,包括一个小示例,展示了 Gitolite 如何根据推送中修改的文件来允许或拒绝推送。在本章中,我们将更详细地探讨 VREF,因为这是 Gitolite 最强大的功能之一。我们将从简单的用法开始,然后逐步深入更复杂的用法。

迁移更新钩子

一些站点可能已经在其现有的(Gitolite 之前的)仓库设置中使用了更新钩子。由于 Gitolite 将更新钩子保留给自己,这在切换过程中会带来一些问题。

如果您的站点有这样的更新钩子,VREF 机制可以帮助替代它们。替换更新钩子是 VREF 最简单的用法之一,但理解如何做到这一点也是理解 Gitolite VREF 机制全部功能的一个良好起点。

要将现有的更新钩子转换为 VREFs,首先在 $HOME/local 中创建一个名为 VREF 的目录(我们延续第九章,自定义 Gitolite 中的约定,即 rc 文件中的 LOCAL_CODE 变量指向此目录)。然后,将每个独特的更新钩子复制到这个新创建的目录中,并对每个更新钩子进行某种重命名。

举个例子,假设有一个仓库经常由使用 Windows 的新手用户操作,因此更新钩子被用来确保没有行结束符问题。你可以将其重命名为 check-crlf

现在,对于每个需要进行此检查的仓库(即每个在 Gitolite 之前的设置中使用了该特定更新钩子的仓库),添加如下规则:

-  VREF/check-crlf  =  @all

当 Gitolite 的更新钩子处理一个推送时,它会遇到这个 VREF 规则,并调用 check-crlf 程序。传递的前三个参数与 Git 自身传递给更新钩子的参数相同,如果程序以非零退出代码退出,Gitolite 将拒绝推送。为了实现这一切,无需更改 check-crlf 代码;它直接生效。

敏锐的读者可能已经注意到,他们可以使用以下规则来替代上面所示的规则:

-  VREF/check-crlf  =  @junior-developers

这有助于将检查限制为规则中指定的用户。换句话说,Gitolite 允许选择性地应用普通的更新钩子,这可能非常有用。

向 VREF 代码传递参数

假设我们在(Gitolite 之前的设置中)有一个更新钩子,用于防止某些用户对某些文件进行更改。一个方法是编写一个更新钩子,检查这些文件,并将其作为 VREF 使用,如前一节所示。然而,检查的文件列表或模式需要在 VREF 代码中以某种方式进行编码,或者需要找到其他方法来传递这些信息。

Gitolite 允许你将额外的参数传递给 VREF 代码。假设使用的 VREF 名称为 NAME,那么你可以像这样传递参数,而不仅仅是说:

-  VREF/NAME           =  @junior-developers

确保 NAME VREF 的代码知道我们正在讨论哪些文件,你可以这样说:

-  VREF/NAME/.*\.h$    =  @junior-developers

如果你稍后发现另一组用户需要以类似的方式进行限制,但限制的是不同的文件集,你会发现这种方法真的非常有用。假设我们有一组技术写作人员在编写文档;他们没有理由去触碰构成源代码的文件:

-  VREF/NAME/.*\.[ch]$    =  @tech-writers

当然,VREF 代码现在与其作为更新钩子时的形式不完全相同。除了前面提到的三个参数(这些参数与 update 钩子中列出的参数相同)之外,现在还有其他一些参数,而我们刚刚添加的文件模式就是其中之一(具体来说是第八个参数)。VREF 代码必须从传入的参数中提取该模式,并用它来决定是否允许或拒绝该推送。

使用权限字段

假设我们有几种不同类型的源代码文件,将它们全部列在技术写作人员的规则中不仅繁琐,而且容易出错,因为我们可能会遗漏一些。不过,我们确实知道技术写作人员只处理文档,所以我们宁愿限制他们只能操作 *.odt 文件。

直到现在,我们一直认为 NAME VREF 的行为是这样的:接收一个文件名模式,如果在推送中有任何文件更改匹配该模式,则退出并返回非零代码,以向 Gitolite 发出拒绝推送的信号。正如你所看到的,这种行为完全忽略了权限字段;也就是说,即使这个规则也会产生相同的效果:

RW+    VREF/NAME/.*\.[ch]$  =  @tech-writers

我们需要的是一种方法来考虑权限字段。我们的第一反应可能是开发某种方式将此字段传递给 VREF 代码,也许是通过一些新的语法,并让 VREF 代码在需要时反向检查。

然而,这使得 VREF 代码变得更加复杂,同时也没有利用 Gitolite 的规则处理逻辑。

Gitolite 会根据匹配每条规则的 引用(通常类似 refs/heads/masterrefs/tags/v1.0)来处理访问控制规则。所以,利用这一点的一种方法是,不让 VREF 代码真正做出决定,而只是 输出一些东西,让 Gitolite 捕获并通过其访问控制规则,就像处理正常的 引用 一样。

你也可以称之为 虚拟引用

提示

让我们简要回顾一下术语,VREF 是执行的代码,而虚拟引用是它可能返回给 Gitolite 的内容。

维护更新钩子功能

但是,我们不希望在作为虚拟引用使用时,影响标准(Git)更新钩子的行为,正如本章前面所描述的那样。这一点相当简单——Gitolite 仅当 VREF 输出的行以 VREF/ 开头时,才会将其视为虚拟引用,即使如此,只有当该 VREF 以零状态退出时才会如此。

默认情况下为成功

在此时,我们需要更改 NAME 虚拟引用。它不再做出决定,而是简单地列出所有已更改的文件,每个文件名前面都加上 VREF/NAME/

一旦完成了这些,接下来可能看起来只有以下规则就足够了:

RW+    VREF/NAME/.*\.odt$  =  @tech-writers

然而,这并不是全部。

虚拟引用与真实引用的处理方式略有不同。对于真实引用,如果没有访问规则匹配该引用(以及用户和实际的写入类型),默认情况下会拒绝推送。

然而,虚拟引用是作为附加规则设计的,它们增加了常规 Gitolite 访问规则无法提供的检查。因此,合理的做法是,如果没有虚拟引用匹配,就应当视为没有任何额外的检查应用于此次推送,因此默认情况下是允许推送的。

因此,我们需要再添加一条规则,将我们的最终规则集变为这样:

RW+    VREF/NAME/.*\.odt$  =  @tech-writers
-      VREF/NAME/          =  @tech-writers

大致来说,这样做的效果是,对于每个已更改的文件,通过在文件名前加上 VREF/NAME/ 来生成一个虚拟引用,并将该虚拟引用通过规则集进行检查。其余的很明显,例如,修改一个名为 foo.c 的文件会创建一个名为 VREF/NAME/foo.c 的虚拟引用,这将仅匹配第二条规则,从而导致此次推送被拒绝。而文件名以 .odt 结尾的文件会匹配第一条规则,不会导致拒绝。

示例虚拟引用及其用法

Gitolite 源代码树带有一些准备好使用的虚拟引用(VREF)。要使用它们,你只需添加类似于上一节末尾所看到的规则。我们将查看其中的几个,以便了解它们是如何使用的,然后从头开始设计一个,以便我们知道如何添加自己的规则。

提示

如果你查看 Gitolite 源代码树,你实际上不会找到名为 NAME 的虚拟引用。这是因为 NAME 是特殊的,其代码内建在 Gitolite 中。

对 Git 新手来说,有时可能会创建一个提交,修改的文件比实际需要修改的文件多得多。也许他们在其他文件中添加了调试语句,或者可能他们不小心将文件保存为不同的行结尾格式(Unix LF 与 Windows CRLF),等等。

如果你确定你的新开发者只会被分配相对简单的任务,并且在任何时候都不应该有任何特定的任务修改超过五个文件,你可以使用 COUNT 虚拟引用来防止他们推送更多文件,从而保护仓库不被大量修改,避免前一段中提到的那种情况。以下是实现这一目标的规则:

-  VREF/COUNT/5    =  @new-devs

COUNT 虚拟引用本质上计算当前推送中所更改的所有文件(并且这些文件不存在于任何其他分支或标签中)。

如果最后这一部分听起来有点复杂,想象一下当开发者仅仅从一个已有的分支创建一个新分支并推送时会发生什么。我们不希望COUNT VREF 仅仅因为被推送的分支的旧值为空,而将分支中的所有文件都视为已更改。这就是为什么COUNT代码会查看那些在其他任何引用中都没有出现的提交。

MAX_NEWBIN_SIZE VREF 的概念类似。这是为了解决有时开发者无意中提交了 JAR 文件或者构建步骤产生的可执行文件的问题。像这样的可执行文件通常比正常的源文件要大一些,因此,如果你有一个合理的大小限制,可以使用这个 VREF 来强制执行它。

编写你自己的 VREF

这是一个 VREF 非常有用的实际案例。我们将使用它来设计一个非常简单的 VREF,这是现有规则无法完成的。

要求很简单:对于任何一个仓库foo,如果一个名为l10n的仓库包含一个名为foo的目录,那么你就不能将任何名为*.po的文件推送到foo

提示

这段内容改编自一个更复杂的现实生活案例,但对于我们的目的来说,这样已经足够了。如你所料,这是一个多仓库系统,逐步朝着集中本地语言文件的方向发展,使得翻译人员只需要处理一个仓库。每个仓库的本地语言文件在准备好后会被迁移过来,从那时起,所有的本地化文件必须提交到为此目的创建的单一仓库。

由于这是一个非常特定的用例,我们可以编写一个不接受任何参数的简单 VREF。我们的规则可以是这样的:

repo @all
-  VREF/l10n-check  =  @all

如你所见,我们将这一规则应用于所有仓库。(如果你有多个仓库,且这些仓库永远不会符合此条件,那么 VREF 将在每次推送时为它们触发,这样稍显低效。如果是这种情况,可以将@all替换为一个仅包含需要此检查的仓库的组名)

提示

这些仓库中可能在其他地方有各自的规则——我们不需要在每个仓库部分都放入这个 VREF 规则。这是规则累积与委托的一个例子,如在第六章《访问控制入门》中讨论的那样。

下面是编写这个 VREF 的一种方法(理解此代码需要一些基本的 Shell 语法和 Git 概念的知识):

#!/bin/bash

# see Gitolite documentation for arguments and meanings
oldtree=$4
newtree=$5
refex=$7

# no *.po files changed?  No problem!
git diff --name-only $oldtree $newtree | grep '.*\.po$' >/dev/null || exit 0

cd $GL_REPO_BASE/l10n.git
# no directory with the same name as $GL_REPO in the l10n repo?  No problem!
git ls-tree master | grep "\s$GL_REPO$" >/dev/null || exit 0

echo $refex "sorry, PO files must be added to '$GL_REPO' subdirectory in 'l10n' repo"

如你所见,第一个检查使用 git diff 命令检查此次推送是否更改了任何po文件。如果没有,那么就没有什么需要检查的,我们直接退出,不做任何操作。第二个检查切换到l10n仓库,然后在该仓库中运行ls-tree命令,检查是否包含一个文件或目录,其名称与用户正在推送的仓库名称相同。如果没有,则可以直接退出,不会报错。

如果这两个检查成功,我们需要触发错误信号。一个方法是直接使用 exit 1;Gitolite 会捕获 VREF 代码的终止并拒绝推送。另一方面,我们可以打印 refex 本身(在这种情况下是 VREF/l10n,但养成使用参数的习惯是个好习惯)。它将匹配我们设置的访问规则,因为权限是 "-",所以推送将被拒绝。

但是,VREF 特性提供了更多功能。如果在 refex 后打印一个空格,然后是一些解释性信息,当推送被拒绝时,这条信息将会被打印出来:

$ git push
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 351 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: FATAL: W VREF/l10n t2 u1 DENIED by VREF/l10n 
remote: sorry, PO files must be added to 't2' subdirectory in 'l10n' repo 
remote: 
remote: error: hook declined to update refs/heads/master 
To u1:t2 
 ! [remote rejected] master -> master (hook declined) 
error: failed to push some refs to 'u1:t2' 

如果你写 VREF 来捕获少见的情况,那么你可能会发现通过一些简单的英语来扩展 Gitolite 相对简陋的错误报告功能非常有用,这样你的用户就不必太费劲思考了!

总结

本章我们探讨了 Gitolite 最强大的功能之一——通过编写 VREF 使用任意外部因素进行访问控制决策的能力。下一章将通过讨论镜像来结束我们对 Gitolite 的探索——这是一个大型多站点设置可能会非常有用的功能。

第十一章:镜像

虽然大多数安装都乐于在一个所有开发者都能访问的单一服务器上安装 Gitolite,但其中一些公司在世界各地有开发者,通常在每个城市提供本地镜像以便快速访问是非常有用的。Gitolite 的镜像功能以非常灵活的方式满足了这些需求。

镜像可以是你想要的简单或复杂。你可以让一台服务器成为所有仓库的主服务器,并由一个或多个从服务器保存其副本。你也可以让不同的服务器成为不同仓库的主服务器,并选择其中一些服务器作为从服务器,而不是所有服务器。你甚至可以选择不将某些仓库纳入镜像过程。

术语和基本概念

一个镜像仓库在一台服务器上有一个可写副本,通常称为该仓库的主服务器,并且在一台或多台服务器上有只读副本,类似地称为从服务器。当主仓库被更新时,从仓库会几乎即时更新,具体取决于网络速度。

仓库级别镜像

大多数镜像的主要目的是通过提供位于附近服务器上的副本来加速读取访问(通过网络),以便进行拉取和克隆。因此,可以预期将指定一台服务器为主服务器,其他几台作为从服务器。

然而,Gitolite 的镜像是在仓库级别定义的,而不是针对整个服务器。对于每个仓库,管理员决定哪个服务器持有主(可写)副本,其他服务器(如果有的话)是否应该为其本地用户提供从属副本。(这就是为什么我们说主仓库和从仓库,而不是主服务器和从服务器。)

这是一个非常重要的概念,相比于镜像整个服务器,它带来了几个优势。

首先,不必在没有该仓库本地用户的服务器上提供仓库。如果用户非常少(例如一两个用户),根据仓库的开发活跃度,从网络利用率的角度来看,仍然可能有意义让这些少数用户直接从主仓库或其他从仓库拉取。这允许资源较少的分支机构仍然能成为 Gitolite 镜像网络的一部分,只要他们有足够的项目供本地用户工作。

其次,由于你不必将所有主仓库放在同一台服务器上,你可以选择将任何特定仓库的主副本放置在哪里。通常,你会选择将其放置在离大多数将要在其上工作的开发者最近的服务器上(从网络速度的角度来看)。

最后,仓库可以仅对特定服务器本地可用,也就是说,它们不需要被镜像。

注意

只能有一个主仓库——你不能让不同的用户推送到不同的服务器,因为那样会导致合并问题,无法可靠地自动化。(不过,Gitolite 确实有一个方便的解决方法,稍后我们会看到。)

gitolite-admin 仓库

从 Gitolite 镜像功能的角度来看,gitolite-admin 仓库只是另一个仓库。就镜像而言,它没有什么特别之处——与所有其他仓库一样,其中一台服务器将持有主(可写)副本。唯一的区别是,所有其他服务器必须被列在从属列表中;如果某个服务器未被提及,它将无法接收该仓库的更新。

设置镜像

设置镜像有相当多的手动步骤,尽管每个步骤本身都非常简单。第一步是给每个主机一个名称,并确保这些主机可以通过这些名称相互通信。第二步是启用每个服务器的镜像功能。

我们首先在所有服务器上正常安装 Gitolite。然后,我们编辑每个服务器的 $HOME/.gitolite.rc 文件,并取消注释定义 HOSTNAME 的行,选择一个简单的主机名作为值。

提示

主机名应该是一个简单的词;它仅对 Gitolite 有意义,与操作系统和 DNS 记录所知道的服务器主机名无关。

Gitolite 镜像通过允许服务器使用名为这些主机名的 ssh 密钥对进行相互通信来实现。这与用户使用的机制相同,唯一不同的是,服务器的公钥文件命名为server-,后跟该服务器的主机名。所有密钥需要提供给所有服务器,添加到它们的 gitolite-admin 仓库中,并进行推送。最后,每个服务器的 $HOME/.ssh/config 需要一个主机别名,用于引用所有其他机器。

示例设置

一个例子会使这个更加清晰。假设你有三台服务器:jupiter、ganymede 和 callisto。这些服务器的 IP 地址分别是 10.1.1.110.2.1.110.3.1.1

  1. 首先,在所有三个服务器上安装 Gitolite。在 Jupiter 上,编辑 $HOME/.gitolite.rc 文件,并取消注释 HOSTNAME 行,按如下方式更改(不要忘记末尾的逗号!):

    HOSTNAME    =>  "jupiter",
    
    
  2. 在其他两个服务器上执行相同操作。

  3. 在每个服务器上运行 ssh-keygen 来创建该服务器的密钥对。收集所有三个 $HOME/.ssh/id_rsa.pub 文件(每个服务器的一个),分别命名为 server-jupiter.pubserver-callisto.pubserver-ganymede.pub

  4. 将这些公钥添加到所有服务器,就像你添加普通用户一样(也就是说,对于每个服务器,从该服务器克隆 gitolite-admin 仓库,添加所有这些密钥到 keydir,然后添加、提交并推送)。

  5. 接下来,将以下行添加到所有三个服务器的 $HOME/.ssh/config 文件中(假设三个服务器上的托管用户都是 git):

    host jupiter
     user git
     hostname 10.1.1.1
    host ganymede
     user git
     hostname 10.2.1.1
    host callisto
     user git
     hostname 10.3.1.1
    
    

完成所有这些操作后,你应该能够通过从木星服务器运行 ssh ganymede info 来测试你的设置。你应该得到类似如下的输出:

hello server-jupiter, this is git@ganymede.example.com running gitolite3 v3.5.3.1-7-g31d11b9 on git 1.8.3.1 

从任何服务器到其他服务器的类似命令应该生成类似的响应。最好测试所有可能的组合,以避免后续出现混淆的错误。

启动镜像过程

上一步骤建立了所有服务器之间的通信和认证。一旦完成并检查无误,下一步是通过为 gitolite-admin 仓库设置镜像选项并推送它们,来启动镜像过程。

在每台服务器上,将以下行添加到 conf 文件中:

repo gitolite-admin
 option mirror.master   = jupiter
 option mirror.slaves    = callisto ganymede

一旦你添加、提交并推送这些更改到所有仓库,Gitolite 镜像就已设置并准备好使用。从此之后,你可以通过推送到 jupiter 服务器上的 gitolite-admin 仓库来管理一切。

镜像其他仓库

到现在,你应该知道需要为其他每个仓库做什么。只需像上一节那样为每个仓库添加选项行。例如,以下是一个简单的镜像设置,所有仓库的镜像方式相同:

repo @all
 option mirror.master   = jupiter
 option mirror.slaves    = callisto ganymede

如果这就是你想要的,那么镜像过程就算完成了。否则,按需要添加选项,为每个仓库指定正确的主服务器名称和正确的一组从服务器。请注意,任何没有这些设置的仓库都被认为是本地仓库。

如果允许用户创建自己的仓库(参见第八章,允许用户创建仓库),只需将镜像选项行添加到这些仓库定义中。以下限制适用于镜像用户创建的仓库。

只有当第一次推送到主服务器上的仓库时,才会传播该仓库的创建。作为克隆或获取结果创建的仓库不会立即传播到从服务器。

权限的更改(使用 perms 命令;参见第八章,允许用户创建仓库,特别是授予其他用户访问权限部分)也仅在下次推送时传播。

本地仓库和主机名替换

我们之前提到,某些服务器可以有本地仓库,比如如果conf文件没有为这些仓库指定 Gitolite 镜像选项的话。然而,只有每个服务器可以拥有自己的一组本地仓库才真正有意义。例如,假设盖尼米德的人想要一个名为 ganymede-news 的本地仓库,他们打算在内部使用。将其添加到conf文件将在木星和卡利斯托上创建这样一个仓库;即使没有人使用它,这也是不美观、不必要的,并且可能导致未来的混淆。我们想要避免这种情况,但由于相同的 gitolite-admin 仓库,使用相同的conf/gitolite.conf文件和其他文件被推送到所有从服务器,似乎很难做到。

使此功能可行的特性是HOSTNAME替换。具体来说,每当 Gitolite 在conf文件中遇到字符串%HOSTNAME(后面跟着一个非字母数字字符;即,除了字母、数字和下划线以外的字符),它将在处理之前使用$HOME/.gitolite.rc中给出的服务器HOSTNAME替换它。

使用我们在第六章中看到的include语句能力,访问控制入门,你现在可以提供特定于服务器的 Gitolite 配置,同时仍然保持所有内容在一个仓库中。只需将以下行添加到主配置文件(conf/gitolite.conf):

include "%HOSTNAME.conf"

然后,为每个服务器创建conf/jupiter.confconf/callisto.conf等文件。在这些文件中,指定各自服务器特有的仓库语句和规则。

使用include语句在主conf文件中是很重要的,每个服务器在推送时会看到这行代码不同。例如,对于木星服务器,会看到如下行(其他服务器类似):

include "jupiter.conf"

这意味着另外两个文件(ganymede.confcallisto.conf),虽然它们在conf目录中确实存在,但实际上根本没有被处理。

重定向推送

我们之前提到只能有一个主仓库。然而,从开发者的角度来看,这会使事情有些不便。假设你希望一些开发者利用地理位置接近的从服务器来获取更新。这意味着他们必须使用推送到与他们克隆的服务器不同的服务器,并定期拉取/获取。虽然 Git 允许这样做(查看 man git-config 并查找 pushurl,是一种可能的方法之一),但每个用户仍需要在其机器上执行某些操作。此外,请记住,Gitolite 允许不同的仓库有不同的主服务器;这会增加开发者混淆的可能性。

因此,Gitolite 提供了一个功能,使得这个限制变得不那么繁重。如果管理员选择,开发者可以推送到从服务器,并且 推送将透明地被转发到该仓库的主服务器。事实上,除非网络速度使其显而易见,用户甚至可能不知道这种事情发生了。

这为管理员带来了极大的便利;不再需要让开发者混淆每个仓库需要克隆和推送的位置。实际上,你可以设置,使得某个特定站点的所有开发者都使用本地的 Gitolite 服务器来进行所有克隆,而无需知道 Gitolite 镜像功能正在被使用!

这个功能被称为 重定向推送,因为这正是后台发生的事情。以下是如何使用它。假设你希望允许从 ganymede(而不是 callisto)进行重定向推送(推送到 repository foo),你需要在 conf 文件中添加以下几行:

repo foo
 option mirror.redirectOK  = ganymede

就这样。只需列出你信任的所有从服务器,并将它们以空格分隔的形式作为该选项的值。如果你信任所有从服务器,直接使用单词 all 即可。

手动同步

有时你会发现某个从服务器没有更新,可能是由于某些临时的网络问题。在网络问题解决后,要让其重新同步,一种方法当然是对仓库进行一些修改并推送。然而,这种方式显得笨拙且不优雅。

注意

目前,Gitolite 并没有提供推送失败的报告功能,尽管它会记录 Git 本身返回的所有信息在日志文件中。这意味着管理员需要主动监控日志文件以检测失败,或者等待用户报告从服务器不同步的问题。

对于美学上较为敏感的人,Gitolite 提供了镜像命令。只需登录到包含主仓库的服务器,然后运行 gitolite mirror push slavename reponame。这将立即使从服务器与主服务器同步。

如果你希望远程用户能够触发此类更新,只需通过取消注释 $HOME/.gitolite.rc 中相应的行来启用远程使用该命令。

切换到不同的主服务器

镜像的最大日常好处显然是,它可以让分布广泛的开发团队更加高效地进行网络读取,尽管这会在每个从服务器位置增加额外的服务器成本。

然而,显然还有一个额外的优势。如果一个仓库的主服务器发生故障,可以轻松地将其中一个镜像从从服务器列表中移除,并将其设置为主服务器。只要没有网络问题,从服务器应该与主服务器保持相同的状态,因此不会丢失任何提交。

提示

镜像与备份不同。本书的范围不涉及这两者之间的区别,但你应该始终为所有服务器和可能的工作站设置一个可靠的备份系统。

当死掉的服务器包含了 gitolite-admin 仓库时,这个简单的策略就会崩溃。这有点像是进退两难的情况;你无法将更改(在 gitolite-admin 仓库的镜像选项中)推送到当前的主服务器,因为它已经宕机了。然而,你也无法将更改推送到你决定提升为“主”服务器的从服务器,因为它还不知道自己已经是新的主服务器了!(不言而喻,作为一种安全功能,从服务器不接受来自任何地方的推送——它们只会接受来自被告知是相关仓库主服务器的服务器的推送。)

解决这个问题的正确方法如下:

  1. 在每个从服务器上,编辑$HOME/.gitolite/gitolite.conf。更改 gitolite-admin 仓库的主服务器和从服务器选项。如果你为这些选项使用了@all仓库,它们将会影响到所有仓库。然而,最好只为 gitolite-admin 仓库修改这些选项,如果可能的话。

  2. 在保存对该文件的更改后,运行gitolite setup。这将覆盖限制并强制在本地服务器上应用更改。(出于安全原因,这不能远程执行。)

  3. 现在,在管理员的工作站上,从新的主服务器克隆 gitolite-admin 仓库,修改其他仓库的镜像选项(如果需要),然后推送更改。

总结

本文结束了我们对 Gitolite 镜像功能的探索。虽然这并不是每个人都需要的功能,但对于较大的站点来说,这具有一些非常明显的好处。

希望你喜欢本次对 Gitolite 及其功能的探索。随着你对 Gitolite 的使用越来越多,你可能还需要查看官方文档,网址为gitolite.com。我们希望这本书能帮助你理解 Gitolite 背后的概念和原理,从而更好地理解官方文档,帮助你更快地完成工作。

posted @ 2025-07-02 17:45  绝不原创的飞龙  阅读(16)  评论(0)    收藏  举报