跳转至

版本控制

Git 是每个程序开发者都应该学习的技术,无论他们的研究方向是哪个领域。

版本控制是对文档、文件或任何其他类型数据的更改的管理。在软件开发中,它对于管理和跟踪代码库的更改、确保代码质量、减少错误以及改善团队成员之间的协作至关重要。

如果没有版本控制,管理和跟踪代码更改将是一项困难且容易出错的任务。Git 等版本控制工具提供了一种管理代码更改、跟踪版本以及与团队成员协作的方法。这使它成为现代软件开发的重要组成部分,几乎所有软件开发团队都在使用它。

前提条件

为了完成本节,你需要具备以下条件:

  • 一个命令行环境(Windows Terminal/Terminal.app/...);
  • 一个你选择的文本编辑器(我将使用 VS Code);
  • 一个GitHub账户。

什么是Git?

Git 是一种流行的版本控制系统,开发者使用它来管理代码更改。它允许开发者跟踪对其代码库所做的更改,与团队成员协作,并在需要时恢复到以前的版本。

Git 由于其灵活性、速度和轻松处理大型代码库的能力而被广泛用于软件开发。它还提供了一系列用于管理和组织代码的功能和工具,例如分支和合并。通过 Git,你可以恢复到文件的不同状态(revert),也可以创建一个文件的副本(fork),对该副本进行修改,然后将这些修改合并到原来的文件(upstream)中。

Git 的使用并不局限于源代码文件,你也可以用它来跟踪文本文件甚至图像。这就是说,Git 不仅仅是为开发者服务的,任何人都可以获得它的帮助。

安装

Git

https://git-scm.com/

GitHub Desktop

https://github.com/desktop/desktop

SourceTree

https://www.sourcetreeapp.com/

使用方法 - Git

提示

由于 Git 的操作和其余图形化界面原理相同,将不讲解图形化界面的操作。

建立一个应用了 Git 的仓库

创建目录

设置新 Git 存储库的第一步是在您的计算机上创建一个新目录。该目录将作为新存储库的根目录。

使用命令行,输入 mkdir,或通过文件资源管理器创建目录;通过 cd <dir_name> 进入目录;在新储存库的根目录下输入 git init 以初始化一个有 Git 的仓库。

修改文件

对仓库中的文件做出你想要的修改。一般将工作目录使用 VS Code 等编辑器打开。

暂存修改

你需要在修改后将做出修改的文件迁移至暂存区(staging area),以通过后续的步骤将修改提交。

在命令行中输入 git add <filename>git add --all 来将更改放置在暂存区。

将文件添加到暂存区后,就可以将其提交到存储库。重要的是要注意,将文件添加到暂存区并不会真正提交它们——它只是为提交做好准备。你可以在提交之前根据需要继续添加和修改文件。

提交修改

在将你想要提交的更改均放置到暂存区后,你就可以提交修改(commit)。

在命令行输入 git commit -m "commit message",将 "commit message" 替换为一段关于你提交修改的内容的摘要。提交后,更改将保存到存储库的历史记录中,并且可以根据需要进行跟踪、还原或与其他分支合并。

要与其他开发人员共享您的更改或在项目上进行协作,你可以使用 Git 将本地存储库连接到远程存储库。

远程存储库是托管在服务器(例如 GitHub、GitLab 或 BitBucket)上的存储库副本,并允许多个贡献者在同一代码库上工作。

要连接到远程存储库,请使用 git remote add 命令,后跟远程存储库的 URL。

在你接下来的推送(push)中,你只需要 git push 即可。

git pull 命令从远程存储库中获取其他贡献者所做的最新更改,并自动将它们合并到当前分支中。

如:你可以输入 git remote add origin <repo_url> 来连接,在你的第一次提交时,使用 git push -u <default branch> 来建立连接。当他人对远程存储库做出修改后,你可以通过 git pull origin main 来拉取更改(fetch)并合并到本地的分支当中。

克隆别人的仓库

克隆存储库是使用 Git 时的一项常见任务。克隆(clone)创建远程存储库的本地副本,包括项目的所有文件和历史记录。

