你说的对,但是《Git》是由 Linus Torvalds 自主研发的一款去中心化的版本控制软件……

你说的对,但是《Git》是由 Linus Torvalds 自主研发的一款去中心化的版本控制软件。软件运行在一个被称作「repository」的世界,在这里,被用户 git add 的工作区文件将被记录进暂存区,git commit 生成版本历史节点。你将扮演一位名为 contributor 的神秘角色,在多人的协作中邂逅性格各异、能力独特的同伴们,在各自的设备上轮流 rm -rf 项目文件夹,然后重新 git clone——同时,逐步发掘「版本控制」的真相。

git 和 GitHub 并不只为程序员而生,也不只为团队而生。

作为一种版本控制软件——

  • 能编码为计算机数据的内容;
  • 除了最终结果以外也关注中间版本,可能会在不同版本之间返工的项目

——理论上都可以用 git 来管理。

(当然实际上,还是程序员的纯文本“小”文件更适合 git,团队协作时更需要 git。)

单人版本记录

  1. 在 git 服务提供商(下文也简称为远端)的网页上创建仓库(repository,一种特殊的文件夹)。然后用 git clone URL 将远端仓库克隆到本地
    • 另一种方法是在本地文件夹 git init
      • 优点是更容易适配一些模板或者框架的创建流程,比如 next.js, python poetry;
      • 缺点是和远端仓库的对接需要更进阶的 git 知识
  2. git pull 将远端的更新拉取到本地。
    • git pull = git fetch + git merge
  3. 在仓库内增加、修改、删除文件
    • git status 可以看到工作区内文件们的总体改动情况
    • git diff FILE 可以检查某个文件内容的具体变化
    • 一些图形界面软件可以可视化修改状态(广告位招商)
  4. git add FILE 将工作区各文件交给 git 追踪,生成 blob,加入 git 的暂存区 (index)。
  5. git commit 根据暂存区的各文件构建一个历史节点,在弹出的编辑器里写 commit message,简明概括这一版本的改动,方便将来回顾。
  6. git push 将本地的更新推送到远端。
  7. GOTO 第 2 步……

多人协作工作流

所谓 git 工作流,指的是 git 硬性规定的接口之外,团队为了方便管理,自行额外约定的各项 git 功能的使用方式。

这个文档 https://www.atlassian.com/git/tutorials/comparing-workflows 比较了几种常见的工作流 (centralized, feature branching, Gitflow, forking),看了一下——

对于绝大多数课题组的规模,feature branch 工作流基本就够用了,而且它的官僚主义成本也可以承受。

Feature Branch 工作流

  1. 课题的实际推动者(不一定是老板)维护远端的 main 分支,让最新一个 commit 保持为整个项目的交付状态。

  2. 参与者 git clone URL 到本地以后,用以下命令从 main 分支的最新 commit 开始工作:

    git checkout main
    git fetch origin
    # 确认当前没有未提交的本地修改后:
    git reset --hard origin/main
  3. 参与者有新的工作要做时,用 git checkout -b NEW_FEATURE 创建一个新的分支,分支的名字 NEW_FEATURE 简要概括工作主题。

  4. 在分支内工作,和“单人版本记录”的第 3—5 步相同。

  5. git push -u origin NEW_FEATURE 把本地分支推送到远端,第一次之后可以省略 git push 后面的部分。

  6. 分支的工作完成后,通知课题实控人合并分支。

  7. 课题实控人在 main 分支上执行 git merge NEW_FEATURE

  8. 参与者跳 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 时,可能有两种想法:

  1. 只是看看,运行一下项目,不作进一步开发:git checkout COMMIT_HASH
  2. 以这个 commit 为起点开始一段新思路的开发,也就是创建一个新的分支:git checkout -b NEW_BRANCH COMMIT_HASH

与别人的分支合并时出现矛盾

有时在我们从 main 创建分支之后,有其他人在我们之后又从 main 新开了一个分支。

如果对方比我们先合并进 main 的话,我们后来进行合并时,有些文件在两个分支中都被修改过,git 无法自动决定保留哪一版,就会出现合并冲突 (merge conflict)。

因此就需要加快工作进度,把合并冲突的球踢给对方

不完全是开玩笑,预防合并冲突的最佳实践就是让每个分支的寿命尽可能短,快进快出,前一个人的分支合并之后下一个人再开始自己的分支。

假如冲突冲突已经发生,git 会按行标记两个分支不同的内容:

<<<<<<< HEAD
自己的版本
=======
对方的版本
>>>>>>> other-branch

解决方法:

  1. 打开冲突的文件 FILE.txt
  2. 删除冲突的标记和不想要的那一版本的内容,保存
  3. git add FILE.txt
  4. git commit 构建一个冲突解决的历史节点

git rebase 预防合并冲突

为了预防合并冲突,在 main 分支有任何新的 commit 之后,都最好用 git rebase 提前反映到我们当前的 NEW_FEATURE 分支。

举个例子,当我们在 E 分出去之后,main 上又新增了 F, G 两个 commits

          A---B---C NEW_FEATURE
         /
    D---E---F---G main
  1. git stash 暂时保存 NEW_FEATURE 分支上,我们工作到一半还没有 commit 的内容
  2. git checkout main 转到 main 分支
  3. git pull 获取远端的新 commits
  4. git checkout NEW_FEATURE 回到我们的分支
  5. git rebase main
                  A'--B'--C' NEW_FEATURE
                 /
    D---E---F---G main

这样操作之后,我们的 A’ 的上游从 E 变成了 G。

有时候这种操作已经来不及了,git rebase 本身就会发生合并冲突,那就还是要回到上一节的方法去解决。

本文收录于以下合集: