Jan 22

通常,一个大型工程总会被分拆为一些子工程。这既有利于工程开发难度的降低,也有利于使用现成的方案或者第三方方案作为子工程。git-submodule 就是完成这样一种子工程拆分与整合的工具。下面开始简单介绍一下 git-submodule 的用法。

A、带 submodule 的 git 仓库的创建
正如 git 仓库有两种创建方式一样,带 submodule 的 git 仓库的创建也有两种。

1、新建一个大型工程

$ cd /path/to/massproj
$ echo Example of Mass Project > README.md
$ git init
$ git add README.md
$ git commit -m 'Mass Project created.'

接着使用 git submodule add 为新建的工程增加一个第三方子工程,例如

$ git submodule add git@domain.com:subproj.git subproj
$ git status

可能会注意到,git 只记录了 submodule 目录,而没有记录目录下的文件。实际上,git 是按照 commit id 来比对 submodule 变动的。直接递交本次更改

$ git commit -m 'thirdpart submodule added.'

若有管理该项目的 Git 服务器,可上传该项目至服务器,例如

$ git remote add origin git@domain.com:massproj.git
$ git push origin master

2、克隆一个带 submodule 的 git 仓库

$ cd /path/to/hold/projcet/
$ git clone git@domain.com:massproj.git

不过不同于常规的 git 仓库克隆,还需要将该仓库中的子模块初始化以及更新:

$ git submodule init
$ git submoudle update

如果觉得这样克隆一个 git 仓库太麻烦,那么试试

$ git clone --rescursive git@domain.com:massproj.git

该命令行会自动完成一系列必需的动作。

B、带 submodule 的 git 仓库更新
git 仓库正常更新过程是这样的:

$ git pull

然后根据修改情况,自动解决冲突、递交变更并合并到 git 仓库。不过对于带 submodule 的 git 仓库的更新,就有两种情况了。

1、更新主 git 仓库,如前,先执行

$ git pull

就要留意 submodule 的变更,先试试

$ git status

若发现 submodule 有修改,需立即执行

$ git submodule update

更复杂一些的情况是,如果有一个 submodule 依赖另一个 submodule,那么很可能需要在 git pull 和 git submodule update 之后,再分别到每个有依赖关系的 submodule 目录中再执行一次 git submodule update。为了免去麻烦,可以执行

$ git submodule foreach "git submodule update"

来实现一次性更新所有 submodule。

2、更新某个子模块,需先进入子模块目录,然后执行普通的更新操作,例如

$ cd subproj
$ git pull

若有很多子模块需要做类似的操作,可简单执行

$ git submodule foreach "git pull"

若子模块之间还有依赖关系,可采用

$ git submodule foreach "git pull" --rescursive

来一次性更新所有模块。

不过需要注意的是,这样的更新操作都是在本地 git 仓库没有做任何修改的前提之下完成的。假如本地仓库有所改动,特别是本地仓库中的子模块有所修改,那么有可能还需要解决一系列冲突才能合并。这需要用到下面这段的知识。

C、带 submodule 的 git 仓库的修改
本地仓库的普通修改不是这里的主要内容,这里要讨论一下本地仓库中子模块的更改。比如我们拥有某个 submodule 的远程仓库操作权限,此时正好碰到该子工程被大型工程调用时需要修改代码。切回到的该子工程在本地的原先仓库位置进行相关操作当然略显麻烦,若能在该大型工程的子模块仓库中直接修改无疑更方便一些。这里就来介绍一下子模块修改的大概流程。

1、有一个重要的事实需要特别强调,在执行 git submodule update 时 git 默认并不会将 submodule 切到任何 branch。因此,submodule 的 HEAD 默认是处于游离状态的(‘detached HEAD’ state)。在子模块修改前,记得一定要将当前的 submodule 分支切换到相应分支,例如切换到 master 分支:

$ cd /path/to/massproj/subproj
$ git checkout master

然后才能对子模块做修改和提交。另外,子模块中所跟踪的远程分支可被 .gitmodules 或者 .git/config 的配置覆盖,例如

$ nano -w .gitmodules
[submodule "subproj"]
   path = subproj
   url = git@github.com:subproj.git
   branch = master

实际上,.gitmodules 的修改可通过在增加 submodule 时直接指定跟踪分支自动完成,例如

$ git submodule add git@github.com:subproj.git -b master

若有很多 submodule 需要类似分支切换操作,可使用如下命令

$ git submodule foreach "git checkout master"

实际上,高版本的 git 解决这一问题的方式更简单,例如执行

$ git submodule update --remote

来跟踪远程分支,默认是 origin/master;若子模块之间还有依赖关系,可采用

$ git submodule update --remote --rescursive

