[Git]真正回滚已上传的更新

首先,抛弃本地的修改应当用:

$ git reset --hard HEAD

使用 git 自身功能来回滚代码,取消上一次的修改应该用:

$ git revert sha1_of_commit

但要注意,虽然代码是实现了回滚,同时也会自动产生一条“回滚代码”的 log 。

有些时候,由于工作人员粗心,错误提交的内容完全无意义且占用空间颇大,就想真正 undo 掉错误的 commit,连历史记录都不想留。以下是我尝试的做法:

准备一份干净的客户端仓库

在客户端,下载服务器上的每个分支,并更新到最新状态。git branch -a查服务器上有哪些分支,挨个 checkout 过去再 pull。

然后回到有错误 commit 的分支,reset 掉错误的 commit:

$ git reset --hard 671475b1ce

这样在客户端就形成了一个已剔除掉错误 commit 的完整状态了。

从客户端仓库生成服务端仓库

这就是 git 分布式源代码管理的优势,客户端也是完整仓库,只是表现形式与服务端的不同罢了,两者之间可以转换。在本地仓库( repo.client )的上级目录中执行:

$ git clone --bare repo.client repo.git

然后把现在服务端仓库中的 hooks, info 目录和 config, description 两个文件拷贝到新生成的服务端仓库当中。

然后备份旧的服务端仓库,删掉用新生成服务端仓库替代,并调整相关文件权限。

基本上就可以了,小结

在客户端 pull:

$ git pull
From ssh://domain.tld/repo
 + 368b15f...671475b master     -> origin/master  (forced update)
Already up-to-date.

客户端仓库的 HEAD 自动被重置到了错误 commit 之前的。

我这种做法,只适用于用户比较小,可以停掉服务慢慢弄的情况,并且会丢失所有错误 commit 之后的改动,所以要慎用。最好的方法还是搞好用户培训,避免产生离谱的错误提交,一些小的错误还是直接用revert好了。

[Git]提交后自动发email

当然是通过hooks来实现了,对应post-receive,脚本也是已经随git-core安装就有了的:/usr/share/doc/git-core/contrib/hooks/post-receive-email,不过这个脚本用到了sendmail,我想好多机器上都没配这个东东吧,好在有好心人在这个脚本的基础上进行了完善,可以用msmtp发信了,看来我以前换用msmtp配mutt太正确了,另外msmtp也确实比sendmail小巧多了。

首先把刚才说到的脚本以及依赖文件functions一起下载到某地,加上执行属性(公用),然后ln到仓库的hooks目录下:

$ cd test.git/hooks/
$ mv post-receive post-receive.origin
$ ln -s ../../gittools/post-receive-email post-receive
$ ln -s ../../gittools/functions

然后修改仓库里的config文件,注意不是客户端的:

[hooks "post-receive-email"]
    mailinglist = list1@domain.tld, list2@domain.tld
    announcelist =
    envelopesender = mailsender@domain.tld
    sendmail = /usr/bin/msmtp

其中:

  • mailinglist 默认的收信人,留空就不发信了
  • announcelist 创建tag时发送从上次创建tag以来的汇总邮件的收信人,比如用版本号作为tag的时候这就生成了每个版本的changelog,留空则用mailinglist的值
  • envelopesender 发信人/账号,要和msmtp的发信账号对应起来
  • sendmail sendmail或其它发信程序的路径

还有个环境变量$USER_EMAIL,也是个发信人,不过是显示在邮件From:里的。如果要设置邮件里的From:,需要自己修改post-receive-email或在其之前执行的脚本,设置环境变量:

export USER_EMAIL="GIT <mailsender@domain.tld>"

这一版修改中作者还去掉了原来的emailprefix设置参数,固定为用仓库里description文件的内容加上[]替代。

最后,在git(git服务器运行用户)用户的HOME下放一个.msmtprc,配置发信认证信息:

defaults
    tls_trust_file /etc/ssl/certs/ca-certificates.crt
account gmail
    host smtp.gmail.com
    port 465
    auth on
    tls on
    tls_starttls off
account mailsender@domain.tld : gmail
#   from must be same as From: in mail, exclude Name, only mail address
#   Because maybe msmtp select account using from
    from mailsender@domain.tld
    user "mailsender@domain.tld"
    password my_passwd
account default : mailsender@domain.tld

现在就可以测试push,看自动发信是否正常了。如果提示functions语法错误,比如:

hooks/functions: 213: Syntax error: Bad for loop variable

可以把这两个脚本第一行的#!/bin/sh改为#!/bin/bash

注意msmtp调用的是提交动作所属用户的.msmtprc,从本机提交和远程ssh进来可能用的就不是一个.msmtprc了。

参考

Git起步

我的学习路径:git basic, http webdav, git-svn, gitosis, gitweb,下面是一些学习记录。

http webdav方式访问

ubuntu下安装非常简单,用apt装上git-core包即可。git仓库的外部访问,首先就是最熟悉的用http协议走apache上的webdav服务,方便啊。git自身的身份认证和权限分配弱了一些,不过可以借助apache实现用户分配,passwd文件直接用svn的也没问题。apache配置示例如下:

Alias /git "/big2/git"
<Location /git/test.git>
    DAV on
    AuthType Basic
    AuthName "Test git freely"
    AuthUserFile "/big2/svn/svnpasswd"
    #Require user Fwolf
    Require valid-user
</Location>

