Git 到底在管理什么?从 commit、branch 到 remote

Kaku Lv4

Git 不是 GitHub

假设你刚开始写代码,第一次要把项目推到 GitHub 上。你照着教程敲了一串命令:

1
2
3
4
5
git init
git add .
git commit -m "first commit"
git remote add origin git@github.com:owner/repo.git
git push -u origin main

项目确实出现在 GitHub 上了。但回过头来看,你可能不太确定这几行命令分别做了什么。git add 是添加文件到 GitHub 吗?git commit 是上传代码吗?

都不是。

Git 是一个版本控制工具。它运行在你的本地机器上,负责记录代码的变化历史。

GitHub 是一个代码托管和协作平台。它提供了一个远程服务器来存放你的仓库,还提供了 Issue、Pull Request、Review 这些协作功能。

你可以只用 Git,不用 GitHub。你也可以把 Git 仓库放在 GitLab、Gitea、Forgejo 或者自己的服务器上。Git 先负责记录版本,平台再负责协作。

这篇文章只讲 Git 本身。GitHub 的协作流程是下一篇的内容。

为什么需要版本控制

回到最开始的问题。一个项目在开发过程中不断修改,如果你不用任何版本控制工具,可能最后会变成这样:

1
2
3
4
5
6
main.py
main-backup.py
main-final.py
main-final-v2.py
main-final-v2-really-ok.py
main-final-v2-really-ok-FIXED.py

这种方式很快就会失控。你不知道哪个版本是最新可用的,也不知道昨天改了什么导致今天跑不起来。如果两个人同时改同一个文件,更是灾难。

版本控制解决的就是这些问题:

  • 改了什么
  • 谁改的
  • 什么时候改的
  • 能不能回到以前的版本
  • 多个人怎么同时改同一个项目

Git 不是简单地备份文件。它记录的是项目随时间变化的历史。每一次改动都有记录,你可以在任意两个版本之间比较差异,也可以回到某个历史版本。

Git 仓库的三层结构

理解 Git,首先要理解三层结构。这是全文最核心的概念。