一次性跟踪所有子模块的远程分支。

2、下面开始对子模块目录做一些修改,例如修改 README.md 文件:

$ cd /path/to/massproj/subproj
$ echo this is a new line. >> README.md

再将修改递交到 git 仓库中:

$ git add README.md
$ git commit -m "new comments added."
$ git push

递交完之后,回到大型工程的顶层目录,该示例中就是

$ cd ..

再试试

$ git status

会看到子模块 subproj 还需要再做一次递交

$ git add subproj
$ git commit -m 'submodule updated'
$ git push

由于 submodule 的更新只记录 commit id,因此,子模块的修改必须按上述流程递交版本。也即先在 submodule 內做递交,之后回到顶层目录再作一次递交,不然会引起 git 仓库的版本错乱。

3、如果你不慎在修改子模块之前忘记将它切换到远程跟踪分支,且又做了提交,此时可以用 cherry-pick 命令挽救。具体做法如下:先到该子模块将 HEAD 从游离状态切换到远程跟踪分支,例如 master 分支:

$ cd subproj
$ git checkout master

这时候,git 会报 Warning 说有一个提交没有在 branch 上,记住这个提交的 change-id(假如 change-id 为 aaaa)。随后将刚刚的提交重新作用远程跟踪分支上:

$ git cherry-pick aaaa

最后只需将更新提交到远程版本库中:

$ git push

D、git 仓库中 submodule 的更名或移动
随着大型工程的进行,有可能需要变更子模块,例如移动子模块的位置,或者第三方方案本身需要更名等。由于 submodule 的更名与移动两者差别不大,这里主要介绍子模块移动目录的流程。至于更名,只略微提及。假设要将 subproj 移动到 /massproj/platform 目录之下,先新建目录并移动子模块到里面

$ cd /path/to/massproj
$ mkdir platform
$ mv subproj platform/

修改相关的配置文件,主要是 .gitmodules、.git/config、子模块目录中的 .git 以及 .git/modules 目录中几个文件与目录:

$ sed -i 's@subproj@platoform/subproj@' .gitmodules
$ sed -i 's@subproj@platoform/subproj@' platform/subproj/.git
$ mv .git/modules/subproj .git/modules/platform/subproj
$ sed -i 's@subproj@platoform/subproj@' .git/modules/platform/subproj/config

若是子模块更名,那么还请留意 .gitmodules、.git/config 以及 git/modules/<path/to/submodule>/config 中 url 设置,根据需要进行调整。最后让 git 记录所有的变更

$ git rm --cached subproj
$ git add platform/subproj .gitmodules
$ git submodule sync -- platform/subproj

若觉得上述手续太烦且网络速度还可以的话,可直接删除原先的子模块,随后指定新路径添加模块。有关这个方法,需先了解一下后一段知识,再回过头来看一下以下操作

$ cd /path/to/massproj
$ git rm --cached subproj
$ rm -rf subproj
$ rm -rf .git/modules/subproj
$ nano -w .gitmodules
...remove subproj...
$ nano -w .git/config
...remove subproj...
$ git submodule add git@github.com:subproj.git platform/subproj -b master
$ git add .gitmodules
$ git commit -m 'subproj moved to platform/.'
$ git push

E、带 submodule 的 git 仓库中 submodule 的删除
这里以 platform/subproj 子模块为例介绍一下子模块删除的大概流程。先到大型工程的顶层目录清理子模块文件:

$ cd /path/to/massproj
$ git rm --cached platform/subproj
$ rm -rf platform/subproj
$ rm -rf .git/modules/platform/subproj

接着删除 git 中记录的相关数据

$ nano -w .gitmodules
...remove platform/subproj...
$ nano -w .git/config
...remove platform/subproj...

随后将变更递交到远程仓库

$ git add .gitmodule
$ git commit -m 'platform/subproj submodule removed.'
$ git push

Sep 6

cgit 是一款为 Git 服务器提供 Web 浏览界面的工具,与 gitweb 的功能类似。不过,cgit 的界面更加友好一些,速度更快一些。这里就来谈谈如何在 Fedora 19 中安装与配置 cgit,以及用它替代 gitweb 实现与 Gitolite 的整合。

cgit 的安装非常便当,仅需执行:

$ sudo yum install cgit
接着适当地调整 cgit 目录的访问权限,这里以 Apache 服务器为例:
$ sudo nano -w /etc/httpd/conf.d/cgit.conf
#
# cgit configuration for apache
#

ScriptAlias /cgit /var/www/cgi-bin/cgit
Alias /cgit-data /usr/share/cgit/

<Directory "/usr/share/cgit/">
   AllowOverride None
   Options None
  <IfModule mod_authz_core.c>
       # Apache 2.4
       Require all granted
   </IfModule>
   <IfModule !mod_authz_core.c>
       # Apache 2.2
       Order allow,deny
       Allow from all
   </IfModule>
