- BetterExplained -https://betterexplained.com -

版本控制可视化指南

版本控制(又称修订控制,又称源代码控制)让你能够随时间跟踪你的文件。为什么要在意?因为当你搞砸时,你可以轻松回到之前可用的版本。

你可能已经捣鼓出了自己的版本控制系统,却没意识到它有个这么极客的名字。你有像这样的文件吗?(希望不是完全一样的这些)。

这就是我们用“另存为(Save As)”的原因。你想要新文件,又不希望毁掉旧文件。这是个常见问题,通常的解决方式如下:

So Why Do We Need A Version Control System (VCS)?

我们的共享文件夹/命名系统对于课堂项目或一次性论文来说没问题。但软件项目?绝不可能。

你觉得 Windows 源代码是放在一个像“Windows2007-Latest-UPDATED!!”这样的共享文件夹里,谁都能改?每个程序员只是在不同的子文件夹里工作?不可能。

有许多作者参与、变化迅速的大型项目需要一个版本控制系统(极客说法即“文件数据库”)来跟踪变更并避免一片混乱。一个好的版本控制系统能做到以下几点:

共享文件夹快捷简单,但比不上这些功能。

Learn the Lingo

大多数版本控制系统都涉及以下概念,尽管标签可能不同。

基本设置

基本操作

高级操作

And a typical scenario goes like this:

Aliceaddsa file (list.txt) to therepository. Shechecks it out, makes a change (puts “milk” on the list), and checks it back in with a checkin message (“Added required item.”). The next morning, Bobupdateshis local working set and sees the latest revision oflist.txt, which contains “milk”. He can browse thechangelogordiffto see that Alice put “milk” the day before.

Visual Examples

This guide is purposefully high-level: most tutorials throw a bunch of text commands at you. Let’s cover the high-level concepts without getting stuck in the syntax (theSubversion 手册is always there, don’t worry). Sometimes it’s nice tosee what’s possible.

Checkins

The simplest scenario is checking in a file (list.txt) and modifying it over time.

version control checkin

Each time we check in a new version, we get a new revision (r1, r2, r3, etc.). In Subversion you’d do:

svn add list.txt
(modify the file)
svn ci list.txt -m "Changed the list"

The-mflag is the message to use for this checkin.

Checkouts and Editing

In reality, you might not keep checking in a file. You may have tocheck out, edit and check in. The cycle looks like this:

version control checkout

If you don’t like your changes and want to start over, you canrevertto the previous version and start again (or stop). When checking out, you get the latest revision by default. If you want, you can specify a particular revision. In Subversion, run:

svn co list.txt (get latest version)
…edit file…
svn revert list.txt (throw away changes)
svn co -r2 list.txt (check out particular version)

Diffs

The trunk has a history ofchangesas a file evolves. Diffs are the changes you made while editing: imagine you can “peel” them off and apply them to a file:

version control diff

For example, to go from r1 to r2, we add eggs (+Eggs). Imagine peeling off that red sticker and placing it on r1, to get r2.

And to get from r2 to r3, we add Juice (+Juice). To get from r3 to r4, we remove Juice and add Soup (-Juice, +Soup).

Most version control systemsstore diffs rather than full copies of the file. This saves disk space: 4 revisions of a file doesn’t mean we have 4 copies; we have 1 copy and 4 small diffs. Pretty nifty, eh? In SVN, we diff two revisions of a file like this:

svn diff -r3:4 list.txt

Diffs help us notice changes (“How did you fix that bug again?”) and even apply them from one branch to another.

Bonus question:what’s the diff from r1 to r4?

+Eggs
+Soup

Notice how “Juice” wasn’t even involved — the direct jump from r1 to r4 doesn’t need that change, since Juice was overridden by Soup.

Branching

分支(Branch)让我们将代码复制到一个独立的文件夹中,这样就能单独折腾它:

version control branch

例如,我们可以为列表创建一条分支,用来放一些新的、实验性的点子:比如 Rice 或 Eggo 华夫饼这类疯狂的东西。根据版本控制系统的不同,创建分支(副本)可能会改变修订号。

既然有了分支,我们就可以修改代码并解决其中的问题。(“嗯……华夫饼?我不知道老板会怎么想。米饭是稳妥的选择。”)。由于我们在独立的分支中,可以隔离地进行修改和测试,知道我们的改动不会伤害任何人。而且我们的分支历史也处于版本控制之下。

在 Subversion 中,你只需将一个目录复制到另一个位置就能创建分支。

