Friday, June 17, 2011

(旧文) SVN Practice


完全无 SCM 知识/经验

SCM Definition from wikipedia
Quote:
Software Configuration Management (SCM) is part of configuration management (CM). Roger Pressman (in his book) Software Engineering: A Practitioner's Approach, says that software configuration management (SCM) is a "set of activities designed to control change by identifying the work products that are likely to change, establishing relationships among them, defining mechanisms for managing different versions of these work products, controlling the changes imposed, and auditing and reporting on the changes made." In other words, SCM is a methodology to control and manage a software development project.
从上述引文最后一句我们知道: SCM 是用以控制和管理软件开发项目的一种方法学. 它本质上只完成一件事情 - 管理变更及变更间的关联与关系.
在公司的实际开发活动中, 我们使用了以下工具以完成 SCM:
  • Subversion: 源代码管理软件 - 从源代码层面上对变更进行管理 (lowest level).
  • Trac: 项目活动管理软件, 它提供以下主要功能
    • Tickets: Trac 扩展了像 Bugzilla 这样的软件缺陷 (bug) 跟踪系统中 "Bug" 的概念. 在 Trac 中一个 Ticket, 不单可以是一个 bug, 还可以是一项功能任务, 一次功能增强的需求. 总之, 所有具备 "创建/优先级/指派/处理/解决/关闭" 这样模式的生命周期的事件项 (issue item) 都可以通过 ticket 提供统一的管理 - Ticket 从软件开发中的事件/活动层面上对变更进行管理 (medium level).
    • Wiki: 具备协同创作/版本控制/交叉引用等功能的 wiki 为软件开发活动带来了极大的便利. 在实践中, 我们主要使用 wiki 完成如下任务: (highest level)
      • 生成/管理项目文档 (需求文档, 测试文档, Release Notes... 在文档中, 我们可以通过 Trac 的交叉引用功能在 tickets 和 changesets 之间轻松生成关联链接, 以便跟踪每个具体的事项都涉及哪些层面上的变动)
      • 生成/管理技术文档 (开发过程中针对具体技术的一些知识积累, 分享)
    • 其他: Trac 还提供各种其他功能协助我们管理项目的变更及其相互间的关系:
      • Timeline: 这里是我们所有项目开发活动的鸟瞰图, 它按时间顺序列出 Ticket 更新, Wiki 变动, 源代码提交等活动 - 我们从这里可以快速知道所有变更.
      • Browse Source: Trac 提供了方便的 Web 界面供我们浏览代码仓库中的文件. 我们最常用的功能是浏览一个 Changeset (变更集), 它可以列出某次提交中涉及的文件列表, 以及高亮显示每个文件的具体变动. 我们还可以用它来查看指定 revision 的文件等.
综上所述, 我们公司通过 Trac/Subversion 这两大工具, 将最高层的项目需求, 到中层的任务/缺陷, 以及最底层的源码所涉及的一切变更都管理起来了.
下面我们先就每天都要打交道的源代码管理软件 Subversion 进行了解, 看看在公司的日常开发中, 我们都用到 Subversion 的哪些方面.

用过 VSS

不少 Windows 下的开发人员对 VSS (Visual Source Safe) 并不陌生, 这是微软自己推出的卖钱的源代码管理软件; 而接触开源软件较多的人则可能更常听到 CVS 和 SVN (Subversion), 它们则都是免费的, 源代码开放的自由软件, 开源世界中大量的项目都是通过 CVS 或 SVN 进行源代码管理的.
SVN 是由 CVS 的原作者在总结 CVS 的长短之后的一个新的实现 (?). 我们下面着重讲 SVN. (@TODO, more refs)
SVN 和 VSS 都具备源代码管理软件基本功能, 诸如 check out, check in, diff, update. 而由于两者诞生的环境的不同, 使得 SVN 和 VSS 在 check out/modify/check in 这一主要功能上有不同的实现模式:
  • VSS: lock -> modify -> unlock
  • SVN/CVS: copy ->  modify -> merge
可以看到, VSS 在 check out 一份代码的时候就给代码加锁了,  直接 "断绝" 掉多人同时修改一个文件的情况; 而 SVN 则不是, check out 的代码仍然可以被多人同时修改, 各个改动间自动进行 merge, 如果遇到冲突 (例如两人同时改了同一行代码), 则需要开发人员自己进行交流, 最终手工 merge 彼此的变更.

VSS 和 SVN/CVS 在锁定和非锁定上的处理源自应用环境的不同. 
VSS 产生于微软这样的 close source 的企业, 而在这样的企业里, 开发软件时, 团队成员间通常就在一个办公室里, 文件的锁定/解锁的协调相对容易, 任务也许可以真的分配到一个人就在一个文件上工作; 但, 在开源世界里, 开发人员来自世界各地, 在世界的每个角落/不同时间里参与着项目的开发, 如果你今天在北京把一个文件锁住, 晚上没做完, 没提交, 然后睡觉去, 我们美国的同志们若也参与这个功能的开发, 那么在北京时间凌晨 2:00 的时候, 你尚在美梦中, 而那边已是早上 9:00 的工作点, 这样就没法对这个文件进行修改, 只能等到你呼呼睡醒, 北京时间 9:00 上班, 而美国那边已经是 16:00, 浪费大半天, 该下班了...

那么我们在公司里, 是不是其实应该很适合用 VSS 呢? 举个简单的例子就知道不行了 - g20 项目中我们有个 g20.js 的文件, 所有 ajax 相关的功能都在这个将近 6K 行代码的文件里完成, 我们的同学们经常需要同时并行着做两个以上的功能, 这使得我们不可能采用锁定的模式. 而 SVN 的拷贝-修改-合并模式并没有在现实中带来想像中的那么多 "冲突" - 只要功能拆解得当, 不会出现两个人同时修改同一个文件同一行的情况, 最常见的情况就是你在 200 行后加了个 function, 我在 300 行后编辑了一些条件判断.

关于两种模式的深入比较, 请参看 SVN 的官方文档.

用过 SVN