</Directory>

<Directory "/var/www/cgi-bin"> 
  AllowOverride None 
  Options ExecCGI FollowSymlinks
  <IfModule mod_authz_core.c>
       # Apache 2.4
       Require all granted
   </IfModule>
   <IfModule !mod_authz_core.c>
       # Apache 2.2
       Order allow,deny
       Allow from all
   </IfModule>
</Directory>
注意,确保上述设置与 cgit 的一下配置一致,也即编辑 /etc/cgitrc 文件:
$ sudo nano -w /etc/cgitrc
查看下面几行
# cgit config

css=/cgit/cgit.css
logo=/cgit/cgit.png
如果不想被搜索引擎索引的话,还可往里添加一行:
# if you don't want that webcrawler (like google) index your site
robots=noindex, nofollow
修改完成之后,重启 Apache 服务器
$ sudo service httpd restart
如果Apache 服务器已经配置好了的话,可以测试一下 cgit 的功能是否正常工作。这只需在可以访问当该服务器器的电脑上打开网页浏览器,并在地址栏输入 http://serverIP/cgit。如果网页能够打开,那么表明 cgit 已经能够工作了。
 
需要注意,上面只是测试了 cgit 提供的 Web 界面是否工作,但是还没有涉及到 Git 服务器的部分。下面开始来谈论 cgit 如何关联到 Git 服务器。实际上,它是通过 /etc/cgitrc 配置文件来关联 Git 服务器的:
$ sudo nano -w /etc/cgitrc
根据具体情况编辑该配置文件,例如修改下面这些行:
#
# List of repositories.
# This list could be kept in a different file (e.g. '/etc/cgitrepos')
# and included like this:
#   include=/etc/cgitrepos

#repo.url=foo
#repo.path=/var/lib/git/foo.git
#repo.desc=the master foo repository
#repo.owner=fooman@example.com
#repo.readme=info/web/about.html

repo.url=MyRepo
repo.path=/var/lib/git/MyRepo.git
repo.desc=This is my git repository
实际上,真正与 Git 服务有关的只有最后三行,这三行给出了 Git 仓库的位置、名称以及扼要的描述。更多的 Git 仓库,均可按该格式添加至 cgit 的配置文件。现在在网页浏览器的地址栏再次输入 http://serverIP/cgit 测试一下,看看能不能访问到 Git 仓库信息。
 
为了更好地控制 Git 服务器的访问权限,一般 Git 服务部分都是通过 Gitolite 这样的工具实现的。Gitolite 的配置过程在前面已经描述过了。自然而然的,需要考虑如何以 cgit 替代 gitweb,将它与 Gitolite 整合在一起。解决这个问题的方法与实现 gitweb 的整合过程差不多。首先,修改 Gitolite 新建仓库的默认权限,这可通过修改 /var/lib/gitolite/.gitolite.rc 的 UMASK 实现:
$ sudo nano -w /var/lib/gitolite/.gitolite.rc
更改 UMASK 为 0022,也即
#$REPO_UMASK = 0077; # gets you 'rwx------'
#$REPO_UMASK = 0027; # gets you 'rwxr-x---'
$REPO_UMASK = 0022; # gets you 'rwxr-xr-x'
接着通过下述命令修改 Gitolite 中所有现有仓库的权限:
$ sudo find /path/to/the/repository/ -type d -exec chmod og+rx {} \;
$ sudo find /path/to/the/repository/ -type f -exec chmod og+r {} \;
最后,为了让 cgit 能够自动访问到 Gitolite 中仓库信息,仍需修改配置文件 /etc/cgitrc:
$ sudo nano -w /etc/cgitrc
提供类似于 gitweb 中所需要的 projects.list 等信息,例如:
#
# information related to gitolite 
#

enable-git-config=1
enable-gitweb-owner=1
remove-suffix=1
project-list=/var/lib/gitolite/projects.list
scan-path=/var/lib/gitolite/repositories
请注意最后这 5 行,它们包含了与 Gitolite 整合所需要的所有内容。至于 Gitolite 中的修改,完全与 gitweb 所需的修改一致,这里就不再重复了。
 
如果还想为 cgit 锦上添花的话,可安装 highlight 实现 cgit 访问的 Git 仓库中源代码的高亮:
$ sudo yum install highlight

不过这还需要 cgit 的配置,也即

$ sudo nano -w /etc/cgitrc

增加或去掉下面这一行的注释

source-filter=/usr/lib/cgit/filters/syntax-highlighting.sh
Jul 30

