作者:志磊先生 <talebook@gmail.com>
概述:
我们使用三个文件$GIT_DIR/hooks/update, $GIT_DIR/info/allowed_users,
$GIT_DIR/info/allowed_groups对GIT的push提交进行控制,进而来实现中心版本库服务器功能。
一、原理
根据相关文献[1],我们可以知道开发者client向版本库服务器push新版本的时候,会执行$GIT_DIR/hooks/中的相关脚本。其中,在每
个ref更新之前,假如$GIT_DIR/hooks/update脚本存在,并且可执行,则会以如下方式调用这个脚本:
$GIT_DIR/hooks/update refname sha1-old sha1-new
因此,可以使用特定脚本对每个client的更新进行过滤处理,以达到控制用户权限的目的。
文献[2]也对本文使用的方案作了简要叙述。
二、适用条件
1、总是使用fast-forward[3]快速提交。例如,从不会执行'git push origin +dev:master'[3]这类命令,并且不会跨
分支提交(如将abc分支提交到xyz分支)。
2、希望控制某些用户对某些分支的更新权利。
3、希望控制标签(tags)的创建/删除,只允许特定用户更新。
三、脚本内容
将以下脚本存储为$GIT_DIR/hook/update,并确定是可执行的。
-- >8 -- 脚本开始 -- >8 --
#!/bin/bash
#file: $GIT_DIR/hooks/update
umask 002
# If you are having trouble with this access control hook script
# you can try setting this to true. It will tell you exactly
# why a user is being allowed/denied access.
verbose=false
# Default shell globbing messes things up downstream
GLOBIGNORE=*
function grant {
$verbose && echo >&2 "-Grant- $1"
echo grant
exit 0
}
function deny {
$verbose && echo >&2 "-Deny- $1"
echo deny
exit 1
}
function info {
$verbose && echo >&2 "-Info- $1"
}
# Implement generic branch and tag policies.
# - Tags should not be updated once created.
# - Branches should only be fast-forwarded unless their pattern starts with
'+'
case "$1" in
refs/tags/*)
git rev-parse --verify -q "$1" &&
deny >/dev/null "You can't overwrite an existing tag"
;;
refs/heads/*)
# No rebasing or rewinding
if expr "$2" : '0*$' >/dev/null; then
info "The branch '$1' is new..."
else
# updating -- make sure it is a fast forward
mb=$(git-merge-base "$2" "$3")
case "$mb,$2" in
"$2,$mb") info "Update is fast-forward" ;;
*) noff=y; info "This is not a fast-forward update.";;
esac
fi
;;
*)
deny >/dev/null \
"Branch is not under refs/heads or refs/tags. What are you trying to do?"
;;
esac
# Implement per-branch controls based on username
allowed_users_file=$GIT_DIR/info/allowed-users
username=$(id -u -n)
info "The user is: '$username'"
if test -f "$allowed_users_file"
then
rc=$(cat $allowed_users_file | grep -v '^#' | grep -v '^$' |
while read heads user_patterns
do
# does this rule apply to us?
head_pattern=${heads#+}
matchlen=$(expr "$1" : "${head_pattern#+}")
test "$matchlen" = ${#1} || continue
# if non-ff, $heads must be with the '+' prefix
test -n "$noff" &&
test "$head_pattern" = "$heads" && continue
info "Found matching head pattern: '$head_pattern'"
for user_pattern in $user_patterns; do
info "Checking user: '$username' against pattern: '$user_pattern'"
matchlen=$(expr "$username" : "$user_pattern")
if test "$matchlen" = "${#username}"
then
grant "Allowing user: '$username' with pattern: '$user_pattern'"
fi
done
deny "The user is not in the access list for this branch"
done
)
case "$rc" in
grant) grant >/dev/null "Granting access based on $allowed_users_file" ;;
deny) deny >/dev/null "Denying access based on $allowed_users_file" ;;
*) ;;
esac
fi
allowed_groups_file=$GIT_DIR/info/allowed-groups
groups=$(id -G -n)
info "The user belongs to the following groups:"
info "'$groups'"
if test -f "$allowed_groups_file"
then
rc=$(cat $allowed_groups_file | grep -v '^#' | grep -v '^$' |
while read heads group_patterns
do
# does this rule apply to us?
head_pattern=${heads#+}
matchlen=$(expr "$1" : "${head_pattern#+}")
test "$matchlen" = ${#1} || continue
# if non-ff, $heads must be with the '+' prefix
test -n "$noff" &&
test "$head_pattern" = "$heads" && continue
info "Found matching head pattern: '$head_pattern'"
for group_pattern in $group_patterns; do
for groupname in $groups; do
info "Checking group: '$groupname' against pattern:
'$group_pattern'"
matchlen=$(expr "$groupname" : "$group_pattern")
if test "$matchlen" = "${#groupname}"
then
grant "Allowing group: '$groupname' with pattern:
'$group_pattern'"
fi
done
done
deny "None of the user's groups are in the access list for this branch"
done
)
case "$rc" in
grant) grant >/dev/null "Granting access based on $allowed_groups_file" ;;
deny) deny >/dev/null "Denying access based on $allowed_groups_file" ;;
*) ;;
esac
fi
deny >/dev/null "There are no more rules to check. Denying access"
-- >8 -- 脚本结束 -- >8 --
四、配置文件
1、文件配置指南
这个脚本从两个文件中读取配置文件:
$GIT_DIR/info/allowed_users
$GIT_DIR/info/allowed_groups
这两个文件描述了那些用户/用户组对于分支/标签具有更新权限,并且使用相同的格式:
refs/heads/master junio jack
+refs/heads/pu junio
refs/heads/cogito$ pasky
refs/heads/bw/.* linus
refs/heads/tmp/.* .*
refs/tags/v[0-9].* junio
字符的匹配使用正则表达式规则,有多用户时请使用空格将用户名隔开。分支/标签的匹配是按顺序由上至下进行的,请确保出现交集
时,将大范围的匹配规则放置于下方。
对于如上示例,jack将能够更新'master'分支;junio能够更新'master'和'pu',并且创建相关的标签;linus则能够创建/更新
bw/abc, bw/xyz等分支;而任何人都可以创建/更新 tmp/xxx分支。
而pu分支的行首的'+'号,则代表了junio能够对pu分支进行non-fast-forward
2、系统配置指南
多数情况下,我们需要建立一个用户组(起名为 git ),并且将相关的开发者账户添加为该组成员。
当我们导出一个公共版本库后,将git目录的所有者设置为boss:git,其中'boss'是项目负责人的账户名。并且设置组用户有写权限,这
样就能够让同组的开发者对这个目录有写入/更新权利。以命令的形式来描述,则如下:
$ pwd
/home/project.git
$ cd ..
$ chown boss:git project.git -R
$ chmod g+w project.git -R
五、实例
1、配置信息
$ pwd
/home/cvs.git
$ ls -l
总计 52K
drwxrwxr-x 2 tale git 4.0K 08-12 00:03 branches/
-rw-rw-r-- 1 tale git 5 08-12 00:03 COMMIT_EDITMSG
-rw-rw-r-- 1 tale git 92 08-12 00:03 config
-rw-rw-r-- 1 tale git 73 08-12 00:03 description
-rw-rw-r-- 1 tale git 23 08-12 00:03 HEAD
drwxrwxr-x 2 tale git 4.0K 08-12 15:48 hooks/
-rw-rw-r-- 1 tale git 320 08-12 00:03 index
drwxrwxr-x 2 tale git 4.0K 08-12 15:47 info/
drwxrwxr-x 3 tale git 4.0K 08-12 00:03 logs/
drwxrwxr-x 52 tale git 4.0K 08-12 15:38 objects/
drwxrwxr-x 4 tale git 4.0K 08-12 00:03 refs/
# 确保相关的账户已经添加进git组
$ grep git /etc/group
git:x:200:tale,wang,xiao
# 确保这个脚本是本文中提供的脚本,并且具有可执行权限
$ ls -l hooks/update
-r-xrwxr-x 1 tale git 3.8K 08-12 15:48 hooks/update*
# 查看一下当前的用户权限分配
$ cat info/allowed-users
+refs/heads/wang wang tale
+refs/heads/xiao xiao
+refs/heads/tale tale xiao
+refs/heads/master tale
+refs/heads/.* tale
+refs/tags/.* tale
2、某客户端的操作
# 先看看目前是哪个用户(显然,是一个无关的用户)
$id
uid=0(root) gid=0(root) groups=0(root)
# 我们使用wang用户与服务器交流
$git clone wang@222.20.103.115:/home/cvs.git
Initialized empty Git repository in /root/cvs/.git/
wang@222.20.103.115's password:
Receiving objects: 100% (40/40), done.
remote: Counting objects: 40, done.
remote: Compressing objects: 100% (29/29), done.
remote: Total 40 (delta 9), reused 0 (delta 0)
Resolving deltas: 100% (9/9), done.
# 目前的开发进度如下:
$cd cvs/
$git show-branch -a
* [master] root 2
! [origin/HEAD] root 2
! [origin/master] root 2
! [origin/tale] Merge branch 'master' into tale
! [origin/wang] root 2
! [origin/wang2] test
------
*++ + [master] root 2
*++ + [master^] jfakfjklasjf
--- - [master~2] Merge branch 'master' into wang
*++ + [master~3] terer
*++ + [master~4] wang
- [origin/tale] Merge branch 'master' into tale
*++++ [master~2^2] 123
*++++ [master~5] ter
*+++++ [origin/wang2] test
# 从客户本人的分支上开始工作
$git checkout -b local-branch origin/wang
Branch local-branch set up to track remote branch wang from origin.
Switched to a new branch 'local-branch'
22:05:55 cvs $echo "nice to meet you" > nice
22:06:14 cvs $git add nice
22:07:02 cvs $git commit -a -m "nice to meet you"
[local-branch cd9da06] nice to meet you
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 nice
# 将工作进度提交到服务器上
$git push origin local-branch:wang
wang@222.20.103.115's password:
Counting objects: 4, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 297 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
To wang@222.20.103.115:/home/cvs.git
27b7142..cd9da06 local-branch -> wang
# 假如提交到别人的分支上,则会提示失败
$git push origin local-branch:wang2
wang@222.20.103.115's password:
Total 0 (delta 0), reused 0 (delta 0)
error: hooks/update exited with error code 1
error: hook declined to update refs/heads/wang2
To wang@222.20.103.115:/home/cvs.git
! [remote rejected] local-branch -> wang2 (hook declined)
error: failed to push some refs to 'wang@222.20.103.115:/home/cvs.git'
六、总结
hooks/update脚本能够提供简陋的用户控制,满足小团队的开发需求。但是,它也有一个很大的缺点:所有控制的用户均是服务器上真
实存在的用户。这会导致两个问题,其一是这些用户可能会登录服务器、并且可能会出现误操作等,造成数据损失(可以将用户的登录
shell设置为/bin/git-shell来解决);其二是随着团队规模的增大,需要在服务器上建立大量的用户。
针对以上缺点,可以使用其他的开源方案(如gitosis[4])作为解决办法。
参考:
[1]Junio C Hamano and Carl Baldwin, control access to branches.
http://www.kernel.org/pub/software/scm/git/docs/howto/update-hook-
example.txt
[2]Git 中文教程 - 管理版本库 - CVS 模式
http://www.linuxsir.org/main/doc/git/gittutorcn.htm
[3]Git manual book: git-push (1)
[4]在 Gentoo 上部署 Git + Gitosis 服务器的笔记
http://py.thonic.org/2008/05/19/git-gitosis-gentoo/