在 Git 中克隆存储库非常简单,您可以通过多种方式完成,例如使用命令行或图形用户界面。这使您能够访问存储库的代码、提交历史记录和分支。

  1. 复制存储库 URL:首先获取要克隆的远程存储库的 URL。 您可以在存储库的托管平台上找到此 URL,例如 GitHub 或 GitLab。

  2. 打开终端或命令提示符。 打开您喜欢的命令行界面。 这可能是 macOS 和 Linux 上的终端或 Windows 上的命令提示符。

  3. 导航到所需位置:使用 cd 命令导航到要克隆存储库的目录。 例如,如果您想将它克隆到桌面上的“Projects”目录中,您可以运行 cd ~/Desktop/Projects

  4. 开始克隆。输入 git clone <https://github.com/username/repository.git> .

回滚之前的更改

Git 将你的更改历史用哈希值表示出来,这意味着你可以通过一次更改的哈希值将储存库返回到当时的状态。

在更改的历史记录中找到你想要回到的提交的哈希值,输入 git reset <hash>,然后通过 git push -f origin maingit push --force origin main 回滚更改(revert)。若你无法强制推送回滚操作,你可能需要通过一次提交进行回滚,但这样不会清除历史记录中之前的提交记录。

冲突解决

当你在本地进行代码修改时,与此同时,远程仓库也有了新的提交,这时你再进行 git push 时,就会出现冲突,这时你需要先 git pull 将远程分支当中的变更在本地分支上进行更新,然后再进行冲突解决。

Git 使用的注意点

Pull Request 与分支的使用

正常情况下,如果你在使用 Github 等主流 Git 仓库托管平台托管代码,你应该在仓库下有多个分支,如:maindevfeaturehotfix 等,其中 main 分支是主分支,dev 分支是开发分支,feature 分支是功能分支,hotfix 分支是热修复分支。main 分支将会开启分支保护,使得他人不得直接将代码 pushmain 分支,而是需要通过 Pull Request 的方式(以下简称 PR)将代码合并到 main 分支。

这样的好处是,在经过审核之前或经过单元测试之前,你的代码不会被合并到 main 分支,从而保证了 main 分支的稳定性。

同时也可以使用 Github 等平台提供的 fork 功能进行开发,如每个人都拥有一个 fork 仓库。每个人在此仓库进行修改后再合并进主仓库的 feature 分支。

多人协同下的合并冲突解决

与单分支的情况相同,多分支也会出现冲突的情况,如程序员 Afeature-a 分支通过了 PR 并被合并进了 main 分支中。但与此同时 B 也在 feature-b 分支中同步开发。当他开发完毕时,他的代码也需要合并进 main 分支中,但此时 main 分支已经发生了变更,这时就会出现冲突。很明显,feature-b 当中并没有应用 feature-a 当中的更新。因此我们要引入 rebasemerge 的概念。

Rebase

rebase 是将一个分支的变更应用到另一个分支上,这样就可以保证 feature-b 分支中的代码是最新的。但这样做的缺点是,feature-b 分支的提交历史将会被改写,这样会使得 feature-b 分支的提交历史变得混乱,不利于代码的追溯。

rebase 一般用于将远端 main 分支当中的更新应用到本地分支上,这样可以保证本地分支的代码是最新的。

使用如下:

git fetch
git rebase main
fix conflicts...
git add .
git rebase --continue 
那么现在 feature-b 分支的代码就是最新的了。

Merge

merge 是将一个分支的变更合并到另一个分支上,这样做的好处是,不会改写提交历史,但是会产生一个新的提交,这样会使得提交历史变得混乱,不利于代码的追溯。

merge 一般用于将其他分支合并进 main 分支的情况。可以使得回滚变得方便,因为不会抹除分支当中的提交历史。

Github 当中提供了相关的功能。因此 merge 操作可以不用在本地进行,直接在 Github 上进行即可。

Git Log

git log 可以查看提交历史,但是默认情况下,git log 只会显示当前分支的提交历史。如果想要查看其他分支的提交历史,可以使用 git log <branch-name> 来查看。

如果按照正确的方式,即上一节所说的步骤进行冲突处理,那么 git log 产生的结果应该如下:

*   e2e6451 (HEAD -> master) feture-c finished
|\
| * 516fc18 C.2
| * 09112f5 C.1
|/
*   c6667ab feture-a finished
|\
| * e64c4b6 A.2
| * 6058323 A.1
|/
*   2b24281 feture-b finished
|\
| * c354401 B.4
| * 4bfefb8 B.3
| * eb13f72 B.2
| * c2c62b9 B.1
|/
* bbbba82 init
若全都使用 rebase 则会发现分支是平的,而 merge 则会发现分支是树状的,十分混乱。 只有使用 rebasemerge 结合的方式才能使得分支的提交历史变得清晰。