Gitolite 是一款由 Perl 语言开发的 Git 服务管理工具,通过 SSH 公钥对用户进行认证,并能够通过配置文件对版本库的写操作进行基于分支、标签以及文件路径等的权限控制。 gitweb 为 Gitolite 提供了版本库的 Web 界面,方便用户浏览版本库的变更与版本打包;另一方面,由于 Gitolite 并没有提供匿名用户的读权限,因此,需要借助 git-daemon 来弥补这一缺陷。

1、Gitolite 的安装与配置

Gitolite 的安装非常简单,执行下述命令即可:
$ sudo yum install gitolite
查看 Gitolite 的用户信息
$ cat /etc/passwd | grep -i gitolite
gitolite:x:976:971:git repository hosting:/var/lib/gitolite:/bin/sh
这表明 Fedora 中 Gitolite 的家目录位于 /var/lib/gitolite。
 
下面开始初始化 Gitolite。由于 Gitolite 采用的是 SSH 协议,因此在初始化开始之前必须注入 Gitolite 管理者的 SSH 公匙。这里不去谈论 SSH 密匙的生成过程了,直接假定拥有 SSH 密匙的用户 James 将成为 Gotolite 的管理者:
$ cp ~/.ssh/id_rsa.pub /tmp/james.pub
请注意公匙的命名方法,这是 Gitolite 区别各个用户的默认办法。另外,可能出于安全的考虑,我们可能修改过 ~/.ssh 目录下文件的权限。由于下面的操作需要用户 James 的公匙具有写权限,所以需要执行下面的命令行
$ chmod a+r /tmp/james.pub
还有一件事情需要指出,既然 Gitolite 使用的是 SSH 协议,因此它工作的前提是运行 sshd 系统服务,请查看它是否运行
$ systemctl status sshd

如果没有运行的话,请执行下述命令行,至于 SSH 端口之类的问题就不做赘述了:

$ sudo systemctl start sshd
$ sudo systemctl enable sshd
现在可以初始化 Gitolite 了:
$ sudo -u gitolite -H gl-setup /tmp/james.pub
 
好了,下面该让我们稍微解释一下 Gitolite 对 Git 服务器的管理方式了。 Gitolite 通过导入用户的 SSH 公匙的方式来实现对 GIT 服务器的用户管理。Gitolite 利用配置文件 /var/lib/gitolite/repositoreis/gitolite-admin.git/config/gitolite.conf 实现了用户权限的控制。非常有意思的是,Gitolite 以 GIT 版本库的形式在管理 gitolite.conf 与所有用户的 SSH 公匙。具体来说,Gitolite 中预设了两个版本库,即 gitolite-admin.git 与 testing.git:
$ sudo ls /var/lib/gitolite/repositories
gitolite-admin.git  testing.git
可能我们已经注意到,版本库目录中的版本库名字均是以 .git 结尾的,这是 Gitolite 的约定。Gitolite 的管理者正是通过 gitolite-admin.git 来管理版本库的,他可以以版本控制的方式管理它。而 Gitolie 会自动根据该版本库中的修改来产生新的配置。好了,让我们的管理者把 gitolite-admin.git 克隆到他的家目录中去吧:
$ cd
$ git clone gitolite@serverIP:gitolite-admin.git
$ cd gitolite-admin
$ cat conf/gitolite.conf
repo    gitolite-admin
        RW+     =   james

repo    testing
        RW+     =   @all
我们很容易看到 gitolite.conf 中奇特的语法。每一个版本库的授权均以 repo 指令开头。repo 后面紧跟的是版本库列表。版本库列表中的版本库名之间用空格分开,版本库名中可以含有相对路径名。根据 Gitolite 的默认规则,repo 后面的版本库名不需要以 .git 为后缀,因为 Gitolite 会自动添加该后缀名。另外,版本库列表里面还可以包括版本库组。版本库组就是一些以空格间隔的版本库名的合集,版本库组名以@开头,例如
@testings testing james/testing
repo @testings
repo 指令也可以紧跟以正则表达式定义的通配符版本库。需要注意通配符版本库在匹配时,会自动在通配符版本库的正则表达式前、后分别加上前缀 ^ 和后缀 $。例如
repo james/.+
要当心的是版本库名中的正则表达式过于简单,就可能引起歧义,让 Gitolite 以为它就是普通版本库名称。为了不必要的麻烦,可在正则表达式的前或后主动加上 ^ 或 $ 符号,例如
repo james/.+$
 