svn copy http://path/to/trunk http://path/to/branch

所以分支并不是一个很难的概念:Pretend you copied your code into a different directory.你大概在学校项目里也分支过自己的代码,确保有一个“保险”版本可以在事情搞砸时回退。

Merging

分支听起来很简单,对吧?其实不然——搞清楚如何将一个分支的改动合并到另一个分支可能会很棘手。

假设我们想把实验分支里的“Rice”特性合并到主线(mainline)中。该怎么做呢?对 r6 和 r7 做差异(diff)然后应用到主线?

Wrongo.我们只想应用那些改动that happened in the branch!。也就是说,我们对 r5 和 r6 做差异,然后应用到主干(trunk):

version control merge

如果我们对 r6 和 r7 做差异,就会丢失主线里的“Bread”特性。这是一个微妙的点——想象从实验分支中“剥下”那些改动(+Rice)并加到主线中。主线可能还有其他改动,这没关系——我们只是想插入 Rice 特性。

在 Subversion 中,合并(merge)和差异非常接近。在主干内部,运行命令:

svn merge -r5:6 http://path/to/branch

该命令对实验分支中的 r5-r6 做差异并应用到当前位置。不幸的是,Subversion 没有简便的方法来追踪哪些合并已经应用过,所以如果你不小心,可能会把同样的改动应用两次。这是一个计划中的特性,但目前的建议是保留一条变更日志(changelog)消息,提醒自己已经把 r5-r6 合并进主线了。

Conflicts

很多时候,版本控制系统(VCS)可以自动合并对同一文件不同部分的改动。Conflicts当出现的改动不协调时就会产生:Joe 想去掉 eggs 并换成 cheese(-eggs, +cheese),而 Sue 想用 hot dog 替换 eggs(-eggs, +hot dog)。

version control conflict

这时候就像一场竞赛:如果 Joe 先提交(check in),那他的改动就会生效(而 Sue 就无法提交她的改动了)。

当改动像这样重叠且互相矛盾时,VCS 可能会报告一个conflict并且不让你提交——得由你来提交一个更新的版本以resolves这个困境。几种做法:

冲突(conflict)不常发生,但可能很烦人。通常我会更新到最新版本,然后重新应用我的改动。

Tagging

谁能想到一个版本控制系统还能符合 Web 2.0 规范?许多系统让你可以标记(tag)(标注)任意修订版以便引用。这样你就可以说“Release 1.0”而不是某个特定的构建号:

version control tag

在 Subversion 中,标签(tag)只是你约定不去编辑的分支;它们留作纪念,这样你就能确切看到 1.0 版本里包含了什么。因此它们止于一个桩(stub)——没有后续了。

(in trunk)
svn copy http://path/to/revision http://path/to/tag

Real-life example: Managing Windows Source Code

我们猜 Windows 是用共享文件夹管理的,但事实并非如此。所以这是怎么做到的?

你在自己的分支中开发新特性,然后“反向集成(Reverse Integrate, RI)”把它们弄进 Main。之后,你“正向集成(Forward Integrate)”把 Main 里最新的改动带入你的分支:

version control branch example

假设我们在 Media Player 10 和 IE 6。Media Player 团队在自己的分支里做出了 11 版。当就绪并测试通过后,会有一个从 10–11 的补丁被应用到 Main(就像“Rice”的例子,只是复杂一点)。这是一个reverse integration,从分支到主干。IE 团队也可以做同样的事。

之后,Media Player 团队可以获取其他团队(比如 IE)的最新代码。这种情况下,Media Playerforward integrates并把 Main 里的最新补丁弄进自己的分支。这就像把“Bread”特性拉进实验分支,不过同样更复杂。

所以是 RI 和 FI。遵命。这种安排让改动在各分支间渗透,同时把新代码挡在主线之外。酷吧?

In reality, there’s many layers of branches and sub-branches, along with quality metrics that determine when you get to RI. But you get the idea: branches help manage complexity. Now you know the basics of how one of the largest software projects is organized.

Key Takeaways

My goal was to share high-level thoughts about version control systems. Here are the basics:

These are the basics — as time goes on I’ll share specific lessons I’ve learned from我的项目. Now that you’ve figured out a regular VCS,试试这篇图解分布式版本控制指南.

本系列其他文章

  1. A Visual Guide to Version Control
  2. 分布式版本控制入门(图解)
  3. 学习 Git 时的顿悟时刻