Subscribe最新博客(更多

12 Apr 2012

Why Git is better than SVN

在版本控制系统的选型上,是选择Git还是SVN?

对于开源项目来说这不算问题。使用Git极大地提高了开发效率、扩大了开源项目的参与度、 增强了版本控制系统的安全性,选择Git早已是大势所趋。

但对于企业用户来说这个决心不太好下。部分原因是出于对Git的误解,部分原因是尚不了解 Git到底能给项目管理带来什么好处。希望本文能对您项目的版本控制系统选型提供帮助。

对SVN的迷信和对Git的误解

误解1:SVN只能检出(checkout)一个版本(revision)的代码,而Git却可以脱库!

这个误解是如此普遍,简直成了SVN在企业市场中封杀Git的尚方宝剑。其实稍微思考一下 这个谣言就很难传播。既然SVN能够读取授权访问的文件的每一个版本,那么就能够重组这些版本, 进而实现对版本库的完整复制。即SVN也可以脱库。

SVN脱库的工具SVN本身就提供: svnsync 。这个工具主要用于SVN的版本库镜像。 例如将版本库 http://host.name/svn/repo 脱库到本地的 dump 目录,命令如下:

$ svnadmin create dump
$ printf '#!/bin/sh\nexit 0\n' > dump/hooks/pre-revprop-change
$ chmod a+x dump/hooks/pre-revprop-change
$ svnsync init file://$(pwd)/dump http://host.name/svn/repo
$ svnsync sync file://$(pwd)/dump

如果使用 git-svn 则为SVN“脱库”更简便。

$ git svn clone -s http://host.name/svn/repo dump

有人认为SVN可以对目录授权,从而阻止对整个版本库进行脱库操作。 下面就来看看SVN的授权究竟是否可靠。

误解2:SVN能对目录进行精细授权,而Git太不安全

SVN的目录授权对管理员来说是灾难,管理负担相当重,在分支或里程碑众多的时候很难作对。 这是因为SVN的分支和里程碑(tags)本身就是一个目录(使用目录拷贝实现的)。

例如管理员为名为demo的SVN版本库授权。一个并不太复杂的主线(/trunk)授权如下:

[demo:/trunk]
@demo-admin = rw
@leaders = r

[demo:/trunk/doc]
@demo-dev = rw
@designers = rw

[demo:/trunk/src/apps]
@demo-dev = rw

[demo:/trunk/src/common]
@demo-dev = rw

[demo:/trunk/src/html]
@designers = rw

[demo:/trunk/src/secret]
* =
@demo-admin = rw
jiangxin = rw

如果项目创建了维护分支 /branches/1.x ,若和 /trunk 授权相同,则需要将上述授权在 /branches/1.x 下重建。需要在授权文件中再添加如下授权指令:

[demo:/branches/1.x]
@demo-admin = rw
@leaders = r

[demo:/branches/1.x/doc]
@demo-dev = rw
@designers = rw

[demo:/branches/1.x/src/apps]
@demo-dev = rw

[demo:/branches/1.x/src/common]
@demo-dev = rw

[demo:/branches/1.x/src/html]
@designers = rw

[demo:/branches/1.x/src/secret]
* =
@demo-admin = rw
jiangxin = rw

如果版本库的分支和里程碑越来越多,配置的工作量相当可观,稍有不慎不是授权文件格式破坏导致SVN无法工作, 就是造成开放授权。

我曾经写过SVN路径授权的补丁,并写了一款SVN版本库管理的开源软件 (参见 《pySvnManager手册》 ), 但想完美解决这个问题很难。我的一个设想是在SVN对分支和里程碑授权检查时缺省使用 /trunk 的授权,但这样的实现要求使用SVN严格遵循约定俗成的三个顶级目录的规范。

Git对于写操作可以精细到目录和分支级别(使用Gitolite作为服务器), 但作为分布式版本库控制系统,在设计上只能实现版本库量子化的读授权。 即某用户对整个版本库要么都能读,要么对整个版本库都不能读。

那么如何控制Git版本库的读授权呢?实际上Git可以通过子模组来实现细粒度的读授权。 即在项目需要精细授权的场合,将版本库拆分为多个Git版本库进行单独授权, 再使用子模组将多个版本库整合为一个。这个操作并不复杂,而且有助于实现项目的模块化。

误解3:Git能随意改变历史提交,这对于版本控制来说是不合适的

Git对历史提交的修改只对本地提交有意义。本地提交就像是和共享版本库间的缓冲。 在未将本地提交推送到远程共享版本库之前,开发者可以后悔。可以对不完整的提交说明进行补充, 可以移除错误的提交,可以压缩合并提交等。Git对提交历史灵活的操作是Git独有的功能, 是提交审核的必备工具。

对于已经推送到远程共享服务器的提交,Git就不能再像本地一样随意更改了。 因为推送到共享版本库的提交一旦被其他程序员获取,便扩散出去, 如覆水难收,难掩众人悠悠之口。所以Git更改历史提交只对本地有效,是安全的。

相比之下,SVN本地工作区和集中式版本库之间没有缓冲,一旦发现提交了错误内容, 或写了错误的提交说明,则无法更改,除非SVN管理员介入。 SVN也允许配置为可修改历史提交说明,但是一旦管理员放开此功能, 历史提交的提交说明有可能被批量、恶意更改,并且无法恢复。

误解4:SVN对中文支持更好,Git库中的中文目录和文件名会出现乱码

我也曾经这么认为,并在《Git权威指南》第3章中用了大量篇幅介绍中文支持的注意事项。 并推荐使用Cygwin作为首选客户端,以避免GBK字符集为跨平台开发的版本库引入乱码。

一个好消息是Windows下最常用的Git客户端 msysGit 也支持Unicode了。 使用最新版本(1.7.10)的 msysGit 无需设置任何Git配置变量, 版本库中的中文文件名、目录名、提交说明都使用Unicode编码。 配合使用Unicode版的TortoiseGit,Windows用户就不再为跨平台开发的字符集问题而伤脑筋了。

误解5:SVN的认证方式比Git丰富,比如可以实现LDAP认证

我为客户配置的Git支持HTTP、SSH协议,和Gitweb。其中HTTP协议、Gitweb都使用LDAP认证, 实现统一的口令管理。并且无论是HTTP协议、SSH协议,还是Gitweb都使用同一套Gitolite授权。

误解6:SVN更易上手,Git太难了

如果想把配置管理做好,SVN并不容易,否则 《SVN Book》 也不会有那么厚了。

  • 很多公司的SVN版本库没有遵照约定俗成的三个顶级目录。
  • 如何配置SVN悲观锁,以便更好地对二进制文件编辑进行协同。
  • 维护合并追踪的 svn:mergeinfo 属性,以便能够正确的分支合并。还要防止无此功能的客户端对其的破坏。
  • SVN如何正确的反删除,直接添加删除的文件是不对的。
  • 如何使用 svn:eol-style 属性,以便正确处理跨平台开发时的文件换行符问题。
  • SVN管理员如何对版本库进行整理,如撤出不当提交、修改错误的提交说明。
  • 版本库的安全性问题,如何做好版本库的备份。

Git的设计模型非常简单,理解了其设计思想,就可以很容易地掌握 git reset, git checkout, git rebase, git push, git pull 等命令。

误解7:程序员不喜欢命令行

谁说Git没有好的图形工具?SVN 有 TortoriseSVN,Git 同样有 TortoiseGit。 只不过Git的命令行太好用,使得图形操作显得笨拙。

至于Windows用做开发环境是否还有前途,看看火热的iOS、Android开发、和优雅的 MacBook 就知道了。

Git能做到,而SVN难以做到的事情

Git分支功能最为强大,分支管理能力让SVN望尘莫及

Git可以很容易地对比两个分支,知道一个分支中哪些提交尚未合并到另一分支,反之亦然。

  • 查看当前分支比other分支多了哪些提交:

      $ git log other..
    
  • 查看other分支比当前分支多了哪些提交:

      $ git log ..other
    

我不认为SVN的分支是真正的分支,因为分支最基本的提交隔离SVN就没能实现。 在SVN中一次提交可以同时更改主线(/trunk)和分支中的内容, 所以判断一个分支中哪些提交未合并到另外的分支,完全不能对SVN抱有希望。

Git可以实现更好的发布控制

针对同一个项目,Git可以设置不同层级的版本库(多版本库), 或者通过不同的分支(多分支)实现对发布的控制。

  • 设置只有发布管理员才有权限推送的版本库或者分支,用于稳定发布版本的维护。
  • 设置只有项目经理、模块管理员才有权推送的版本库或者分支,用用于整合测试。

隔离开发,提交审核

如何对团队中的新成员的开发进行审核呢?在Git服务器上可以实现用户自建分支和自建版本库的功能, 这样团队中的新成员既能将本地提交推送到服务器以对工作进行备份, 又能够方便团队中的其他成员对自己的提交进行审核。

审核新成员提交时,从其个人版本库或个人分支获取(fetch)提交,从提交说明、代码规范、编译测试 等多方面对提交逐一审核。审核通过执行 git merge 命令合并到开发主线中。

对合并更好的支持,更少的冲突,更好的冲突解决

因为Git基于对内容的追踪而非对文件名追踪,所以遇到一方或双方对文件名更改时, Git能够很好进行自动合并或提供工具辅助合并。而SVN遇到同样问题时会产生树冲突, 解决起来很麻烦。

Git的基于DAG(有向非环图)的设计比SVN的线性提交提供更好的合并追踪, 避免不必要的冲突,提高工作效率。这是开发者选择Git、抛弃SVN的重要理由。

保证已修复Bug不再重现

以为创建完毕里程碑标签(tag)便完成软件版本的发布是有风险的, 往往会由于之前的版本(维护版本)中的一些 Hotfix 提交没有合并到最新版本而造成已修复问题在新版本中重现。

Git分支和合并追踪可以解决这个问题。例如用 maint 分支跟踪最新的发行版, 当确定里程碑tag v1.6.4 为最新发行版时,在 maint 分支执行如下命令以切换到最新发行版:

$ git checkout maint
$ git merge --ff-only v1.6.4

如果合并成功,代表发行版 v1.6.4 包含了所有前一个发行版的提交。 反之说明前一个发行版某个或某些Hotfix提交尚未合并到最新发行版中。

版本库的安全性

SVN版本库安全性很差,是管理员头痛的问题。

  • SVN版本库服务器端历史数据被篡改,或者硬盘故障导致历史数据被篡改时, 客户端很难发现。管理员的备份也会被污染。
  • SVN作为集中式版本控制系统,存在单点故障的风险。备份版本库的任务非常繁重。

Git在这方面完胜SVN。首先Git是分布式版本控制系统,每个用户都相当于一份备份, 管理员无需为数据备份而担心。再有Git中包括提交、文件内容等都通过SHA1哈希保证数据的完整性, 任何恶意篡改历史数据都会被及时发现从而被挫败。

更多的十条喜欢Git的理由

更多的十条喜欢Git的理由,参见 《Git权威指南》 第11-21页。

  • 异地协同工作。
  • 现场版本控制。
  • 重写提交说明。
  • 无尽的后悔药。
  • 更好用的提交列表。
  • 更好的差异比较。
  • 工作进度保存。
  • 作为SVN前端实现移动办公。
  • 无处不在的分页器。
  • 快。

什么情况推荐使用SVN

SVN具有的悲观锁的功能,能够实现一个用户在编辑时对文件进行锁定,阻止多人同时编辑 一个文件。这一悲观锁的功能是 Git 所不具备的。对于以二进制文件 (Word文档、PPT演示稿) 为主的版本库,为避免多人同时编辑造成合并上的困难, 建议使用SVN做版本控制。

View Comments
29 Mar 2012

New hack for topgit: git-merge--no-edit

Git 1.7.10 对 git merge 提供了一个改进,而这个改进可能会带来兼容性问题, 会导致某些依赖 git merge 自动完成的工具受到影响。而 Topgit 就中招了。

Junio 的 一篇博客 专门对这个问题做了描述。简单的说就是Git 1.7.10 起, 执行 git merge 时,成功的合并不会使用默认的(没有意义的)提交说明自动提交, 而是会打开一个编辑器等待用户输入提交说明。

这个改动会使得调用 git merge 的工具在执行时被打断。Topgit 的 tg update 等命令即受此影响。

这个 GitHub上的提交 即用于解决此问题:

From: Jiang Xin <worldhello.net@gmail.com>
Subject: [PATCH] No stop to edit for the new merge behavior of git

In Git 1.7.10, Merge will stop and wait for a merge commit log. This
backward-incompatible improvement that will break topgit. To fix it,
just export GIT_MERGE_AUTOEDIT=no.

See: http://git-blame.blogspot.jp/2012/02/anticipating-git-1710.html

Signed-off-by: Jiang Xin <worldhello.net@gmail.com>

---
 tg.sh |    2 ++
 1 个文件被修改,插入 2 行(+)

diff --git a/tg.sh b/tg.sh
index 9082d88..b7661c2 100644
--- a/tg.sh
+++ b/tg.sh
@@ -430,6 +430,8 @@ get_temp()
 ## Initial setup

 set -e
+# suppress the merge log editor feature since git 1.7.10
+export GIT_MERGE_AUTOEDIT=no
 git_dir="$(git rev-parse --git-dir)"
 root_dir="$(git rev-parse --show-cdup)"; root_dir="${root_dir:-.}"
 # Make sure root_dir doesn't end with a trailing slash.
-- 
tg: (d279e29..) t/git-merge-no-edit (depends on: tgmaster)
View Comments
19 Mar 2012

Git测验(A卷)

在深圳做Git培训和项目管理软件实施,这是一份Git培训的测验题,通过率不高。因为不合格的还要重考,答案就不公布了。

单项选择题

  1. 如果提示提交内容为空、不能提交,则最为合适的处理方式是:_____

    a) 执行 git status 查看状态,再执行 git add 命令选择要提交的文件,然后提交。

    b) 执行 git commit --allow-empty ,允许空提交。

    c) 执行 git commit -a ,提交所有改动。

    d) 执行 git commit --amend 进行修补提交。

  2. 如果把项目中文件 hello.c 的内容破坏了,如何使其还原至原始版本? _____

    a) git reset -- hello.c

    b) git checkout HEAD -- hello.c

    c) git revert hello.c

    d) git update hello.c

  3. 修改的文档 meeting.doc 尚未提交,因为错误地执行了 git reset --hard 导致数据丢失。丢失的数据能找回么? _____

    a) 不能。执行硬重置使工作区文件被覆盖,导致数据丢失无法找回。

    b) 能。可以通过 git checkout HEAD@{1} -- meeting.doc 找回。

    c) 不确定。如果在重置前执行了 git add 命令将 meeting.doc 加入了暂存区,则可以在对象库中处于悬空状态的文件中找到。

    d) 不能。因为未提交所以无法找回。

  4. 仅将工作区中修改的文件添加到暂存区(新增文件不添加),以备提交,用什么命令标记最快? _____

    a) git add -A

    b) git add -p

    c) git add -i

    d) git add -u

  5. 下面哪一个命令 会改变提交历史? _____

    a) git reset --hard HEAD~1

    b) git checkout HEAD^^ .

    c) git rebase -i HEAD^^

    d) git commit --amend

  6. 我使用和其他人不一样的IDE软件,总是在目录下生成以 .xx 为后缀的临时文件。如何避免由于自己的误操作导致此类文件被添加到版本库中呢? _____

    a) 执行 git clean -f 删除临时性文件。

    b) 向版本库中添加一个 .gitignore 文件,其中包含一条内容为 *.xx 的记录。

    c) 在文件 .git/info/exclude 中添加一条内容为 *.xx 的记录。

    d) 更换另外一款IDE软件。

  7. 项目跨平台导致文件中的换行符不一致。其中有 Linux 格式换行符(0A),也有DOS格式换行符(0D 0A)。要如何避免此类情况呢? _____

    a) 修改 /etc/gitattributes 文件,在其中包含一条内容为 * text=auto 的设置。

    b) 执行命令 git config --global core.autocrlf true

    c) 执行命令 git config --global core.autocrlf input

    d) 向版本库中添加一个 .gitattributes 文件,在其中包含一条内容为 * text=auto 的设置。

  8. 下列对于版本库授权说法正确的是:_____

    a) 可以为分支或路径设置不同的写入权限,但不能设置不同的读取权限。

    b) 除管理员外,版本库的创建者都可以为自己创建的版本库授权。

    c) 只要通过授权后,便不能限制所推送的提交的署名作者,可以是任何人。

    d) 如果没有向版本库的写入权限,就一定没有读取权限。

  9. 取消服务器版本库中ID为 a2387 的提交,而且不能引起历史提交的变更,用什么操作? _____

    a) git rebase -i a2387^

    b) git checkout a2387^ -- .

    c) git revert a2387

    d) git reset --hard a2387^

  10. 从版本库中的历史提交中彻底移除ID为 a2387 的提交,用什么操作? _____

    a) git reset --hard a2387^

    b) git checkout a2387^ -- .

    c) git revert a2387

    d) git rebase --onto a2387^ a2387 HEAD

  11. 所有改动的文件都已加入暂存区,若希望将其中的 other.py 文件下次再提交,如何操作? _____

    a) git reset -- other.py

    b) git checkout -- other.py

    c) git checkout HEAD other.py

    d) git reset --hard -- other.py

  12. 若产品的版本号显示为 1.7.10.rc0-33-g9678d-dirty ,可以判断出此版本号是如何生成的么? _____

    a) git tag

    b) git describe --tags --always --dirty

    c) git name-rev HEAD

    d) git --version

  13. 关于 git clone 下面说法 错误 的是:_____

    a) 克隆时所有分支均被克隆,但只有HEAD指向的分支被检出。

    b) 可以通过 git clone --single-branch 命令实现只克隆一个分支。

    c) 克隆出的工作区中执行 git loggit statusgit checkoutgit commit 等操作不会去访问远程版本库。

    d) 克隆时只有远程版本库HEAD指向的分支被克隆。

  14. 关于删除分支 XX ,下列说法正确的是: _____

    a) 执行 git push origin :XX 来删除远程版本库的 XX 分支。

    b) 执行 git branch -D XX 删除分支,总是能成功。

    c) 远程版本库删除的分支,在执行 git fetch 时本地分支自动删除。

    d) 本地删除的分支,执行 git push 时,远程分支亦自动删除。

  15. 下面的操作中哪一个不能确认维护分支 maint 上所有的 bugfix 提交均已合并至当前分支 master 中。 _____

    a) git rev-list ..maint 的输出为空。

    b) 在 maint 分支成功地执行 git merge master

    c) git log ..maint 的输出为空。

    d) 新版本发布,在 maint 分支执行 git merge --ff-only master 成功。

  16. 一个图片文件 logo.png 冲突了,如何取出他人的版本。 _____

    a) git show :1:./logo.png > logo.png-theirs

    b) git show :2:./logo.png > logo.png-theirs

    c) git show :3:./logo.png > logo.png-theirs

    d) git show :0:./logo.png > logo.png-theirs

  17. 工作在特性分支,常常因为执行 git push 默认推送所有本地和远程共有分支,导致非当前分支报告 non-fast-forward 错误。如果设置只推送当前分支可避免此类问题。下面操作正确的是:_____

    a) git config --global push.default upstream

    b) git config --global pull.rebase true

    c) git config --global receive.denyDeletes true

    d) git config --global pager.status true

  18. 关于对象库(.git/objects)说法 错误 的是:_____

    a) 两个内容相同文件名不同的文件,在对象库中仅有一个拷贝。

    b) 对象库执行 git gc 操作后,reflog 会被清空导致其中记录的未跟踪提交及指向的文件被丢弃。

    c) 删除文件后,再通过添加相同文件找回,不会造成版本库的冗余。

    d) 对象库并非一直保持最优存储,而是通过周期性地执行 git gc 优化版本库。

  19. 关于子模组 错误 的说法是:_____

    a) 克隆父版本库,默认不会克隆子模组版本库。

    b) 子模组可以嵌套。执行 git submodule update --recursive 可对嵌套子模组进行更新。

    c) 子模组和父版本库的新提交,要先推送父版本库,后推送子模组。

    d) 子模组检出处于分离头指针状态(gitlink的指向),在子模组中工作需要手动切换分支。

  20. 当一个提交说明显示为 souce code refactor (fix #529) ,下面哪个说法是正确的? _____

    a) 这个提交只是代码重构,并未修复任何东西,因此没有改变版本库的提交历史。

    b) 这个提交修正了第529号提交,没有改变版本库的提交历史。

    c) 这个提交撤销了第529号提交,改变了版本库的提交历史。

    d) 这个提交和项目的缺陷跟踪平台(如Redmine)关联,并会更新相关问题的状态。


View Comments

我的书

联系方式