repo 指令后续的几行就是版本库的授权命令列表。每条授权指令有相同的缩进,且以 = 为标记分为前后两段,等号前面的是权限表达式,等号后面的是用户列表。授权指令的语法类似于
perms [regex ...] = user1 [user2 ...]
正如我们所看到的,授权指令中包含一个可选的 regex 列表。regex 列表有正则表达式给出,它们与 repo 所指定的版本库里的分支、标签甚至是文件名或路径进行匹配。具体说来,正则表达式 regex 可能与分支名匹配,也可能与分支组匹配。分支组名以@开头,它是一些有空格间隔的分支名的合集,例如
@mainstream master developing testing
regex 也可能与 refs/tags/ 中的标签名匹配;regex 还可能与版本库中的文件名或文件路径匹配。如果 regex 中的表达式不以 $ 结尾的话,那么它将匹配以该正则表达式开头的任意字符,相当于在表达式后面添加 .*$。如果授权指令中不包含任何 regex 列表,那么该授权将针对由 repo 指定整个的版本库。
 
perms 部分只能出现如下授权之一:-、C、R、RW、RW+、RWC、RW+C、RWD、RW+D、RWCD、RW+CD。它们的具体含义如下:
  • -:不能写入、但是可以读取;
  • C:仅在通配符版本库中可以使用。用于指定谁可以创建和通配符匹配的版本库。
  • R, RW, 和 RW+:R 为只读。RW 为读写权限。RW+ 含义为除了具有读写外,还可以对 rewind 的提交强制 PUSH。
  • RWC, RW+C:只有当授权指令中定义了 regex 列表才可以使用该授权指令。其中 C 的含义是允许创建与 regex 匹配的分支、标签、文件名或者路径。
  • RWD, RW+D:只有当授权指令中定义了 regex 列表才可以使用该授权指令。其中 D 的含义是允许删除与 regex 匹配的分支、标签、文件名或者路径。
  • RWCD, RW+CD:只有当授权指令中定义了 regex 列表才可以使用该授权指令。
Gitolie 中的用户通常是以公匙文件名来指定的,这是 Gitolite 中的用户的表示方法之一。另一种 Gitolite 用户的表示方法就是用户组,用户组的命名以@开头,例如
@myteam james alice
定义了一个名为 @myteam 的用户组,james、alice 都是属于 myteam 这个用户组。Gitolite 中有个特殊的用户组,即@all,用来表示所有的用户。
 
下面给出一个权限控制的示例
@admin james
@myteam james alice
@test alice

@testings james/testing alice/testing

@mainstream master developing

repo    james/.+$
        C                           =   @admin
        R       @mainstream         =   @test
        -                           =   bob
        RW                          =   @myteam bob

repo    testing @testings
        RW+                         =   @admin
        RW      master              =   bob
        RW      developing$         =   alice
        -                           =   bob
        RW      tmp/                =   @all
        RW      refs/tags/v[0-9]    =   alice
这里有一点需要指出,那就是当同一个用户或者组有多个授权时,Gitolite 将采用所有授权的并集。
 
为了将上面的介绍与实际应用相结合,不妨考虑为 Gitolite 增加一个版本库 myproj、两个用户组 @admin 与 @myteam,以及用户 Alice 并授予她 myproj.git 的读写权限与用户 Bob 但只给予 myproj.git 的读权限。先让 Gitolite 的管理者 James 执行下述命令:
$ cd ~/gitolite-admin
$ cp /path/to/alice.pub keydir/
$ cp /path/to/bob.pub keydir/
$ nano -w conf/gitolite.conf
@admin james
@myteam james alice

repo    gitolite-admin
        RW+     =   @admin

repo    testing
        RW+     =   @all

repo    myproj
        RW+     =   @myteam
        RW+CD   =   @admin
        R       =   bob
$ git add conf/gitolite.conf keydir/alice.pub keydir/bob.pub
$ git commit -m 'new user and repo created.'
$ git push origin master
接着再让 James 在自己的主机家目录下创建新项目 myproj 并推至服务器:
$ mkdir -p ~/myproj
$ cd ~/myproj
$ git init
$ echo "hello, Gitolite!" >> hello.txt
$ git add hello.txt
$ git commit -m 'initial commit.'
$ git remote add origin gitolite@serverIP:myproj.git
$ git push origin master
现在试试让 Alice 在自己的主机上克隆并做一次提交
$ cd ~
$ git clone gitolite@serverIP:myproj.git
$ cd myproj
$ echo "hi, I'm Alice." >> hello.txt
$ git add hello.txt
$ git commit  -m 'say hello to Git Server."
$ git push origin master

再来让 Bob 试试 GIT 服务器的情况:

$ cd ~
$ git clone gitolite@serverIP:gitolite-admin.git

正常情况,Bob 无法克隆该仓库;

$ git clone gitolite@serverIP:testing.git
$ cd testing
$ echo 1 > index.txt
$ git add index.txt
$ git commit -m 'write permission for testing repo.'
$ git push origin master

这表明 Bob 拥有 testing.git 的读写权限;再来看

$ cd
$ git clone gitolite@serverIP:myproj.git
$ myproj
$ echo "hello, I'm Bob." >> hello.txt
$ git add hello.txt
$ git commit -m 'say hello to Gitolite.'
$ git push origin master

这表明 Bob 只能读取 myproj 但没有写权限。这几个操作表明 Gitolite 比较完美的控制了用户操作 GIT 服务器的权限。

2、与 gitweb 的整合
 
最初 gitweb 是为 GIT 服务器的 http 协议提供 Web 界面的。这里正是借助这个功能,为 Gitolite 实现同样的功能。同时,Gitolite 也为 gitweb 提供了两个便利:为 gitweb 提供了设置版本库的描述信息途径,可用于在 gitweb 的项目列表页面显示;为 gitweb 自动生成项目的列表文件,避免 gitweb 使用效率低的目录递归搜索查找 Git 版本库列表。
 
安装 gitweb 与 highlight:
$ sudo yum install highlight gitweb
其中 highlight 是为了在网页中查看代码的时候可以高亮代码的语法。查看 gitweb 的配置文件所在的位置:
$ rpm -qc gitweb
/etc/gitweb.conf
/etc/httpd/conf.d/git.conf
/etc/httpd/conf.d/git.conf 是与 Apache 相关的配置。gitweb 本身的配置文件是 /etc/gitweb.conf,下面开始配置 gitweb:
$ sudo nano -w /etc/gitweb.conf
根据需要调整里面的内容,下面这几行的内容必须调整
our $projectroot="/var/lib/gitolite/repositories";
our $projects_list="/var/lib/gitolite/projects.list";
至于语法高亮,自己看着办
# Add highlighting at the end 
$feature{'highlight'}{'default'}= [1];
 
为了能够让 Apache 访问到 Gitolite 目录中的文件,需要将 apache 加入到 gitolite 组中,并修改相关文件的权限
$ sudo usermod -a -G gitolite apache
$ sudo chmod g+r /var/lib/gitolite/projects.list
$ sudo chmod -R g+rx /var/lib/gitolite/repositories
由于 Gitolite 中默认新建文件的权限是 0077,这意味着要让 Apache 访问之后新建版本库,必须对之后新建版本库权限作类似的修改。为了免去这一麻烦,这里利用 Gitolite 的配置文件 ~/.gitolite.rc 来指定新建文件的权限:
$ sudo nano -w /var/lib/gitolite/.gitolite.rc
根据自己的需要修改下面的行
#$REPO_UMASK = 0077; # gets you 'rwx------'
#$REPO_UMASK = 0027; # gets you 'rwxr-x---'
$REPO_UMASK = 0022; # gets you 'rwxr-xr-x'
好了,一切就绪了,重启 Apache 服务:
$ sudo service httpd restart
 
以上配置基本都是在告诉 Apache 一些 GIT 服务器的信息。不过,为了让 gitweb 工作,还需要让 Gitolite 知道哪些仓库启用了 gitweb。这可以通过下面这个例子来了解这个机制,让管理者再次修改 Gitolite 的配置文件
$ cd
$ cd gitolite-admin
$ nano -w conf/gitolite.conf
repo    gitolite-admin
        RW+     =   james

repo    testing
        testing "James" =   "Git repository for testing"
        RW+     =   @all
        R       =   gitweb
$ git add conf/gitolite.conf
$ git commit -m 'gitweb support for Gitolite.'
$ git push origin master

很明显,'R = gitweb' 这一行告诉 Gitolite 版本库 testing.git 启用 gitweb,这样 Gitolite 就会把 testing.git 写入 projects.list 文件;而 'testing "James" = "git repository for testing"' 这一行则是为了在网页中显示 testing 仓库的额外信息,也即仓库的创建者以及仓库的概述。其他版本库启用 gitweb 的设置完全类同,这里就不一一修改了。实际上,版本库的信息的设定也通过如下的命令来完成:

repo    testing
        config gitweb.owner         =   some person's name
        config gitweb.description   =   some description
        config gitweb.category      =   some category 
 
是时候打开网页浏览器开始测试了,在地址栏中输入 http://serverIP/git,正常情况应该出现下图。
 
3、与 git-daemon  的整合
 
最后,让我们利用 git-daemon 为 Gitolite 开启匿名访问功能。首先安装它:
$ sudo yum install git-daemon
需要注意的是,由 Fedora 19 中 git-daemon 的 RPM 包存在问题,详见 https://bugzilla.redhat.com/show_bug.cgi?id=980574。因此,如果要在 Fedora 19 中启动 git-daemon 的服务以配合 Gitolite,那么需要作如下的修改:
$ mv /usr/lib/systemd/system/git.service /usr/lib/systemd/system/git@.service
随后将它按如下方式修改:
$ cat /usr/lib/systemd/system/git@.service
[Unit]
Description=Git Repositories Server Daemon
Documentation=man:git-daemon(1)
Wants=git.socket

