学习自MIT6.031 软件构造课的Reading材料的第5篇。
Reading 5: Version Control (mit.edu)
目录
一.关于版本控制的介绍
我们首先来了解一下版本控制。
版本控制无论是在多项目还是少项目中,无论是在开源的还是专有的中都是有必要的,如果没有版本控制,同一个代码项目的程序员间的协调将变得十分困难。
发明版本控制的原因
当我们处理自己设置的问题时可能会出现。如果从一个文件开始,可能会花费几天的时间去处理。保存备份文件这个简单的规则可以满足她在交出评分之前,如果要做出改变回到过去的状态,可以通过这个规则来找回过去的版本。其遵循的规则主要为新版本只是为了避免混淆eclipse。我们也将最新版本称为头部。
现在,当 Alice 意识到版本 3 存在致命缺陷时,她可以将版本 2 复制回当前代码的位置。 避免了灾难! 但是,如果版本 3 包含一些好的更改和一些不好的更改呢? Alice 可以手动比较文件以查找更改,并将它们分为好的和坏的更改。 然后,她可以将好的更改复制到版本 2 中。
这是大量的工作,人眼很容易错过变化。 幸运的是,有用于比较文本的标准软件工具;在 UNIX 世界中,一个这样的工具是 diff。 更好的版本控制系统将使差异易于生成。
当她开始编辑来创建“版本5”时,由于分心而忘记自己的变化。就会在更新上传云时发生错误,她可能将所有的本的文件复制到云中,导致他仅包含版本5D
在这一点上,仅考虑一个程序员单独工作的场景,我们已经有一个版本控制方案应该支持的操作列表:
- 恢复到过去的版本
- 比较两个不同的版本
- 将完整版本历史记录推送到另一个位置
- 从该位置拉回历史记录
- 合并属于同一早期版本的分支的版本
如何使用复制对象图git clone
如何将对象图从 Athena 获取到本地计算机,以便开始处理问题集?Git 克隆复制图形。假设您的用户名是:bitdiddle
git clone ssh://.../psets/ps0/bitdiddle.git ps0
我们仍然没有解释对象图中的内容。 但在我们这样做之前,让我们了解第 3 步:查看分支的当前版本。git clonemaster
对象图以方便高效的结构存储在磁盘上,用于执行 Git 操作,但不是以我们可以轻松使用的格式存储的。 在 Alice 发明的版本控制方案中,只是调用当前版本的 ,因为她需要能够正常编辑它。 在 Git 中,我们通过从对象图中签出文件来获取文件的普通副本。 这些是我们在 Eclipse 中看到和编辑的文件。
我们还在上面决定,在版本历史记录中支持多个分支可能很有用。 对于从事长期项目的大型团队来说,多个分支机构至关重要。 为了在 6.031 中保持简单,我们不会使用分支,也不建议您创建任何分支。 每个 Git 存储库都带有一个名为 的默认分支,我们所有的工作都将在该分支上。
因此,第 2 步为我们提供了一个对象图,第 3 步为我们提供了一个工作目录,其中包含我们可以编辑的文件,从项目的当前版本开始。
让我们最后深入了解该对象图!
克隆示例存储库:https://github.com/6031-sp18/ex05-hello-git.git
使用入门或专业版 Git 2.3:查看提交历史记录中的命令,或使用 SourceTree 等工具,向自己解释这个小项目的历史。
这是此示例存储库的 git lol 输出:
* b0b54b3 (HEAD, origin/master, origin/HEAD, master) Greeting in Java
* 3e62e60 Merge
|\
| * 6400936 Greeting in Scheme
* | 82e049e Greeting in Ruby
|/
* 1255f4e Change the greeting
* 41c4b8f Initial commit
Git 项目的历史记录是一个有向无环图 (DAG)。 历史图是存储在 中的完整对象图的主干,所以让我们花一分钟时间关注它。
对象图中的内容
历史图是完整对象图的主干。 里面还有什么?
每次提交都是我们整个项目的快照,Git 用树节点表示。 对于任何合理大小的项目,大多数文件在任何给定的修订中都不会更改。 存储文件的冗余副本会浪费,所以 Git 不会这样做。相反,Git 对象图将单个文件的每个版本存储一次,并允许多个提交共享该副本。 左侧是我们示例中更完整的 Git 对象图呈现。
序列、树和图形
当您在单台计算机上独立工作时,版本历史记录的 DAG 通常看起来像一个序列:提交 1 是提交 2 的父级,是提交 3 的父级...
在我们的示例存储库的历史记录中涉及三个程序员。 其中两个——Alyssa和Ben——“同时”做出了改变。 在这种情况下,“同时”并不意味着完全同时期。 相反,这意味着他们基于相同的先前版本制作了两个不同的新版本,就像Alyssa在她的笔记本电脑和台式机上制作了5L和5D版本一样。
当多个提交共享同一个父提交时,我们的历史记录 DAG 会从一个序列变为一个树:它分开分支。 请注意,项目历史记录中的分支不需要任何人创建新的 Git 分支,只需我们从相同的提交开始并在存储库的不同副本上并行工作:
* commit 82e049e248c63289b8a935ce71b130a74dc04152
| Author: Ben Bitdiddle <ben.bitdiddle@example.com>
| Greeting in Ruby
|
| * commit 64009369c5ab93492931ad07962ee81bda921ded
|/ Author: Alyssa P. Hacker <alyssa.p.hacker@example.com>
| Greeting in Scheme
|
* commit 1255f4e4a5836501c022deb337fda3f8800b02e4
| Author: Max Goldman <maxg@mit.edu>
| Change the greeting
⋮
最后,当分支更改合并在一起时,历史记录 DAG 从树形变为图形形:
* commit 3e62e60a7b4a0c262cd8eb4308ac3e5a1e94d839
|\ Author: Max Goldman <maxg@mit.edu>
| | Merge
| |
* | commit 82e049e248c63289b8a935ce71b130a74dc04152
| | Author: Ben Bitdiddle <ben.bitdiddle@example.com>
| | Greeting in Ruby
| |
| * commit 64009369c5ab93492931ad07962ee81bda921ded
|/ Author: Alyssa P. Hacker <alyssa.p.hacker@example.com>
| Greeting in Scheme
|
* commit 1255f4e4a5836501c022deb337fda3f8800b02e4
| Author: Max Goldman <maxg@mit.edu>
| Change the greeting
更改是如何合并在一起的? 首先,我们需要了解如何在不同的用户和仓库之间共享历史记录。
使用git push和git pull 发送和接受对象图
我们可以使用 git push 将新提交发送到远程仓库:
我们使用 git pull 接收新的提交。 除了获取对象图的新部分外,还可以通过签出最新版本来更新工作副本(就像签出工作副本一样)。 如果远程存储库和本地存储库都发生了更改, 将尝试将这些更改合并在一起。git pullgit clonegit pull
并行时会发生的问题
让我们看看当更改并行发生时会发生什么:
将鼠标悬停或点击每个步骤以更新图表:
- Alyssa 和 Ben 都通过两个提交( 和 )克隆存储库。
- Alyssa 创建并将她的更改提交为 。
- 同时,Ben 创建并提交他的更改为 .此时,它们的两个更改都仅存在于其本地存储库中。 在每个存储库中,现在指向不同的提交。
- 假设Alyssa是第一个将她的改变推给Athena的人。
- 如果本现在试图推动会发生什么? 推送将被拒绝:如果服务器更新为指向 Ben 的提交,Alyssa 的提交将从项目历史记录中消失!
- 本必须将他的更改与Alyssa的更改合并。
- 为了执行合并,他从 Athena 中提取她的提交,这做了两件事:
- (a) 将新提交下载到 Ben 仓库的对象图中
- (b) 将 Ben 的历史与 Alyssa 的历史记录合并,创建一个将两个历史记录连接在一起的新提交 ()。 此提交是与其他任何提交一样的快照:应用了两个更改的存储库的快照。
现在本可以了,因为当他这样做时,没有历史会消失。
而Alyssa可以得到本的作品。
在这个例子中,Git 能够自动合并 Alyssa 和 Ben 的更改,因为它们各自修改了不同的文件。 如果它们都编辑了相同文件的相同部分,Git 将报告合并冲突。 Ben 必须在提交合并之前手动将它们的更改编织在一起。 所有这些内容都在有关合并、合并和合并冲突的入门部分中进行了讨论。
了解版本控制的三大思想
版本控制与 6.031 的三大思想有何关系?
免受错误侵害
- 查找何时何地出现问题
- 寻找其他类似的错误
- 确信代码没有意外更改
易于理解
- 为什么要进行更改?
- 同时还更改了什么?
- 我可以向谁询问此代码?
为改变做好准备
- 所有关于管理和组织更改的信息
- 接受和集成来自其他开发人员的更改
- 隔离分支上的推测工作
总结
MIT认为发明版本控制的主要目的是解决“并行开发”和“历史管理”两大问题。
并行开发: 在软件开发过程中,多个开发人员需要同时对同一个代码库进行修改和提交。这就会导致代码冲突和覆盖等问题,影响开发效率和代码质量。因此,版本控制系统需要提供支持并行开发的功能,如分支、合并等。
历史管理:在软件开发的漫长过程中,代码会不断地经历迭代、修复和重构等变化,而这些变化都需要被记录和管理起来,以便后期维护和升级。因此,版本控制系统需要提供详细的历史记录和注释功能,以便开发人员了解每次更改的目的和影响。
除此之外,MIT还推崇一种名为“分布式版本控制”的新型技术,它可以让每个开发者都能够拥有完整的代码副本,并且可以独立地进行分支管理和合并操作,从而更好地支持并行开发和协作。其中,Git就是一种基于分布式版本控制的工具,被广泛地应用于软件开发领域。
学习心得
通过了解MIT关于版本控制中发明版本控制的观点,我深刻认识到了版本控制在软件开发中的重要性以及版本控制系统需要具备的核心功能。并行开发和历史管理是版本控制系统必须要解决的两大问题,一个好的版本控制工具需要提供分支、合并等并行开发功能,并且能够记录每次更改的详细信息,以便后期追溯和维护。
此外,学习到MIT提出的分布式版本控制技术,让我对版本控制的理解更加深入。传统的版本控制系统有着中心化的架构,代码库位于服务器上,开发人员在本地进行操作。相比之下,分布式版本控制系统可以让每个开发者都拥有完整的代码副本,这种去中心化的设计可以更好地支持并行开发和协作,而且更加安全可靠。
总的来说,MIT关于版本控制的观点为我们指明了版本控制的方向和目标,为我们开发高效、优质的软件提供了参考和指导。在实际开发中,我们应该根据项目需求和规模选择合适的版本控制工具,并且灵活运用其各种功能,以便更好地协作、管理和维护代码。