नमस्ते

聊一聊版本控制

写在前面

本文是笔者在一次社团活动讲演的文字底稿,略经修饰后刊于此处。主要谈了谈我们为什么需要版本控制,以及版本控制系统提供给我们那么多功能是做啥的,并在最后尝试给出一些关于如何上手版本控制的建议。

不好的味道

我们假设你有这么一个功能1

// alpha

int Fib(int nth) {
    switch (nth) {
        case 0:
        case 1:
            return 1;
        case 2:
            return 2;
        case 3:
            return 3;
    }
}

这几乎是一个用来求解斐波那契数列第 n 项的函数,它也许很不完善,但如果你只需要前四项,那也挺好。可有一天,你的需求变了,需要求解的项数变得非常大,大到你无法通过继续扩充这个函数来完成。你决定对代码进行一些破坏性的更新,总得来说,你希望获得下面这个感觉的代码。我们称原本的函数为 alpha,而你新写的版本为 beta。

// beta

int Fib(int nth) {
    if (nth == 0 || nth == 1) return 1;
    return Fib(nth - 1) + Fib(nth - 2);
}

于是你删掉了原来的函数 Fib(nth),着手写这个新函数。就在函数写了一半的时候,你突然意识到你可以进一步优化内存占用。你希望新函数是下面这个感觉,它将被我们称为 beta*:

// beta*

int Fib(int nth) {
    return FibIter(1, 1, nth);
}

int FibIter(int a, int b, int counter) {
    if (counter == 0) return a;
    if (counter == 1) return b;
    return FibIter(b, a + b, counter - 1);
}

你把写了一半的源文件拷贝了一份存在别的地方,然后把内容改写成你最后想要的样子。
你嗅到了一丝不好的味道:你拥有三个版本的源代码,其中两份在你的硬盘上,只有一份是可用的。

更不好的味道

你写的 Fib(nth) 函数的调用者部分的代码并不是你来写的——你和你的朋友们共同维护一个项目。无论怎么说,你总是把一个可用的函数写出来了,你把你的源代码文件通过即时通讯软件发给你的朋友。
在收到你的源代码后,你的朋友意识到你的函数缺少了一点安全检查,这点小事就不需要喊你来改了,他给你的函数加上了一行,这个新版本被他称为 beta1*:

// beta1*

int Fib(int nth) {
    // 改动发生在这里
    if (nth < 0) throw new ArgumentOutOfRangeException();
    return FibIter(1, 1, nth);
}

int FibIter(int a, int b, int counter) {
    if (counter == 0) return a;
    if (counter == 1) return b;
    return FibIter(b, a + b, counter - 1);
}

巧合的是,你也发现了这一点。理所当然地,你也给源代码加上了一行,我们称这个新版本为 beta**:

// beta**

int Fib(int nth) {
    return FibIter(1, 1, nth);
}

int FibIter(int a, int b, int counter) {
    // 改动发生在这里
    if (counter < 0) throw new ArgumentOutOfRangeException();
    if (counter == 0) return a;
    if (counter == 1) return b;
    return FibIter(b, a + b, counter - 1);
}

你感到代码正在散发着更不好的味道:你和你的协作者拥有两份内容不同但都完成同一个功能的源代码。

除臭球

这里我们只考虑了一份源代码中的一份文件,如果我们将讨论的范畴扩大:当我们要管理一个项目里面的复数份源代码的时候会如何呢?更进一步,当我们需要管理项目配置文件的时候又会如何呢?
无论怎样,我们都需要一个工具来帮助我们管理我们的源代码,它应该做到这样的事情:

  • 拥有我们的源代码,以及它们的历史。
  • 我们应该能同时拥有一份源代码的平行的不同历史。
  • 我们能够对比同一份源代码的历史版本,并选择我们要的那份。
  • 我们的协作者应当能做到跟我们一样的事情。