[Service]
User=nobody
ExecStart=-/usr/libexec/git-core/git-daemon --base-path=/var/lib/gitolite/repositories --export-all --user-path=public_git --syslog --inetd --verbose
StandardInput=socket
由该段代码可以看到,Fedora 19 中 git-daemon 包并没有创建新的用户组,而是以 nobody 用户启动了系统服务。但是,Gitolite 的目录则是赋予了 gitolite 组:
$ ls -ald /var/lib/gitolite
drwxr-xr-- 5 gitolite gitolite 4096 Jul 29 22:31 /var/lib/gitolite
因此,为了让 git-daemon 能够访问 Gitolite 的家目录,需要将 nobody 加入 gitolite 组并赋予 Gitolite 家目录以可读权限
$ sudo usermod -a -G gitolite nobody
$ sudo chmod g+r -R /var/lib/gitolite/repositories
现在启动 git-daemon 服务并让它随机器启动:
$ sudo systemctl start git.socket
$ sudo systemctl enable git.socket
git-daemon 默认端口是 9418,查看该端口是否打开
$ netstat -nat | grep 9418
如果没有打开的话,请编辑 /etc/sysconfig/iptables:
$ nano -w /etc/sysconfig/iptables
在 Drop 与 Reject 规则之前,增加一行
-A INPUT -p tcp -m conntrack --ctstate NEW -m tcp --dport 9418 -j ACCEPT
重启防火墙:
$ sudo service iptables restart
至此,git-daemon 已经知道 Gitolite 的工作目录了。不过,Gitolite 中的版本库还需要为 git-daemon 自动生成 git-daemon-export-ok 文件, 这便是下面的设置,仍需管理者运行:
$ cd 
$ cd gitolite-admin
$ nano -w conf/gitolite.conf
repo    gitolite-admin
        RW+     =   james

repo    testing
        RW+     =   @all
        R       =   daemon
$ git add conf/gitolite.conf
$ git commit -m 'git daemon support for Gitolite.'
$ git push origin master

'R=daemon' 这一行就是告诉 Gitolite 为 git-daemon 生成 git-daemon-export-ok 文件。

好了,现在让我们做简单的测试:

$ git clone git://serverIP/testing.git
一切正常。

 

Jul 20
我们知道 Gnu PG 是非常重要的加密工具,它可以帮助我们加密数据或者对使用的文件、文本进行签名以确保数据的真实性。Gnu PG 被广泛的应用于电子邮件传递、软件包打包等过程中。下面我们就来谈谈 Gnu PG 在 GIT 中的使用。
 
1、标签、提交等的签名
通常提交源码时,我们直接采用
$ git commit -m 'something added.'
对于这种提交方式,我们无法确认是否本人做了这些修改。为了防止这种情况发生,我们可以尝试
$ git commit -S <gpg-key-id> -m 'something added.' 
其中 <gpg-key-id> 就是用户的Gnu PG 的密匙ID。该命令执行时会提示用户输入该 <gpg-key-id> 的口令,随后对这次提交做了 GPG 签名,如此一来便可确信是本人做了这次递交。类似地情况也可运用到标签上来,例如
$ git tag v1.0 -s -u <gpg-key-id> -m 'tagged with important information.'
如果觉得每次手动指定 Gnu PG 密匙 ID 比较麻烦,可以通过下述方式自动指定
$ git config --global user.signingkey <gpg-key-id>
 
2、push/pull的口令缓存
我们在将版本库的修改同步到远程版本库时,通常需要输入账户与口令。如果版本库中有多次提交需要同步,那么账户、口令就需要输入多次。早期 GIT 版本库同步时,由于没有提供密码的缓存机制,为了免去重复输入账户名与口令,通常直接将密码明文的保存在.git/config文件之中。例如,
$ git remote add origin https://<username>:<password>@github.com/someone/repo
其中 <username> 与 <password> 就是账户与密码。很明显,以明文方式存储隐私信息是非常不可取的。因此新版的 GIT 中提供了新的办法,大体来说有两种:一、利用 git-credential;二、利用 ~/.netrc 或者 ~/.authinfo 等文件实现自动读取隐私信息。
 
credential 机制有点类似于 Gnu PG 代理,它能够缓存用户的密码,例如
$ git config --global credential.helper 'cache --timeout=3600'
如此设置之后,当我们输入完账户密码之后,GIT 就会缓存密码1个小时。实际上,credential 的机制还有好几个,有兴趣的朋友请自行阅读文档
$ man git-credential
 
