跳到正文
Soren
Knowledge阅读室13 个章节
本页目录
← Knowledge
Tools & collaboration · personal synthesis

Git 与 GitHub:从本地快照到协作网络

把命令背后的对象关系捋直:文件怎样进入一次提交,分支为什么只是指针,远端如何同步,以及 Pull Request 为什么成为协作的中心。

· Soren

我的核心理解:Git 不是“把文件上传到 GitHub”的工具。它首先是一套在本地记录快照、组织历史和移动指针的系统;GitHub 则把这些历史放进权限、讨论、审查和自动化组成的协作网络。

先分清 Git 和 GitHub

GitGitHub
本质分布式版本控制系统托管 Git 仓库的协作平台
在哪里运行主要在本地,也能与任意 Git 服务器通信云端网站与服务
解决什么版本、分支、合并、历史权限、Pull Request、Issue、Review、Actions
没有网络能否工作大部分操作可以不能访问云端协作功能

GitHub 不是 Git 的必需部分,Git 也不只服务于 GitHub。把二者分开之后,许多命令会突然变得清楚:git commit 保存到本地;git push 才把本地历史发送到远端。

Git 保存的不是“修改记录”,而是一串快照

理解 Git 最有效的模型,是把每次提交看成项目在某一时刻的完整快照。内部实现会复用没有变化的对象,所以它不需要机械地复制所有文件;但从使用者视角,每个 commit 都能还原出完整目录状态。

一次提交包含项目树的指针、作者、时间、说明文字和父提交。父指针把提交连成历史。合并提交可能有两个父节点,所以 Git 历史更准确地说是一张有向无环图,而不是永远笔直的一条线。

main · C3
feature · F2

分支不是另一份代码。它只是一个会随提交向前移动的名字。HEAD 则告诉 Git:我现在站在哪个分支,或者直接站在哪个提交上。

本地的三个区域

Working tree硬盘上正在查看和编辑的文件。这里允许有尚未准备提交的实验。
Staging area / index下一次提交的候选快照。它让一次提交只包含一个清晰主题。
Repository.git 中的对象、引用与历史。commit 真正写入这里。
编辑文件→ git add →暂存→ git commit →本地历史

暂存区不是多余的一步。它回答的是:“我这次究竟想提交哪一组变化?”一个文件可以同时存在已暂存和未暂存的修改,因此提交前最值得养成的习惯不是盲目 git add .,而是查看:

git status
git diff
git diff --staged

一次最小而完整的本地循环

git init                         # 让当前目录成为仓库
git status                       # 看清当前状态
git add README.md                # 选择进入下次提交的内容
git diff --staged                # 审阅将要提交的快照
git commit -m "Document setup"   # 生成本地提交
git log --oneline --decorate     # 查看历史和分支指针

好的 commit 应该是一个可以解释、可以审查、也可以单独撤销的逻辑单元。提交信息写“做了什么以及为什么”,而不是笼统的 update 或 fix。

分支、切换与 worktree

新任务通常从新分支开始:

git switch -c docs/git-guide
# 修改、暂存、提交
git switch main

git switch 改变 HEAD 指向的分支,并让工作目录呈现该分支的快照。旧教程常用 git checkout,它仍然存在,但同时承担切分支和恢复文件等多种职责;新手用 switch 与 restore 更容易看清意图。

Working tree 与 git worktree

working tree 是当前检出的文件目录;git worktree 是一组命令,可以让同一个仓库同时拥有多个工作目录。这样能在不反复切换分支的情况下,一边维护主线,一边开发功能或修复紧急问题。

合并与变基:都在整合历史,但表达不同

MergeRebase
动作把两条历史汇合,必要时生成合并提交把一组提交重新放到新的基点上
历史保留真实分叉结构得到更线性的历史
风险通常不会改写现有提交会生成新的提交 ID,等于改写历史
建议共享分支上的安全默认项清理自己尚未共享的功能分支

最重要的原则是:不要随意 rebase 已经被别人依赖的共享历史。历史是否漂亮,永远不该优先于协作者能否安全同步。

远端并不是“云端文件夹”

远端仓库是另一份 Git 仓库。origin 只是克隆时默认创建的远端名字,并不具有特殊权限。常见对象关系是:

命令真正发生的事
git clone复制仓库、历史和默认分支,并设置远端 origin。
git fetch下载远端新对象并更新远端跟踪分支,不改当前工作文件。
git pull先 fetch,再把远端变化 merge 或 rebase 到当前分支。
git push发送本地对象,并请求远端把某个分支指针向前移动。

这也解释了为什么 push 会被拒绝:如果远端已经有你本地不知道的新提交,Git 不愿直接让远端分支跳过别人的历史。先 fetch,理解差异,再合并或变基,才是安全做法。

GitHub 协作的两种基本模型

共享仓库:branch → Pull Request

适合已有写入权限的团队成员。每个人从默认分支创建功能分支,推送到同一个仓库,通过 Pull Request 讨论、运行检查和合并。

开源贡献:fork → branch → Pull Request

适合没有原仓库写入权限的外部贡献者。fork 是自己账号下的一份仓库;origin 通常指向自己的 fork,upstream 指向原项目。Pull Request 把自己分支上的提交提议给上游维护者。

Pull Request 不是 Git 命令

PR 是 GitHub 提供的协作对象:它把提交差异、说明、讨论、审查、自动测试和合并决策放在一起。Git 负责历史,GitHub 负责围绕历史协作。

一次规范的开源贡献

  1. 先读规则。查看 README、CONTRIBUTING、许可证、行为准则、测试要求和安全报告方式。
  2. 确认问题值得做。优先选择 good first issue 或 help wanted;需求不明确时先与维护者沟通。
  3. Fork 并克隆。自己的 fork 用作可推送的远端,同时把原项目添加为 upstream。
  4. 创建主题分支。让每个 PR 聚焦一个问题,不在默认分支直接堆改动。
  5. 做小而完整的提交。运行项目要求的格式化、测试和构建,提交前审阅 diff。
  6. 推送并打开 PR。说明背景、改动、验证方式和关联 Issue;未完成时可以先开 Draft PR。
  7. 响应审查。继续向同一分支提交即可更新 PR,不需要因修改意见重开一个 PR。
git clone https://github.com/YOU/PROJECT.git
cd PROJECT
git remote add upstream https://github.com/OWNER/PROJECT.git
git switch -c docs/clarify-installation

# 修改并验证
git add docs/install.md
git commit -m "Clarify Windows installation"
git push -u origin docs/clarify-installation

# 然后在 GitHub 上从该分支创建 Pull Request

冲突不是事故,而是 Git 无法替人做决定

当两条历史修改了同一位置,Git 没有足够信息判断哪个意图更正确,于是把决定交给人。可靠的处理顺序是:

  1. 先用 git status 确认冲突文件和当前操作;
  2. 阅读两边内容,理解业务意图,而不是机械删除冲突标记;
  3. 编辑成最终版本,运行测试;
  4. git add 标记该文件已解决;
  5. 继续 merge / rebase,或在判断错误时使用对应的 --abort 回到操作前。

比背命令更重要的安全习惯

最后的判断

Git 真正难学的地方,不是命令多,而是它同时展示了几个不同状态:硬盘里的文件、暂存区里的下次快照、本地历史、远端历史,以及指向这些提交的各种分支。只要先问“我现在在移动哪个对象”,大多数命令就不再神秘。

我会把整套系统压缩成一句话:在本地用小而清晰的提交保存意图,用分支隔离工作,用远端交换历史,再用 Pull Request 让变化经过讨论、验证与授权。

资料与延伸阅读