幸好,已经有了相当多这样的工具,它们被称为版本管理工具。
它们通常会提供一些概念来简化版本管理的过程。比较流行的是分支模型:我们所有的代码版本都存放在存储库中,它们的历史通过在分支上的后继关系表示,而平行的源代码历史则用平行的分支表示。我们可以根据这个原理把上文提到的代码表示成这样:

  ------> beta  
  |
alpha --> beta* --> beta**
            |
            ------> beta1*

接着,我们可以对分支进行操作。比如把 beta1* 和 beta** 合并为一个版本,你可以通过选择其中任意一份源代码版本或者改写一份新的源代码版本来做到这点:

  ------> beta  
  |
alpha --> beta* --> beta** -----> gamma
            |                |
            ------> beta1* ---

最后,我们期望的多人协作功能也可以以这种方式实现:

  ------> beta  
  |
  |                               (YOU)
alpha --> beta* --> beta** -----> gamma
            |                |
            |                |
            ------> beta1* ---
                (YOUR_FRIEND)

我们每个人的机器上都拥有一份完整的存储库,而我们当前的源代码状况就可以表示成我们在存储库分支上的位置。先前我们通过合并版本创建了一个新版本并将我们的位置移动到 gamma,而我们的协作者仍然在他的版本 beta1* 上。这时我们的协作者可以同步我们的存储库历史并选择前进:

  ------> beta  
  |
  |                               (YOU, YOUR_FRIEND)
alpha --> beta* --> beta** -----> gamma
            |                |
            |                |
            ------> beta1* ---

让我们用分支的语言来描述我们之前做的事情:

  • 开始,我们处于 alpha 版本上,那是我们的旧 Fib(nth) 函数。
  • 然后,我们决定创建一个新版本 beta,它拥有一个改写过的 Fib(nth) 函数。
  • 我们意识到另一种写法会更好,为了不破坏我们现有的工作,我们决定从 alpha 创建一个新分支,并将我们的另一种写法的 Fib(nth) 存储版本 beta* 里面。
  • 我们最终选择了 beta* 版本,并将历史同步到存储库。
  • 我们和协作者分别从 beta* 中创建了新的版本,由于它们是平行的,所以相当于创建了分支。
  • 我们从同步的存储库历史中发现了这一点,决定将它们合并成一个新版本 gamma。
  • 我们的协作者也发现了这一点,于是他们前进到版本 gamma。

具体一点

至此,你应该明白我们为什么会需要版本管理工具和版本管理工具如何帮助我们了。现在,是时候谈一谈具体的工具了。
事实上,可选择的工具相当多样,FreeBSD 项目曾经也在苦恼该将项目中的源代码迁移到什么版本管理工具中,所以他们将最流行的一些工具列了一个表格,用来对比它们的优缺点。常见的源代码项目通常会使用 Git 或 Mercurial 等,但他们在应对图片和音频等大文件上存在短板,所以又有如 Plastic SCM 这类天生能管理大文件的版本管理工具。
当然,最流行的工具之一 Git 的详细用法并不涵盖在本文的内容中。不如说,我认为用文字复述一遍文档并没有意义,而如何利用 Git 赋予我们的能力来管理我们的源代码也在上文详细地叙述了。学会使用 Git 或其他同类工具的办法就是先翻翻对应的文档,然后在你的源代码库中使用它。你也可以在网络上找到很多教你认识 Git 命令的教程。你也许也需要一个安全的地方练习 Git 的基本用法,以免一不小心把所有的源代码历史都丢了,我这里推荐 Learn Git Branching,它通过一个模拟环境和几个交互式的问题来让你快速上手 Git。这大概会花掉你半天的时间,但相信我,这是值得的。


  1. 本文提到的代码均在 mono 6.12.0 上编译通过
聊一聊版本控制

https://blog.rasp505.top/index.php/archives/10/

作者

Rasp

发布时间

2023-04-22

添加新评论