在实际使用 SVN 时, 我们需要养成一些良好的习惯, 它们有助于 PL/PM 更好地得到项目信息, 减少不必要的询问/确认这样的交流代价; 也让团队成员之间可以明确地知道彼此的开发活动情况.
  • 开工前先 update. 在开始我们一天的开发活动之前请记得 svn up 一下, 以取出代码库中最新的版本. 因为每天你的团队伙伴们和你一样都在代码树的不同指节上做着开发, 一天结束之后, 代码库里都会增加很多修改, 保持自己本地拷贝的新鲜度, 也许昨天存在的某个影响你的 bug, svn up 之后就已经修好了.
  • 提交前先 status/diff. 每次提交前务必确认自己这次修改所涉及的文件/代码行的变动是 *确切的, 必须的, 没有冗余的*. 我们可以用 svn status 列出所有被修改过的文件; 然后以 svn diff 逐个查看修改过的文件中的代码行变动 *是否确实目的明确的, 有意义的*, 我们有时会不小心加多一个空格, 敲多一个换行, 这在 diff 的时候同样会被列出来, 这些 "变动" 属于不必要的 "噪音", 是不应该被记录到源码库里的, 我们在发现这些 "噪音" 之后就应该把它们清除出去 - 手工恢复原状即可.
  • 细粒度, 经常地, 有目的地提交必要的修改. 我们每次修改通常都是有明确目的的, 为了新增一个功能, 为了修复一个 bug. 在对代码进行变动的时候, 我们应该保持完成一个目的之后就进行相应的提交的习惯, 要避免把好几件事情都做了 2 ~ 3 天之后再一口气提交这样的做法 (我们会遇到大量代码需要变动的情况, 例如重构, 这时要用 svn 的另外一个功能来完成: branch, 后面会对这个进行说明). 这样通常会使得你的提交过程痛苦一些, 因为这些天已经有了不少变动, 和你的代码产生冲突的机会也递增了. 另外, 这样会使得你的 check in message 变得不好写, 我们下面来说这一块.
  • 提交时一定要写 message. 所有源代码管理软件在提交时都提示 (而非强制) 你要写一段文本, 藉以对这些代码变动进行必要的说明. 我希望给大家建立一个强烈的概念 - 这样一段文本对项目管理非常非常重要, 千万不要偷懒不写或随便写! 这段文本的主要目的就一个: 说明我为什么做这些代码变动. 当每个 changeset 都有对应的 message 的时候, 作为项目的 PL/PM 就可以轻松地从 Trac 的 Timeline 上知道每个团队成员都在为什么目的做什么变动. 我们再用上  [1234] (changeset), #4321 (ticket) 这样的格式来利用 Trac 的交叉引用功能, 就能达到更好的效果 - PL/PM 可以通过 Timeline 知道哪些 Changesets 之间有关联, 哪些 Changeset 完成了哪些 tickets, PL/PM 可以不用来搔扰你, 打断你的思路就可以知道任务进展情况了; 再者, 写了 check in message, 也方便我们开发人员自己在必要的时候可以快速搜索/定位一些 changesets, 我们有时想回溯/确认某次修改时都做过什么事情, 是否因那次修改引入了某些新的 bug... 这时, 我们在每次 ci 时提交的 message 就会帮到我们, Trac 会对它们进行索引, 我们随时可以通过 Trac 的搜索, 快速找到可能的 Changesets; 对 PL/PM 来说, 这些 message 还有另外一个价值, 那就是在为客户写 Release Notes/Change Logs 的时候, 可以很容易地从这些 message 中整理出来, 而不用回头再搔扰开发人员, 多余地确认你做过的事情. (svn check in message 我们还有自己的约定, 这个另外专题讲述)
  • conflicts 出现时, 务必谨慎地, 细致地进行手工 merge/resolve. SVN 的 copy-modify-merge 模式通常不会带来什么 "麻烦", 我们做 svn update 的大部分时候都会看到自己刚改过的, 尚未提交的文件前头有个 G, 这表示 SVN 聪明地给你自动 mer[G]e 了别人的更新和你当前的改动. 当然, 我们始终会有遇到 [C]onflict 的时候. 但只要遵循了之前的细粒度, 少量的, 多次的提交习惯, 这冲突一般都在 < 20 行内, 很容易可以手工合并的. 遇到冲突时, 我们一定要要非常留心: 千万不要把别人辛勤劳动的结果给覆盖了. Conflict 是正常的, 可轻松解决的, 不存在别人的改动给自己带来任何麻烦的说法. 解决 conflict 很简单, 我们只需打开处于冲突状态中的文件, 找到类似这样的地方:然后做出你的技术判断, 看看到底是将两个块合并, 还是择其一而用之, 或是两部分都改动, 最终合并出一个结果. 通常这时候我们还需要和做了 revsion 2 变动的团队成员进行沟通, 确认他的改动的意图 (你也可以直接看 changeset 的 check in message ;), 确认这部分的合并方案.
    有人可能会好奇, 为什么 SVN 这时就这么笨了? 它为什么不直接合并两个修改部分? 其实, 能自动合并的部分, SVN 已经尽责了, 面对这种两个变动发生在同一处的情况, SVN 认为, 这时候该交给更聪明的人类来解决了 ;)
    在我们手工细致合并完毕之后, 记得 svn resolve 这个文件, 然后做 svn ci 即可. 
  • svn config. 我们看看一些常见的 svn 的相关配置.
    • EOL; (@TODO: Cheng help me with this part?)
    • ignore: 我们在 svn status 的时候, SVN 会把未加入版本控制的文件项列出来, 然后前面加上 ? 号. 当我们有很多像 .pyc, .tmp, .o 文件的时候, 这些会对我们查看全局的变动情况产生 "视觉噪音". 我们可以通过配置让 svn 无视它们的存在 - 编辑 ~/.subversion/config, 找到 global-ignores, 然后把你不想见到的非版本控制中的文件都列上去, 例如: global-ignores = *.swp *.o *.lo *.la *.pyc sess_* default_* (@TODO: how to windows?)
    • proxy server. svn 还可以配置代理服务器, 编辑 ~/.subversion/servers, 在最后的 [global] 部分里加入两行即可:
      http-proxy-host = www.your-proxy.com
      http-proxy-port = 8888
  • branch, merge (rollback)
    • branch: 我们在进行源代码管理的时候通常还会遇到这样的需求 - 一段时间内需要对原有代码进行大量的改动, 而且这些改动可能会让已有功能不能工作 - 例如重构. 当我们遇到这种情况的时候, 就需要利用 svn 的分支功能, 将现有代码拷贝一份出去进行单独的版本管理. 在那个分支上, 我们就可以进行大刀阔斧的改动, 而不必担心影响 trunk 上已有的, 甚至已经给了用户的代码的稳定性. 
    • merge: 我们在一个或多个 branch 上的变更, 到了一定时候, 是可以互相合并的, 包括 branch <-> branch, branch <-> trunk. 例如我们现在对 g20 代码的管理方式是, 到了一个版本, 我们就做一个 branch 出去 (例如, alpha-1.0, alpha-2.0 ...), 而 trunk 保持活跃的开发. 这些独立分出去的代码枝就拥有了自己的生命, 必要时, 我们可以在 branch 上为客户做一些小的修改, 满足一些他们的新需求, 然后再 merge 回 trunk 或者不 merge - 取决于具体情况.
      merge 除了做分支间的 "大动作" 的合并之外, 还可以做单个文件的 revision 之间的合并, 我们可以利用这个功能, 在提交了错误的 changeset 之后, 用 merge 来 undo 你的修改, 例如, revision 1234 是我提交错了的, 那么, 我们只需 svn merge -r 1234:1233 就可以 undo 了, 当然, 最后还需要 svn ci 一次 :)
  • use google code to practice: 要练习 svn 的各种命令, 可以用我们专门为学习 svn 创建的一个Google Code Project 来无风险地练练, 或者自己装上 subversion :)
  • GUI (小乌龟, esvn) (@TODO: Cheng help me with this?)
  • 常用简写/参数:
    • svn ci = svn commit 
    • svn st = svn status
    • svn up = svn update
    • --dry-run: 假装跑一次, 让我们知道什么文件会被更新. 我们在 svn ci, svn  up 等命令的时候可以加入 --dry-run 这个参数, 以便测试实际哪些文件会被动到. 