flowchart TD
    A["工作目录
你正在编辑的文件"] -->|"git add"| B["暂存区
准备进入下一次提交的改动"] B -->|"git commit"| C["本地仓库
已经记录下来的历史"]

工作目录(Working Tree):就是你磁盘上那些正在编辑的文件。你打开编辑器,修改代码,改的就是工作目录里的内容。

暂存区(Staging Area / Index):一个中间层。你通过 git add 把想提交的改动放进来。它的作用是让你选择”哪些改动进入下一次提交”。

本地仓库(Repository):通过 git commit 把暂存区的内容记录成一次提交。这才是 Git 真正保存历史的地方。

一个关键点:

git add 不是上传。
git commit 也不是上传。
它们都还发生在本地。

你的代码从工作目录到暂存区到本地仓库,整个过程都在你自己的电脑上完成。不需要网络,也不需要 GitHub。很多人以为 commit 之后 GitHub 上就变了,其实没有。要让远程仓库看到你的提交,还需要 git push。这个后面再讲。

git status:先看当前状态

不知道下一步该敲什么命令的时候,先 git status

1
git status

它会告诉你几件事:

  • 当前在哪个分支
  • 哪些文件被修改了
  • 哪些改动已经进入暂存区
  • 哪些文件还没被 Git 跟踪
  • 当前分支和远程分支有没有差异

示例输出:

1
2
3
4
5
6
7
8
9
10
11
12
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: README.md

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
modified: src/main.go

Untracked files:
(use "git add <file>..." to include in what will be committed)
config.yaml

这里可以看到:

  • README.md 已经被暂存(staged),准备进入下一次提交
  • src/main.go 被修改了但还没有暂存
  • config.yaml 是新文件,还没被 Git 跟踪

git status 不会修改任何东西。它只是查看。可以放心用。

git add:选择哪些改动进入下一次提交

git add 这个名字有点误导。很多人以为它只是”添加新文件”。其实不是。

git add 的作用是把工作目录中的改动放进暂存区。不管是新文件还是已有文件的修改,都用 git add 来暂存。

1
2
git add README.md
git add src/main.go

也可以一次暂存当前目录下的所有改动:

1
git add .

git add . 要谨慎。它会把当前目录下所有改动都加进暂存区,包括你可能不想提交的临时文件、调试代码。提交之前最好再看一眼 git status 或者 git diff --staged

暂存区的意义是什么?它让你可以把一次提交做得更干净。比如你改了三个文件,但其中两个是修 Bug,一个是加新功能。你可以先 git add 前两个文件,提交一次,再 git add 第三个文件,提交另一次。这样每次提交只做一件事,历史更清晰。

暂存区存在的意义,是让你精确控制每次提交包含哪些改动。
不是所有改动都应该塞进同一个 commit。

git commit:记录一次项目快照

git commit 把暂存区里的内容记录成一次提交。

1
git commit -m "Fix typo in README"

-m 后面跟的是提交说明。这条命令执行后,Git 会在本地仓库里创建一个新的提交记录,包含:

  • 改动了哪些文件
  • 改了什么内容
  • 谁提交的
  • 什么时候提交的
  • 提交说明

commit 可以理解成项目历史上的一个存档点。以后你能回看它、比较它,也能在需要时回到它附近。

有几个关于提交信息的建议:

  • 不要写 updatefix 这种一个词的说明
  • 尽量写清楚这次提交做了什么
  • 一次 commit 尽量只做一件事
1
2
3
4
5
6
7
8
9
# 不好的提交信息
git commit -m "update"
git commit -m "fix bug"
git commit -m "changes"

# 更好的提交信息
git commit -m "Fix incorrect command in README"
git commit -m "Add login validation for empty username"
git commit -m "Update dependencies to fix CVE-2024-1234"

git log 和 git diff:看历史和看差异

提交之前,最好先看看自己改了什么。

git diff 看工作目录中尚未暂存的改动:

1
git diff

git diff --staged 看已经暂存、准备提交的改动:

1
git diff --staged

提交之后,用 git log 看提交历史:

1
git log --oneline

示例输出:

1
2
3
a1b2c3d Fix typo in README
d4e5f6a Add login validation
7890abc Initial commit

--oneline 让每条提交只显示一行,适合快速浏览。如果想看更详细的信息,直接用 git log,会显示完整的作者、日期和提交说明。

提交之前看 diff,是一个很好的习惯。
它能帮你发现很多”我怎么把这个也改了”的问题。

branch:一条开发线

项目主线(通常是 main)代表的是稳定状态。如果你想加一个新功能,但还没写完,直接在主线上改,主线就一直处于不稳定的状态。

分支解决的就是这个问题。它让一组改动先待在另一条开发线上,等准备好了再合回主线。

gitGraph
    commit id: "A"
    commit id: "B"
    commit id: "C"
    branch feature-login
    checkout feature-login
    commit id: "D"
    commit id: "E"

创建并切换到新分支:

1
git switch -c feature-login

如果用的是较老版本的 Git,可能需要这样写:

1
git checkout -b feature-login

两种写法效果一样。git switch 是 Git 2.23 引入的新命令,语义更清晰。git checkout 是老写法,你两个都会见到。

查看当前有哪些分支:

1
git branch

分支名最好能看出意图。feature-loginmy-branch 好得多。有些团队有命名规范,比如 feature/xxxfix/xxxdocs/xxx

这里要澄清一个常见的误解:

branch 不是复制一份完整项目。
它更像是给某条提交历史起了一个名字。

创建一个分支,Git 不会把所有文件拷贝一份。它只是创建了一个指向某个 commit 的引用。非常轻量,几乎不占空间。

另外,mainmaster 指的是同一种东西——项目的默认分支。不同项目叫法不同。Git 早期默认用 master,后来 GitHub 把默认值改成了 main。你两个都会见到,不要以为是不同的概念。

HEAD:你现在站在哪里

HEAD 可以粗略理解为”当前所在位置”。它通常指向当前分支。

flowchart LR
    A["commit A"] --> B["commit B"] --> C["commit C"]
    M["main"] --> C
    H["HEAD"] --> M

当你在 main 分支上提交时,新的 commit 会接到 main 后面,HEAD 也跟着移动。当你切换到 feature-login 分支时,HEAD 就指向 feature-login,新的提交就会接到 feature-login 后面。

1
2
3
4
5
git switch main
# HEAD -> main

git switch feature-login
# HEAD -> feature-login

看懂 HEAD,不是为了炫技。
它能帮你理解”我现在到底在哪个分支上改东西”。

如果 git status 显示 HEAD detached,说明你没有在任何分支上。这种情况通常出现在 checkout 了一个具体的 commit 或 tag 时。对于日常开发,确保自己在一个正常的分支上就好。

merge:把分支合回来

功能分支写完了,需要合回主线。

1
2
git switch main
git merge feature-login

merge 会把另一个分支的改动整合到当前分支。

gitGraph
    commit id: "A"
    commit id: "B"
    branch feature-login
    checkout feature-login
    commit id: "C"
    commit id: "D"
    checkout main
    commit id: "E"
    merge feature-login

如果两边的修改没有冲突——比如你改了文件 A,别人改了文件 B——Git 可以自动处理,生成一个合并提交。

但如果两边改了同一段内容,就会出现冲突。这个下一节单独讲。

Git 里还有 rebase、squash 等整理历史的方式,但这篇先不展开。知道 merge 能把分支合回来就够了。

conflict:Git 不能替人做决定

冲突不是 Git 坏了。冲突只是说明 Git 无法自动判断哪一边应该保留。

比如两个人都改了同一个文件的同一行配置,Git 不知道谁对,就会把两边的版本都放在文件里,等你来决定。

冲突标记大概长这样:

1
2
3
4
5
<<<<<<< HEAD
当前分支的内容
=======
要合入分支的内容
>>>>>>> feature-login

<<<<<<< HEAD======= 之间是当前分支的内容,=======>>>>>>> feature-login 之间是另一个分支的内容。你需要手工改成正确的内容,然后删掉这些标记。

处理冲突的流程:

flowchart TD
    A["出现冲突"] --> B["打开冲突文件"]
    B --> C["手工改成正确内容"]
    C --> D["git add"]
    D --> E["git commit 或 git merge --continue"]

冲突不是错误,而是 Git 在提醒你:这里需要人来判断。

不要怕冲突。冲突是多人协作中正常的一部分。遇到冲突时,先看清楚两边各改了什么,再决定最终应该保留什么。

remote:远程仓库只是一个地址别名

本地 Git 仓库可以独立存在。你完全可以在自己电脑上用 Git 管理代码,不需要联网,也不需要任何远程服务器。

但如果你想和别人协作,或者想把代码备份到服务器上,就需要一个远程仓库。

remote 就是远程仓库的地址。你可以给它起个名字,方便以后引用。

1
git remote -v

示例输出:

1
2
origin  git@github.com:owner/repo.git (fetch)
origin git@github.com:owner/repo.git (push)

这里 origin 只是一个名字。它指向 GitHub 上的某个仓库地址。如果你愿意,也可以把远程仓库叫 companyserverupstream

origin 不是 GitHub,也不是什么特殊的东西。它只是 git clone 时自动帮你设置的默认远程仓库名字。

很多人把 Git 和 GitHub 混在一起,其实它们不是一回事。
origin 也不是 GitHub。它只是一个远程仓库的默认名字。

后面讲 Fork 时,会经常看到 originupstream 两个远程名。先记住一点:它们本质上都是远程仓库的别名,只是一个名字,没有魔法。

clone、fetch、pull、push 分别做什么

本地仓库和远程仓库之间的同步,主要靠这四个命令。

clone

1
git clone git@github.com:owner/repo.git

clone 是把远程仓库完整复制到本地,并自动帮你设置好 origin 这个远程别名。第一次获取一个项目时用。

fetch

1
git fetch origin

fetch 把远程的新提交下载到本地,但不会直接改你的工作分支。它更新的是远程跟踪分支(比如 origin/main),让你先看看远程有什么变化。

pull

1
git pull

pull 通常是 fetch 之后再把远程改动整合到当前分支。具体来说,它会先执行 fetch,然后尝试将远程的改动合并到你当前的分支。

关于 pull 的具体行为,有一点需要注意:默认情况下 pull 会用 merge 来整合,但你也可以配置成用 rebase。不同团队、不同项目的配置可能不同。不要把 pull 的行为想得太绝对。

push

1
git push origin main

push 是把本地的提交推到远程仓库。执行之后,远程仓库才会看到你的新提交。

flowchart LR
    A["本地仓库"] -->|"git push"| B["远程仓库 origin"]
    B -->|"git fetch"| A
    B -->|"git pull = fetch + 整合"| A

一个很重要的区分:

commit 只是提交到本地仓库。
push 之后,远程仓库才会看到你的提交。

很多人分不清 commit 和 push。记住这条线就够了:

flowchart TD
    A["工作目录"] -->|"git add"| B["暂存区"]
    B -->|"git commit"| C["本地仓库"]
    C -->|"git push"| D["远程仓库"]

git addgit commit 都在本地完成。只有 git push 才涉及网络和远程仓库。

一次最普通的本地开发流程

把前面的概念串起来,看看一次普通的开发流程是什么样的。

flowchart TD
    A["clone 仓库"] --> B["新建分支"]
    B --> C["修改文件"]
    C --> D["git status 查看状态"]
    D --> E["git add 暂存改动"]
    E --> F["git diff --staged 检查"]
    F --> G["git commit 记录提交"]
    G --> H["git push 推到远程"]

命令大概是这样:

1
2
3
4
5
6
7
8
9
10
11
12
git clone git@github.com:owner/repo.git
cd repo

git switch -c fix-typo

# 修改文件后
git status
git add README.md
git diff --staged
git commit -m "Fix typo in README"

git push -u origin fix-typo

-u 参数是把本地分支和远程分支关联起来,之后在这个分支上直接 git push 就行,不用每次都指定远程和分支名。

这里 push 的是一个功能分支,不是 main。至于 push 之后怎么让别人审查、怎么合进 main,那就是 GitHub 协作流程的内容了。

Git 做不到什么

Git 能记录你改了什么。但它不负责判断这次改动该不该进入项目主线。

Git 本身不负责这些事情:

  • 需求管理和 Bug 跟踪
  • 权限审批和访问控制
  • 代码审查流程
  • CI 检查结果展示
  • Pull Request 流程
  • 维护者是否接受你的改动
  • 主分支保护规则

这些能力是 GitHub、GitLab、Gitea 这类平台提供的。它们没有把 Git 变成另一个东西,而是在 Git 之上加了一套协作机制。

Git 管版本,平台管协作。这两层不是替代关系。

常见误区

最后列几个新手最容易犯的误解。

1. 以为 Git 就是 GitHub

Git 是版本控制工具,运行在本地。
GitHub 是代码托管和协作平台,运行在云端。
它们不是一回事。

2. 以为 commit 就是上传

commit 是在本地仓库记录一次快照。
push 才是推送到远程仓库。
commit 之后 GitHub 上什么都不会变。

3. 以为 git add 只是添加新文件

git add 也用于暂存已修改的文件。
它的作用是把改动放进暂存区,不管是新文件还是旧文件的修改。

4. 以为 branch 是复制一份完整代码

branch 只是一个指向某个 commit 的引用。
创建分支不会复制项目文件,非常轻量。

5. 以为 origin 是 GitHub

origin 只是远程仓库的默认名字。
它是 git clone 时自动设置的,你完全可以改名或添加其他远程仓库。

6. 以为 pull 永远安全

pull 会把远程改动整合进当前分支。
如果本地有未提交的改动,可能出现冲突或意料之外的历史变化。
执行前最好先 git status 看清当前状态。

7. 以为冲突是 Git 出错

冲突不是错误。冲突是 Git 在告诉你:两边都改了同一个地方,我没法自动判断,请你来决定。

总结:Git 管版本,平台管协作

回到最开始的问题:Git 到底在管理什么?

Git 管的是代码变化的历史。

  • 工作目录、暂存区、本地仓库是 Git 最基础的三层。改动从工作目录到暂存区(git add),再从暂存区到本地仓库(git commit)。
  • commit 记录一次改动。它是在本地发生的,不会自动传到任何远程。
  • branch 是一条开发线。它不是复制代码,而是给提交历史起个名字。
  • merge 把开发线合回来。如果两边改了同一处,会出现冲突,需要人来处理。
  • remote 是远程仓库的地址。origin 只是默认名字,不是 GitHub 本身。
  • fetch、pull、push 负责本地和远程之间的同步。只有 push 才会把提交推到远程。

到这里,我们已经知道 Git 怎么记录代码变化,也知道本地仓库和远程仓库怎么同步。

但真正多人协作时,还会遇到另一个问题:

谁来决定一个分支能不能合进主线?Bug 和需求放在哪里讨论?没有仓库写权限的人怎么贡献代码?

这些问题,就不是 Git 本身解决的了。下一篇再看 GitHub 这类平台在 Git 之外做了什么。

参考资料

  • 标题: Git 到底在管理什么?从 commit、branch 到 remote
  • 作者: Kaku
  • 创建于 : 2026-05-12 10:00:00
  • 更新于 : 2026-06-09 18:26:48
  • 链接: https://www.kakunet.top/2026/05/12/Git-到底在管理什么?从-commit、branch-到-remote/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论