另一方面,GIT 利用 ~/.netrc 等文件存取账户名与密码的方式与 ftp 的工作方式类似。例如,
$ cat ~/.netrc
machine github.com
  multilinetoken <hashnumber>
  login <username> password <password>
随后,我们便可以执行
$ git push origin master
此时 GIT 会自动读取 ~/.netrc 中账户信息将本地版本库修改同步远程端 GIT 服务器。这样一来,隐私信息的安全完全有赖于 ~/.netrc 文件权限了,通常,会对该文件的权限作如下设置
$ chmod 600 ~/.netrc
 
不过即使是这样,我们可能还是觉得安全性不够。例如用户登录电脑后有事需要离开一段时间,但没有及时开启密码屏保,这就难保 ~/.netrc 的信息不被泄漏。下面就来谈谈进一步的保密措施,那就是用 Gnu PG 加密 ~/.netrc 文件
$ gpg -o ~/.netrc.gpg -er <gpg-key-id> ~/.netrc
随后再对 GIT 做如下配置
$ git config --global credential.helper 'netrc -f ~/.netrc.gpg -d'
为了让 GIT 支持如上的修改,还需要开启 git-credential-netrc。在 Fedora 19 中,该文件位于 /usr/share/doc/git-<version>/contrib/credential/netrc 目录之中。需要将它置于 /usr/libexec/git-core 目录之下,并增加可执行权限
$ sudo cp /usr/share/doc/git-<version>/contrib/credential/netrc/git-credential-netrc /usr/libexec/git-core
$ sudo chmod +x /usr/libexec/git-core/git-credential-netrc
当然,用户也可以试试我的 Fedora 源中重新打包的 GIT。好了,现在再试试远程版本库的同步吧。
 
用 Gnu PG 加密 ~/.netrc 有个问题需要指出,那就是原先 ~/.netrc 的功能并不能被完全拓展到 ~/.netrc.gpg。换而言之,当我们执行 ftp 命令时,~/.netrc.gpg 并不能被解析。因此,我们需要适当分离 ~/.netrc 中内容以确保各个程序能够合理的工作。
 
3、版本库中文件的自动加密/解密
 
通常版本库中不太可能有什么隐私信息,不过随着很多人将 Linux 配置文件制成 GIT 版本库,隐私问题也就随之而来了。有些配置文件,例如 SSH 的私匙文件,再如 Emacs 的 GNUS 与 ERC 等,它们的密码有一部分只能以明文的方式保存的。如果将它们的配置发布到公开的 GIT 版本库中显然不是很合适。那么有没有办法将这些隐私文件以加密的形式保存到远程版本库,但本地工作目录中则仍以解密形式存在呢?答案仍是使用 GPG,主要是利用 GIT 的 filter 机制来配合 GPG 的使用。具体来说是这样的:
$ git config --local filter."gpg".smudge 'gpg -d --batch --no-tty -r <gpg-key-id>'
$ git config --local filter."gpg".clean 'clean = gpg -ea -q --batch --no-tty -r <gpg-key-id>'
$ git config --local diff."gpg".textconv decrypt
接着将需要将解密的文件名列表加入.gitattributes或者.git/info/attributes,例如
/path/to/id_rsa filter=gpg diff=gpg
/path/to/gnus.el filter=gpg diff=gpg
现在你可以尝试将id_rsa、gnus.el加入版本库了,当它们被加入到版本库时,GIT会利用 clean 所指定的方式进行加密。而当它被检出时,GIT 会调用 smudge 进行解密。需要注意的是,整个过程需要预先运行 gpg-agent。
 
用 GIT 管理配置文件的过程中,可能还会碰到一个特殊的文件需要加密,那就是 GIT 本身的配置文件。这个需求仍然与登录密码相关,最直接的例子就是 Github 的 token。很明显,直接加密整个 ~/.gitconfig 文件并不是很好的选择。这里提供一种方法,可以将需要加密的那部分配置分离出来。具体来说就是在 GIT 的配置文件中增加include部分,例如
$ git config --global include.path '~/.gitrepo'
并在 ~/.gitrepo 中加入与隐私相关的部分:
$ nano -w ~/.gitrepo
[github]
      username = <username>
      token = <hashnumber>
至于如何将 ~/.gitconfig、~/.gitrepo 纳入我们的配置文件版本库,这里就不多说。最后就是加密~/.gitrepo文件,这个非常简单。例如,~/.gitrepo 在版本库中的名字gitrepo,下面只需要再在.gitattributes中增加一行
/path/to/gitrepo filter=gpg diff=gpg

 

以上就是关于 GPG 与 GIT 的一些应用,希望对大家有用。