你说的对,但是《Git》是由 Linus Torvalds 自主研发的一款去中心化的版本控制软件。软件运行在一个被称作「repository」的世界,在这里,被用户 git add 的工作区文件将被记录进暂存区,git commit 生成版本历史节点。你将扮演一位名为 contributor 的神秘角色,在多人的协作中邂逅性格各异、能力独特的同伴们,在各自的设备上轮流 rm -rf 项目文件夹,然后重新 git clone——同时,逐步发掘「版本控制」的真相。
git 和 GitHub 并不只为程序员而生,也不只为团队而生。
作为一种版本控制软件——
- 除了最终结果以外也关注中间版本,可能会在不同版本之间返工的项目
——理论上都可以用 git 来管理。
(当然实际上,还是程序员的纯文本“小”文件更适合 git,团队协作时更需要 git。)
单人版本记录
- 在 git 服务提供商(下文也简称为远端)的网页上创建仓库(repository,一种特殊的文件夹)。然后用
git clone URL 将远端仓库克隆到本地
- 优点是更容易适配一些模板或者框架的创建流程,比如 next.js, python poetry;
git pull = git fetch +git merge
git status 可以看到工作区内文件们的总体改动情况git diff FILE 可以检查某个文件内容的具体变化
git add FILE 将工作区各文件交给 git 追踪,生成 blob,加入 git 的暂存区 (index)。git commit 根据暂存区的各文件构建一个历史节点,在弹出的编辑器里写 commit message,简明概括这一版本的改动,方便将来回顾。
多人协作工作流
所谓 git 工作流,指的是 git 硬性规定的接口之外,团队为了方便管理,自行额外约定的各项 git 功能的使用方式。
这个文档 https://www.atlassian.com/git/tutorials/comparing-workflows 比较了几种常见的工作流 (centralized, feature branching, Gitflow, forking),看了一下——
对于绝大多数课题组的规模,feature branch 工作流基本就够用了,而且它的官僚主义成本也可以承受。
Feature Branch 工作流
课题的实际推动者(不一定是老板)维护远端的 main 分支,让最新一个 commit 保持为整个项目的交付状态。
参与者 git clone URL 到本地以后,用以下命令从 main 分支的最新 commit 开始工作:
git checkout maingit fetch origin# 确认当前没有未提交的本地修改后:git reset --hard origin/main
参与者有新的工作要做时,用 git checkout -b NEW_FEATURE 创建一个新的分支,分支的名字 NEW_FEATURE 简要概括工作主题。
在分支内工作,和“单人版本记录”的第 3—5 步相同。
git push -u origin NEW_FEATURE 把本地分支推送到远端,第一次之后可以省略 git push 后面的部分。
课题实控人在 main 分支上执行 git merge NEW_FEATURE。
参与者跳 GOTO 第 2 步(初次参与)或第 3 步(已经参与过)……
Forking 工作流
在 GitHub 上,每个参与者都有自己的账号,但只有实控人才掌握项目官方 repository。这时上面的 feature branch 工作流中的——
- 第 2 步之前,参与者要在自己的账号下 fork 项目实控人的 repository;
- 第 5 步的远端,指的是参与者自己的 forked repository
- 第 6 步的通知合并,变成在 GitHub 网站上发起 pull request,从参与者的
NEW_FEATURE 分支指向实控人的 main 分支
异常处理
“还是换回第 1 版吧”
每一个 commit 都是版本历史中的一个时间节点,也就是一个版本。在 git 系统内部,每一个 commit 有一个不重复的哈希值作为索引。
git log 可以看到每个 commit 的哈希值和提交信息。
回到之前某个 commit 时,可能有两种想法:
- 只是看看,运行一下项目,不作进一步开发:
git checkout COMMIT_HASH - 以这个 commit 为起点开始一段新思路的开发,也就是创建一个新的分支:
git checkout -b NEW_BRANCH COMMIT_HASH
与别人的分支合并时出现矛盾
有时在我们从 main 创建分支之后,有其他人在我们之后又从 main 新开了一个分支。
如果对方比我们先合并进 main 的话,我们后来进行合并时,有些文件在两个分支中都被修改过,git 无法自动决定保留哪一版,就会出现合并冲突 (merge conflict)。
因此就需要加快工作进度,把合并冲突的球踢给对方。
不完全是开玩笑,预防合并冲突的最佳实践就是让每个分支的寿命尽可能短,快进快出,前一个人的分支合并之后下一个人再开始自己的分支。
假如冲突冲突已经发生,git 会按行标记两个分支不同的内容:
<<<<<<< HEAD自己的版本=======对方的版本>>>>>>> other-branch
解决方法:
git rebase 预防合并冲突
为了预防合并冲突,在 main 分支有任何新的 commit 之后,都最好用 git rebase 提前反映到我们当前的 NEW_FEATURE 分支。
举个例子,当我们在 E 分出去之后,main 上又新增了 F, G 两个 commits
A---B---C NEW_FEATURE / D---E---F---G main
git stash 暂时保存 NEW_FEATURE 分支上,我们工作到一半还没有 commit 的内容git checkout main 转到 main 分支git checkout NEW_FEATURE 回到我们的分支
A'--B'--C' NEW_FEATURE / D---E---F---G main
这样操作之后,我们的 A’ 的上游从 E 变成了 G。
有时候这种操作已经来不及了,git rebase 本身就会发生合并冲突,那就还是要回到上一节的方法去解决。