但这样远不如使用ssh/git协议访问仓库速度快和稳定,但更适合公开/对外发布。另外和svn的webdav方式不同,git webdav是一种dumb transport,不仅慢而且比较傻,只能进行简单的存、取文件操作,所以hooks是不起作用的(很多文章中说的提交后要在hooks/post-update里启用git-update-server-info,指的是git/ssh方式更新后,为webdav对外发布作准备)不会自动进行和git相关的其它处理。

最好用的还是gitosis,后面会提到。有些内容先以webdav方式下来记,使用的时候根据不同的方式转换用户角色即可。

创建服务端的仓库

其实和svn创建仓库也是比较类似的,找一空目录,比如叫test.git,执行:

$ git --bare init
$ sudo chown www-data:www-data * -R

客户端就可以下载了,但还有些问题:

$ git clone https://domain.tld/git/test.git gittest
Initialized empty Git repository in /home/fwolf/dev/gittest/.git/

error: server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt

首先遇到的是证书错误,git使用curl,所以证书的处理和svn也不一样,用firefox浏览git网站,在Page Info->View Certificate->Details中把网站的根证书(Issued By/root)导出为一个pem文件,然后将这个pem文件的内容添加到/etc/ssl/certs/ca-certificates.crt的末尾就可以了。接着遇到的是401身份验证错误:

error: The requested URL returned error: 401

git不像svn会自动提示用户名/密码,因为它用的是webdav的原生功能,所以需要在$HOME下创建文件.netrc(记得将权限修改为600):

machine domain.tld
login Fwolf
password your_passwd

最后,还是仓库本身不完善的问题:

warning: remote HEAD refers to nonexistent ref, unable to checkout.

在服务器上仓库目录,执行$ git update-server-info并再次chown到www-data就行了。初次push得指明服务器上的origin对应本地master分支/branch:

$ git push origin master

而下载之前,还要设置下载后放到那个分支、与哪个分支的现有内容进行merge:

$ git config branch.master.remote origin
$ git config branch.master.merge master
$ git pull  # 注意如果服务器是新建的空repo,得随便push点东西才好pull

gitosis

还是通过apt安装,gitosis使用SSH key来认证用户,但用户不需要在主机上开账号,而是公用主机上的一个受限账号。首先创建这个账号(名称可变):

sudo adduser \
    --system \
    --shell /bin/sh \
    --gecos 'git user' \
    --group \
    --disabled-password \
    --home /big2/gitosis \
    git

然后生成自己用户的ssh密钥,可以在git用户的$HOME下操作:

ssh-keygen -t rsa

不要密码,可以起名叫git_rsa[.pub],然后用公钥来初始化gitosis的目录:

$ sudo -H -u git gitosis-init < /home/fwolf/.ssh/git_rsa.pub 
Initialized empty Git repository in /big2/gitosis/repositories/gitosis-admin.git/
Reinitialized existing Git repository in /big2/gitosis/repositories/gitosis-admin.git/

可以看出gitosis自己也在用git仓库的形式存储东西,传递公钥进去是为了配置自动登录,将公钥写入git用户的$HOME/.ssh/authorized_keys。接下来就启用gitosis仓库的post-update hook:

$ sudo chmod 755 repositories/gitosis-admin.git/hooks/post-update

其实用apt安装的gitosis,这个post-update早已指向其它文件并具有x属性了。

一点提示:把gitosis的repositories和自己原先的git repo目录弄到一起,以后方便,因为gitosis自己的管理也是要通过git来提交的,在客户端先修改$home/.ssh/config

Host git.domain.tld
Compression yes
IdentityFile ~/.ssh/git_rsa # 注意这个是私钥,不是pub公钥

然后从客户端clone gitosis的配置文档:

$ git clone git@git.domain.tld:gitosis-admin.git

注意实际上这个gitosis-admin.git应该是在git用户的$HOME下的repositories目录下,但这么写才能下载。clone得到一个配置文件gitosis.conf和目录keydir/,对了,git配置的修改、上传也是通过git提交,并且由于刚才设置的post-update hook,立即生效。所以现在就能够远程修改、提交配置了。下面是一份比较完整的自说明的gitosis.conf

# example: http://eagain.net/gitweb/?p=gitosis.git;a=blob;f=example.conf
[gitosis]

## Allow gitweb to show all known repositories. If you want gitweb,
## you need either this or a [repo foo] section for each repository
## you want visible in gitweb.
gitweb = no

[group gitosis-admin]
writable = gitosis-admin
# member is key file name, without (.pub)
members = fwolf

# user group
[group all]
# members = jdoe alice bob @anothergroup
members = fwolf
# this user group is writeable to ... repo, eg: test.git.
# writeable = foo bar baz/thud
writeable = test
#readonly = somerepo

[repo test]
## Allow gitweb to show this repository.
gitweb = yes
## Oneline description of the project, mostly for gitweb.
description = Fwolf's test git repository
## Owner of this repository. Used in gitweb list of projects.
owner = Fwolf
## Allow git-daemon to publish this repository.
#daemon = yes
daemon = no

平时就是为其它用户添加SSH key,然后用git@git.domain.tld:repo.git来连接服务器,gitosis会自动比对服务器上的pub key,判断出用户是谁。

gitweb

用apt安装,得到/usr/lib/cgi-bin/gitweb.cgi,由于cgi-bin已经由ubuntu设置好了,apache只要设置一下资源文件路径即可:

Alias /gitweb "/usr/share/gitweb"

然后修改/etc/gitweb.conf

# path to git projects (<project>.git)
$projectroot = "/big2/git";

$site_name = "Fwolf's Git Repositories";
@git_base_url_list = ("ssh://git\@git.domain.tld");

# directory to use for temp files
$git_temp = "/tmp";

# target of the home link on top of all pages
#$home_link = $my_uri || "/";

# html text to include at home page
$home_text = "indextext.html";

# file with project list; by default, simply scan the projectroot dir.
$projects_list = "/big2/gitosis/gitosis/projects.list";
# Avoid use guess repo's address and edit url manually
$strict_export = 1;

# stylesheet to use
$stylesheet = "/gitweb/gitweb.css";
# logo to use
$logo = "/gitweb/git-logo.png";
# the 'favicon'
$favicon = "/gitweb/git-favicon.png";

重启apache后就能够用http://git.domain.tld/cgi-bin/gitweb.cgi访问了。

git-svn混用

首先还要安装git-svn包,git和svn混用的好处就是既可以用git进行本地提交和灵活的分支,又能够用svn实现集中管理和分发,缺点我想可能就是svn的revision号在git里没有了,另外git有些太灵活,不是很好入门。

首先导入svn的现有项目到一个空目录:

$ git svn clone --username your-name -s https://domain.tld/svn/repo
# older versions of git: replace "-s" with "-Ttrunk -bbranches -ttags"

就这么简单,很多工作git-svn都代劳了,剩下就当正常的git目录使用即可,比如为他们指定git仓库的位置,并下载文件:

$ git config remote.origin.url https://domain.tld/git/test.git
$ git config remote.origin.fetch +refs/heads/*:refs/remotes/origin/*
$ git config branch.master.remote origin
$ git config branch.master.merge refs/heads/master
$ git checkout master   #回到master分支
$ git pull

向svn仓库提交:

$ git svn dcommit

从svn仓库接受更新:

$ git svn rebase

在git-svn的使用中还发现,如果在机器A上使用git-svn,但是在另外一台机器B上用纯git进行了push,那么B上的commit是不会被git-svn认出来并dcommit的,只有pull下来,然后再假commit一下才能让git-svn看到。

仓库地址变更

服务器上直接修改仓库目录名和apache设置,客户端上:

$ git config remote.origin.url https://domain.tld/git/testnew.git
$ git pull  # 这个必须要做,不然不能push
Merge made by recursive.
$ git push

git-svn方式下变更svn的地址

首先要修改所有commit中的地址:

$ git filter-branch --msg-filter 'sed "s/domain_old\/trunk/domain_new\/trunk/g"' $(cat .git/packed-refs | awk '// {print $2}' | grep -v 'pack-refs')

然后删掉缓存,修改地址,重新下载:

$ rm -rf .git/svn
# 编辑`.git/config`修改`[svn-remote "svn"]`的url。
$ git svn rebase (--all)

我还曾经遇到过这样的错误:

$ git svn rebase
Unable to determine upstream SVN information from working tree history

不太好处理,这里有些参考信息,我是在一个repo里pull另外一个仓库的内容了,照下面方式处理也不管用:

  • 从svn里重新clone出一份
  • git里创建个branch保存一下
  • git连接上服务器,pull
  • git checkout 到刚才创建的branch
  • git merge –no-ff master
  • git svn rebase/dcommit

效果不好,不仅产生大量的conflict,而且还是只能在新创建的branch上rebase/dcommit。大概是出现了两“棵”树,怎么也捏不到一起,最后只好重建仓库。

其它使用技巧/tips

  • 回上个版本 $ git reset --hard HEAD^
  • 得到一个文件5个版本以前的状态 $ git checkout HEAD~5 foo.c
  • 由于git是分布式的scm,所以$Id被彻底消灭掉了。
  • 不像svn,git能够记录文件的执行属性。
  • 为显示加上颜色 $ git config color.(branch|diff|interactive|status) auto,或者$ git config color.ui true
  • hooks不算仓库的内容,每个repo的hooks可以不同。
  • 当心svn的hooks,如果git里已经commit,却因为svn的hooks使git-svn的dcommit失败,很麻烦。
  • .gitignore里可以用config.php忽略文件,并且用!mt/config.php指明个别文件还是要归入scm管理的。
  • 从现有的客户端目录中生成仓库目录 $ git clone --bare dev_dir repo_dir 每个客户端都是一个完整备份,方便。
  • 用于ignore文件,.gitignore是要提交的版本,.git/info/exclude则是只在你本地生效的版本,不提交。
  • 仅给ssh登录的开发者push/pull的访问权限,将其shell设置为/usr/bin/git-shell(未测试).
  • info/allowed-users可以控制用户对branch/tag的访问权限(未测试)。
  • git --bare init --shared=group可以让仓库被同group的用户共享,不过有gitosis就不用这个了,我没测试。

参考

Update @ 2009-03-27

使用git-svn的时候,有时候会出现git和svn树不太同步的情况,在进行svn rebase的时候总是重新merge文件,这种现象多是由于以前没有正确rebase引起的,而rebase不成功的一种原因就是git的fast forward。简单理解一下,git是按照树形结构排下来的,如果遇到前后两个节点被判定为“重复”的,git就聪明的作了个fast forward,直接使用后者了。这在git里没事,因为都是一颗树,而对于svn就不行了,它还记录着要从前面那个节点开始呢,所以合并的时候就出现了两个源头的情况,自然会产生问题。所以大体上向svn更新的顺序应该是这样的:

  • git push # git更新
  • git pull –no-ff # 此处的–no-ff最为重要,平时可以不用,这里要用
  • git svn rebase # 准备提交到svn之前先和当前的树合并
  • git svn dcommit
  • git pull –no-ff
  • # 后面就是检查了,前面操作正常的话不会产生新的有效更新了
  • git push
  • git svn dcommit
  • git svn rebase
  • git pull
  • git push

附一张比较典型的出错后来回折腾的结果图: git-svn rebase dcommit error

参考 @ 2009-03-29

有了git track以后,新建要和服务器同步的branch:

$ git branch fwolf
Branch fwolf set up to track local branch refs/heads/master.

$ git track fwolf origin
tracking origin/fwolf

$ git checkout fwolf
Switched to branch "fwolf"

$ git push origin fwolf
Total 0 (delta 0), reused 0 (delta 0)
To git@domain.tld:repo.git
 * [new branch]      fwolf -> fwolf

而从其它客户端下载这个branch就简单多了:

$ git branch --track fwolf origin/fwolf
Branch fwolf set up to track remote branch refs/remotes/origin/fwolf.

掌握原理,不用git track也一样可以实现的。

Update @ 2009-04-18

MediaTemple使用的是CentOS,安装git也很简单:

$ rpm -Uvh http://mirror.centos.org/centos/5/os/i386/CentOS/python-iniparse-0.2.3-4.el5.noarch.rpm
$ rpm -Uvh http://mirror.centos.org/centos/5/os/i386/CentOS/yum-3.2.19-18.el5.centos.noarch.rpm http://mirror.centos.org/centos/5/os/i386/CentOS/yum-fastestmirror-1.1.16-13.el5.centos.noarch.rpm  # 版本号可能会有变化
$ rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-2.noarch.rpm
$ yum -y update
$ yum install git

参考:

Update @ 2009-04-27

升级到ubuntu 9.04 jaunty后,客户端push时会遇到错误:

$ git push
/usr/bin/gitosis-serve:5: UserWarning: Unbuilt egg for ClientCookie [unknown version] (/usr/lib/python2.6/dist-packages)
  from pkg_resources import load_entry_point
Traceback (most recent call last):
  File "/usr/bin/gitosis-serve", line 5, in <module>
    from pkg_resources import load_entry_point
  File "/usr/lib/python2.6/dist-packages/pkg_resources.py", line 2562, in <module>  
    working_set.require(__requires__)
  File "/usr/lib/python2.6/dist-packages/pkg_resources.py", line 626, in require
    needed = self.resolve(parse_requirements(requirements))
  File "/usr/lib/python2.6/dist-packages/pkg_resources.py", line 524, in resolve
    raise DistributionNotFound(req)  # XXX put more info here
pkg_resources.DistributionNotFound: gitosis==0.2
fatal: The remote end hung up unexpectedly

半天查不出原因,只是怀疑和python的版本有关系(2.5/2.6),因为gitosis在debian包中的文件,显示应该是对应python 2.5的,不过,试着自己用python(2.6)重新下载安装了一遍gitosis,好了:

	$ git clone git://eagain.net/gitosis
	$ cd gitosis
	$ sudo python setup.py install

不懂Python太吃亏了呀。另外aptitude reinstall gitosis是没用的。

Update @ 2009-05-15

Tsung那里学来一招,可以更改 Git 默认的信息存放目录 .git :

修改 Shell 變數 GIT_DIR => "export GIT_DIR=.test"

Update @ 2010-08-03

升级到 Ubuntu 10.04 Lucid 以后,gitosis 自带的 hook 文件 post-update,就是修改权限等等 commit 后,自动出发 gitosis 更新相关配置的钩子,原来 ln 到的 /usr/share/python-support/gitosis/gitosis-0.2-py2.5.egg/gitosis/templates/admin/hooks/post-update 无效了,更换成 /usr/share/pyshared/gitosis/templates/admin/hooks/post-update 就可以了,参见Bug报告。挺让人无语的bug,我折腾了半天还以为是权限设置出了什么问题呢。

[MT]主机上安装svn客户端

有了svn,就可以方便的获取一些程序的源码了,所以我要在MT主机上安装,注意只是客户端,并非架设svn服务器。

svn目前的最新版是1.4.4,我参考shunz的Apache+Subversion安装笔记,从http://www.axint.net/apache/apr/binaries/rpm/i386/下载了apr-1.2.8-1.i386.rpmapr-util-1.2.8-1.i386.rpm(新版svn需要apr版本0.9.7以上,而且是必需),可是rpm -U apr*.rpm的时候系统却提示依赖性错误:

warning: apr-1.2.8-1.i386.rpm: V3 DSA signature: NOKEY, key ID 751d7f27
error: Failed dependencies:
        libpq.so.3 is needed by apr-util-1.2.8-1.i386
        libapr-0.so.0 is needed by (installed) httpd-2.0.52-32.3.ent.centos4.i386
        libapr-0.so.0 is needed by (installed) mod_perl-1.99_16-4.5.i386
        libapr-0.so.0 is needed by (installed) subversion-1.1.4-2.ent.i386
        libapr-0.so.0 is needed by (installed) subversion-perl-1.1.4-2.ent.i386
        apr = 0.9.4-24.5.c4.2 is needed by (installed) apr-devel-0.9.4-24.5.c4.2.i386
        libaprutil-0.so.0 is needed by (installed) httpd-2.0.52-32.3.ent.centos4.i386
        libaprutil-0.so.0 is needed by (installed) mod_perl-1.99_16-4.5.i386
        libaprutil-0.so.0 is needed by (installed) subversion-1.1.4-2.ent.i386
        libaprutil-0.so.0 is needed by (installed) subversion-perl-1.1.4-2.ent.i386
        apr-util = 0.9.4-21 is needed by (installed) apr-util-devel-0.9.4-21.i386

(rpm和redhat系列不熟悉)看了半天才搞懂,rpm -U是升级,而系统中的现有apr 0.9.4版本被其他软件比如apache、mod_perl等依赖,升级过去可能就破坏依赖性关系了。如果rpm -i的话应该是安装新版本,不知道centos上不同的软件版本能否共存,考虑到合租服务器的稳定性,还是决定按照MT客服的建议,安装svn的旧版本1.3.2:

wget http://subversion.tigris.org/downloads/subversion-1.3.2.tar.gz
gunzip subversion-1.3.2.tar.gz
tar -xvf subversion-1.3.2.tar
cd subversion-1.3.2
./configure && make && make install

顺利完成,也可以下载其他形式的源码包,然后再配置svn到用户的chroot环境当中,大家就都能用了。

配置svn到chroot环境的时候需要的包很多,ldd就行,另外如果想checkout https形式的仓库,还要配置/dev下的随机数生成器:

# mknod /var/www/vhosts/chroot/dev/urandom c 1 9

不然会出错:

SSL negotiation failed: SSL disabled due to lack of entropy

基本可用了,不过checkout含有中文名称文件的仓库时,又会遇到如下错误:

svn: Can't recode string

基本可以确定是svn无法转换字符集的原因(参考),再进一步查找原因,发现竟然是centos的系统默认字符集不是utf8的,于是更改/etc/sysconfig/i18n,设定为如下内容:

LANG="en_US.UTF-8"
SUPPORTED="en_US.UTF-8:en_US:en"
SYSFONT="latarcyrheb-sun16"
#LANG="C"

重启服务器,root用户可以使用中文了,普通用户还不行,需要把/usr/lib/locale/en_US.utf8/配置到chroot环境中去,然后在普通用户的$HOME目录下的.bashrc或者.bash_profile文件中加入一句export LANG=en_US.UTF-8,重新登录后就可以啦,UTF8万岁!

仍然剩余一个诡异的问题,root用户无法使用https形式的仓库:

svn: SSL is not supported

注意普通用户是可以使用的,怎么回事?用whereis发现服务器上有两个svn文件:

# whereis svn
svn: /usr/bin/svn /usr/local/bin/svn /usr/share/man/man1/svn.1.gz

分别查看其版本:

# /usr/bin/svn --version
svn, version 1.1.4 (r13838)
   compiled Aug 21 2005, 20:56:55
...
* ra_dav : Module for accessing a repository via WebDAV (DeltaV) protocol.
  - handles 'http' schema
  - handles 'https' schema

# /usr/local/bin/svn --version
svn, version 1.3.2 (r19776)
   compiled Aug  9 2007, 16:51:43
...
* ra_dav : Module for accessing a repository via WebDAV (DeltaV) protocol.
  - handles 'http' scheme

合着我编译了半天都是徒劳啊,服务器上早就有了svn了,我明明记得是没有啊,难道是客服在装编译套件developer’s tools package时给装上了?可看了那个包列表里也没有啊?依然诡异,忙活半天还少编译了个SSL选项进来。

也许是MT客服给装编译套件的时候,为了自己用着方便,顺手放过来一个svn?谁还有新租的DV 3.0主机麻烦确认一下好了。

参考(部分是安装svn服务器的):

关于svn字符集错误以及centos字符集问题的参考:

带SSL编译svn的一些资料:

在Ubuntu上加装Trac

原本机器上就架着svn服务器,但以前是从windows平台下转过来的,所以配套的轻型项目管理工具一直是在用Mantis,相比之下,trac和svn的结合程度更高一些,所以装一个试用一下。

相关软件版本:Ubuntu 7.04, Apache 2.2.3, Python 2.5.1, Trac 0.10.4。

安装trac,创建仓库

安装trac

首先通过apt安装trac,由于trac需要python的一些包,所以根据本机python环境的不同,可能还需要安装一些附加的包,我的机器上是自动安装了如下软件包:

python-clearsilver python-setuptools python-subversion trac

默认的,trac的程序文件是安装在/usr/share/trac目录下,包括很多默认的样式、模板也都在这个目录下。

创建env目录

trac需要创建env目录,用于存储trac生成的页面、配置文件等,也就是存储“内容”的地方,把程序和程序生成的数据文件分开是个好习惯。在trac中,这个目录被称为environment

A Trac environment is the backend storage where Trac stores information like wiki pages,
tickets, reports, settings, etc. An environment is basically a directory that contains a    
human-readable configuration file and various other files and directories.

我创建的是/big2/trac/svntest的目录,对应svn里面的svntest仓库:

$ trac-admin /big2/trac/svntest initenv
Creating a new Trac environment at /big2/trac/svntest

Trac will first ask a few questions about your environment 
in order to initalize and prepare the project database.

 Please enter the name of your project.
 This name will be used in page titles and descriptions.

Project Name [My Project]> SvnTest

 Please specify the connection string for the database to use.
 By default, a local SQLite database is created in the environment 
 directory. It is also possible to use an already existing 
 PostgreSQL database (check the Trac documentation for the exact 
 connection string syntax).

Database connection string [sqlite:db/trac.db]> 

 Please specify the type of version control system,
 By default, it will be svn.

 If you don't want to use Trac with version control integration, 
 choose the default here and don't specify a repository directory. 
 in the next question.

Repository type [svn]> 

 Please specify the absolute path to the version control 
 repository, or leave it blank to use Trac without a repository.
 You can also set the repository location later.

Path to repository [/path/to/repos]> https://somewhere.fwolf.com/svn/svntest

 Please enter location of Trac page templates.
 Default is the location of the site-wide templates installed with Trac.

Templates directory [/usr/share/trac/templates]> 

Creating and Initializing Project
 Installing default wiki pages
 /usr/share/trac/wiki-default/RecentChanges => RecentChanges
 /usr/share/trac/wiki-default/CamelCase => CamelCase
 /usr/share/trac/wiki-default/InterMapTxt => InterMapTxt
 /usr/share/trac/wiki-default/InterTrac => InterTrac
 /usr/share/trac/wiki-default/InterWiki => InterWiki
 /usr/share/trac/wiki-default/TitleIndex => TitleIndex
 /usr/share/trac/wiki-default/SandBox => SandBox
 /usr/share/trac/wiki-default/TracInterfaceCustomization => TracInterfaceCustomization
 /usr/share/trac/wiki-default/TracAccessibility => TracAccessibility
 /usr/share/trac/wiki-default/TracAdmin => TracAdmin
 /usr/share/trac/wiki-default/TracBackup => TracBackup
 /usr/share/trac/wiki-default/TracBrowser => TracBrowser
 /usr/share/trac/wiki-default/TracCgi => TracCgi
 /usr/share/trac/wiki-default/TracChangeset => TracChangeset
 /usr/share/trac/wiki-default/TracEnvironment => TracEnvironment
 /usr/share/trac/wiki-default/TracFastCgi => TracFastCgi
 /usr/share/trac/wiki-default/TracGuide => TracGuide
 /usr/share/trac/wiki-default/TracImport => TracImport
 /usr/share/trac/wiki-default/TracIni => TracIni
 /usr/share/trac/wiki-default/TracInstall => TracInstall
 /usr/share/trac/wiki-default/TracModPython => TracModPython
 /usr/share/trac/wiki-default/TracLinks => TracLinks
 /usr/share/trac/wiki-default/TracLogging => TracLogging
 /usr/share/trac/wiki-default/TracTicketsCustomFields => TracTicketsCustomFields
 /usr/share/trac/wiki-default/TracNotification => TracNotification
 /usr/share/trac/wiki-default/TracPermissions => TracPermissions
 /usr/share/trac/wiki-default/TracPlugins => TracPlugins
 /usr/share/trac/wiki-default/TracQuery => TracQuery
 /usr/share/trac/wiki-default/TracReports => TracReports
 /usr/share/trac/wiki-default/TracRevisionLog => TracRevisionLog
 /usr/share/trac/wiki-default/TracRoadmap => TracRoadmap
 /usr/share/trac/wiki-default/TracRss => TracRss
 /usr/share/trac/wiki-default/TracSearch => TracSearch
 /usr/share/trac/wiki-default/TracStandalone => TracStandalone
 /usr/share/trac/wiki-default/TracSupport => TracSupport
 /usr/share/trac/wiki-default/TracSyntaxColoring => TracSyntaxColoring
 /usr/share/trac/wiki-default/TracTickets => TracTickets
 /usr/share/trac/wiki-default/WikiRestructuredText => WikiRestructuredText
 /usr/share/trac/wiki-default/TracTimeline => TracTimeline
 /usr/share/trac/wiki-default/TracUnicode => TracUnicode
 /usr/share/trac/wiki-default/TracUpgrade => TracUpgrade
 /usr/share/trac/wiki-default/TracWiki => TracWiki
 /usr/share/trac/wiki-default/WikiDeletePage => WikiDeletePage
 /usr/share/trac/wiki-default/WikiFormatting => WikiFormatting
 /usr/share/trac/wiki-default/WikiHtml => WikiHtml
 /usr/share/trac/wiki-default/WikiMacros => WikiMacros
 /usr/share/trac/wiki-default/WikiNewPage => WikiNewPage
 /usr/share/trac/wiki-default/WikiPageNames => WikiPageNames
 /usr/share/trac/wiki-default/WikiProcessors => WikiProcessors
 /usr/share/trac/wiki-default/WikiRestructuredTextLinks => WikiRestructuredTextLinks
 /usr/share/trac/wiki-default/WikiStart => WikiStart

Warning:

You should install the SVN bindings

---------------------------------------------------------------------
Project environment for 'SvnTest' created.

You may now configure the environment by editing the file:

  /big2/trac/svntest/conf/trac.ini

If you'd like to take this new project environment for a test drive,
try running the Trac standalone web server `tracd`:

  tracd --port 8000 /big2/trac/svntest

Then point your browser to http://localhost:8000/svntest.
There you can also browse the documentation for your installed
version of Trac, including information on further setup (such as
deploying Trac to a real web server).

The latest documentation can also always be found on the project
website:

  http://trac.edgewall.org/

Congratulations!

提示信息比较多,真正需要用户输入的就那么几项:

  • Project Name [My Project]> SvnTest 项目名称,帮助辨识而已。
  • Database connection string [sqlite:db/trac.db]> 数据库的连接串,使用的默认值(直接回车即可)。
  • Repository type [svn]> 仓库类型,使用默认值(svn),trac也可以连cvs等其他类型的仓库么?
  • Path to repository [/path/to/repos]> https://somewhere.fwolf.com/svn/svntest 仓库地址,风轻扬说只能连本机的仓库地址,可我输入的http仓库地址好像也能用。的确是只能用本机的路径,http形式的仓库地址会提示错误:

    TracError: https:/office.fwolf.com/svn/svntest does not appear to be a Subversion repository.
    

    直接修改配置文件/big2/trac/svntest/conf/trac.ini就可以了,里面的[trac]段,repository_dir设置为本机的svn仓库文件地址,比如/big2/svn/svntest,不用重启服务就生效了。

  • Templates directory [/usr/share/trac/templates]> 模板地址,用默认。

简单运行

至此,trac已经可以运行和访问了,不过类似本机的svn服务器,首先需要手工开启服务:

$ tracd --port 8000 /big2/trac/svntest

然后就能用地址http://localhost:8000/svntest访问了,如果错输为http://localhost:8000/,它还会提示用户当前有哪些项目可用(Available Projects)并给出链接地址。

把trac配置到apache上

每次都要手工启用trac服务肯定是麻烦的,所以还是像svn那样配置到apache上用着才方便,有几种方式可选:cgi方式和mod_python方式,另外还有fastcgi方式,不过fastcgi我不熟悉,就不介绍了,相关资料可以参考官方文档Trac with FastCGI

CGI方式

cgi程序设定

虽然cgi方式比其他方法要慢一些,还是介绍一下。首先就是在apache中配置cgi的执行程序,这里我采用比较简单的ScriptAlias方式,也可以吧trac.cgi复制到apache的cgi-bin目录下,或者ln过去(不过cgi-bin目录要开放FollowSymLinks选项才行:

ScriptAlias /trac /usr/share/trac/cgi-bin/trac.cgi

光能执行cgi程序还不够,trac还主要知道一些环境变量才能工作,主要是environment目录的位置,不然会提示错误:

EnvironmentError: The environment options "TRAC_ENV" or "TRAC_ENV_PARENT_DIR" or the mod_python options "TracEnv" or "TracEnvParentDir" are missing. Trac requires one of these options to locate the Trac environment(s).

根据trac用法的不同,这个环境变量的设置有两种方式,一种是trac只用于一个项目,那么只需要定义一个TRAC_ENV,指定env目录的地址即可;另外一种情况是trac用于多个项目,通常所有的env目录都是在同一个目录之下,所以定义TRAC_ENV_PARENT_DIR,指定env目录的上级目录:

<Location "/trac">
    # Select one of below Env according your usage of trac
    # Trac is for single environment
    #SetEnv TRAC_ENV "/big2/trac/svntest"
    # Trac is for multi environment
    SetEnv TRAC_ENV_PARENT_DIR "/big2/trac"
</Location>

这里我采用的是多env方式。

运行于apache下是env目录的权限(mod_python方式也适用,不再累述)

注意在cgi或者mod_python方式下,env目录的owner需要设置为web服务器的用户,在ubuntu上一般是www-data,不然会没有访问权限:

svntest: Error
(The user www-data requires read _and_ write permission to the database file /big2/trac/svntest/db/trac.db and the directory it is located in.)

mod_python方式

安装python支持

mod_python方式不仅比cgi方式速度更快,而且可以借助apache的强大功能实现一些tracd(简单运行方式)无法实现的功能,也是我个人比较喜欢的方式。python基本上是所有linux平台下的必备软件,接下来就是要安装apache的python模块libapache2-mod-python,然后在apache的conf文件中装载python模块,在ubuntu或者debian中则有更方便的做法:

a2enmod mod_python

想测试python是否正常支持的话,可以配一个Location测试一下:

<Location /mpinfo>
    SetHandler mod_python
    PythonInterpreter main_interpreter
    PythonHandler mod_python.testhandler
</Location>

虽然不懂python,但只要能出现正常的信息页(类似phpinfo页)就说明python安装正常了。

配置env目录

和cgi方式类似,也分单项目和多项目的形式,并且多项目也是用环境变量TracEnvParentDir来实现的,注意这个TracEnvParentDir是python的设置(option),和cgi的环境变量TRAC_ENV_PARENT_DIR名称不同。单env目录的的配置如下:

<Location /trac/svntest>
    SetHandler mod_python
    PythonInterpreter main_interpreter
    PythonHandler trac.web.modpython_frontend
    PythonOption TracEnv /big2/trac/svntest
    PythonOption TracUriRoot /trac/svntest
</Location>

文档中说TracUriRoot在某些情况下不是必需的,除非你看到如下错误信息:

404 Not Found (No handler matched request to %s)

多env目录的配置如下,使用了TracEnvParentDir:

<Location /trac>
    SetHandler mod_python
    PythonInterpreter main_interpreter
    PythonHandler trac.web.modpython_frontend
    PythonOption TracEnvParentDir /big2/trac
    PythonOption TracUriRoot /trac
</Location>

这样配置,如果访问http://domain.com/trac,会列出现有env目录列表,如果想禁止这个列表,可以用LocationMatch替代Location:

<LocationMatch "/trac/.+">

这样env列表页就无法显示了。

其他

静态文件映射出来让apache处理

静态文件脱离cgi或python的处理,由apache直接负责会对速度有一些提升,用apache的别名把他们直接指向到实际文件路径就可以了,由于执行的顺序问题,需要注意Alias必须放在ScriptAlias的前面,就像这样:

Alias /trac/svntest/chrome/common /usr/share/trac/htdocs
ScriptAlias /trac /usr/share/trac/cgi-bin/trac.cgi

看一看页面引用的css地址:/trac/svntest/chrome/common/css/trac.css,可以看到图像的地址在Alias的处理范围之内(注意本例Alias地址中包含了仓库名称)。如果有多个env目录,逐一创建Alias很麻烦,可以在每个env目录的trac.ini文件[trac]段htdocs_location中设定一个公用的资源文件地址,比如:

[trac]
htdocs_location = /trac-htdocs

这样在apache中也只需要设定一个Alias就可以了:

Alias /trac-htdocs /usr/share/trac/htdocs

这样我们的css地址就变成了/trac-htdocs/css/trac.css。不过logo图像和icon图片的地址好像不受htdocs_location控制,转换不过来。

身份验证

trac的身份验证仍然是通过apache的机制,比如我们常用的、最简单的mod_auth_basic,比如一下配置:

<LocationMatch "/trac/[^/]+/login">
    AuthType Basic
    AuthName "Fwolf's Trac"
    AuthUserFile /big2/svn/svnpasswd
    Require valid-user
</LocationMatch>

在这里我直接使用了svn的passwd文件,用户就不用记多个密码了。这样设置只是在用户登录的时候验证身份,如果想让用户一进入trac就需要验证身份,把LocationMatch改成这样就行了:

<LocationMatch "/trac/.+">

不过logout好像就不能用了。另外这样配置trac的用户登录信息是存储在浏览器中的,在需要的时候会自动被调用,所以logout以后,如果不关闭浏览器就重新login,不用再输入密码就直接验证成功了。

如果想定制某些用户能访问某些env,trac好像没有这方面的权限控制功能(wiki的开放性贯彻的真彻底,不过在某些场合下确实需要),依然可以通过apache来实现:

<LocationMatch "/trac/(svntest|other_env)">
    ...
    require user user1 user2

trac只是能够使用通过apache验证的用户名罢了。

关于中文的一些问题

如果源码或上传文件内容乱码,可以看看conf/trac.ini里[trac]段选项default_charset,如果你svn仓库里的文件和上传文件编码类型都是utf-8的话,把这个设置为utf-8即可。

参考:

subversion中symbolic link的使用

nix操作系统中有link链接文件类型,硬链接(hard link)和符号链接(symbolic link)两种形式,硬链接只能在同一个分区内对文件使用,使用起来感觉就像是为文件创建了一份同步更新的副本,命令:ln link_target link_filename;符号链接则可用于文件和目录,纯粹就是一个指向链接目标的“指针”而已,并没有真正把内容复制过来,命令:ln -s link_target link_filename。实际应用中我个人的感觉是符号链接使用得更多一些,两种链接都比windows下的“快捷方式”要强大得多。

那么在svn的应用当中,如果两个项目共用一个库文件,使用符号链接文件就不需要两边分别更新了,不是很方便么?但是今天在一个项目中试用了一下,却不是我想像的那样。

我的情况是这样的,本来的项目中有一个dbupdater.php,现在转移到另外一个项目中了,但是在这个项目中还要继续使用,就产生了我刚才说的需要两边更新的问题,so想着采用link的方式,直接删除掉dbupdater.php,并创建了一个同名的符号链接文件覆盖了它,结果在svn commit的时候就出错了:

$ svn ci svn: Commit failed (details follow): svn: Entry ‘/home/fwolf/project/dbupdater.php’ has unexpectedly changed special status

原来,svn不仅跟踪记录了文件内容,连文件类型也记录了(nix下文件分为三种类型:file, dir, link),这样直接替换是不行的。如果硬是要用链接文件替代正常的文件,只有一种办法:先用svn rm删除掉原来的文件,提交,再用svn add添加链接文件,提交。不仅如此,我还测试了一下,发现这样提交到svn服务器上的文件内容居然是:

link /home/fwolf/svn_symbolic

也就是说,使用了符号链接之后,提交到服务器上去的并不是链接指向文件的内容,而是符号链接的“符号”而已,这样,如果在另外一台不同环境的客户端checkout出来,符号链接文件也许就不能正常工作了。

所以,虽然svn支持link类型的文件,但除了在少数情况下可以使用hard link,或者在项目内部文件之间使用symbolic link之外,尽量不要link项目之外的文件,因为结果并非我们所期待的那样。

参考: Problem replacing symbolic links Basic question ->Re: Basic question

另外再搭车介绍一下svn的“廉价复制”功能,svn创建tag或branch的机制和cvs不同,他基本上是通过创建一份拷贝的方式来创建tag或branch的,不过你不用担心这样会占用成倍的空间,因为svn的拷贝是一种“廉价复制”,只有当tag或branch中的文件被修改时才会真正的另存一份,这也是svn和cvs的一个主要区别,延伸阅读

PS: 现在海缆断了,查点资料那叫费劲啊,我容易么我?可spam怎么还是那么多呢?