SVN Check In Message Conventions

 Svn check in format:
<Prefix>: <message goes here>.
The check in message should be short but straight forward for others to understand. We need to ensure that: other developer or the project lead know exactly what you did in this changeset once they read your message, and they don't have to ask you for further explaination. (@TODO: benifits)
Prefixies:
  • ADD (Add): adding directories, files
  • BRN (Branch): branching
  • CMT (Comment): comments added to code
  • DEL (Delete): removing directories, files
  • DOC (Document): document related changes
  • FIX (Fix): bug fixing
  • FMT (Format): Code formating changes 
  • INI (Initial): initial import of a project
  • MRG (Merge): merging from revision to revision, branch to trunk, etc.
  • REV (Revision): stable/small changes
  • WIP (Work In Progress): not yet finished changes, for example:
    • you need to check in your code before other developer could work on it for a feature;
    • you need to check in since it's 18:00, you would like to work on it tomorrow, or
    • you need to check in before making to many changes

How to Deal With System-specific Configuration Files

在一个项目的代码中,通常有数个配置文件也被置于版本控制之下。然而,这些文件的特点在于,其中有些配置项在每个系统上是不一样的,例如,每个开发人员 checkout 的目录就不一样,部署到外部服务器上也不一样,如果这个文件仍旧和其他代码文件一样对待,首先在 ci 时就得时时提醒开发人员不要提交你的本地定制,二是每次 svn stat 都会看到配置文件前面有个 M 这样的“噪音”,三则一旦不小心提交了,所有人自己的定制都面临可能有冲突的麻烦,尤其生产服务器上的更是不能随便出问题。

以下解决方案来自我们一位客户的经验,值得我们学习!


We typically identify files that contain system-specific configuration information, and name the versioned copies of the files with a  supplemental .default suffix. Such files contain clearly-generic values for system paths, DB connection parameters, and the like.

We then make a copy of this file without the suffix, and set up the `svn:ignore` parameters in the parent directory to ignore that file.

Let's examine this approach using a particular file from the project:

  /trunk/src/cake_project/app/config/rebuild_database.sh

Assuming I've checked out the repository to $client's `DEV` server  filesystem, and have cd`d into app/config in the project files, here's  what the process looks like from a terminal session:

 $ svn mv ./rebuild_database.sh ./rebuild_database.sh.default
  A         rebuild_database.sh.default
  D         rebuild_database.sh

 $ svn propset svn:ignore 'rebuild_database.sh' .
  property 'svn:ignore' set on '.'

 $ svn commit . -m 'rebuild_database.sh is no longer under danger of wildly reverting commits.'

From here, I could continue to edit rebuild_database.sh as frequently  (or not) as necessary, and the file--changed or not--will never show  up in svn status queries, nor will changes to its contents ever be  committed into the repository.

____

原帖: http://groups.google.com/group/acg-school/web/svn-practice

No comments: