git 常见的使用场景
git 命令学习
git commit 与 git notes
虽然 git commit 和 git notes 都与提交信息有关,但它们的设计初衷和存储方式有着本质的区别。简单来说:Commit 是历史的“骨架”,而 Note 是贴在骨架上的“便利贴”。
以下是它们的详细对比:
1. 核心定义与永久性
-
Git Commit(提交):
-
它是 Git 的基本组成单元。每个 Commit 包含指向树对象的指针、作者信息、时间戳以及提交说明(Commit Message)。
-
不可变性: 一旦生成,Commit 的任何内容(包括消息)都不能直接修改。如果要改,就会生成一个新的 Commit ID(哈希值),这会改变项目历史。
-
-
Git Notes(附注):
-
它允许你为现有的 Commit 附加额外的信息,而无需修改 Commit 本身。
-
灵活性: 你可以在不改变 Commit ID 的情况下,随时添加、删除或更新 Note。
-
2. 关键区别对比表
| 特性 | Git Commit Message | Git Notes |
| 修改后果 | 会改变 Commit ID,破坏历史连续性 | 不改变 Commit ID,对现有历史无影响 |
| 存储位置 | 直接包含在 Commit 对象中 | 存储在独立的引用中(通常是 refs/notes/commits) |
| 主要用途 | 记录代码变更的原因和逻辑 | 添加元数据、代码评审意见、构建状态等 |
| 传输行为 | 默认随 git push/pull 同步 | 默认不同步(需显式推送 refs/notes/*) |
| 可见性 | git log 默认显示 | 需 git log --show-notes 或配置后显示 |
3. Git Notes 的典型应用场景
因为 Notes 不破坏哈希值的特性,它通常用于以下“补充说明”的场景:
-
持续集成 (CI/CD): 自动将测试结果、构建编号或部署链接附加到对应的提交上。
-
代码评审: 在不重新提交代码的情况下,将评审者的意见记录在对应的 Commit 上。
-
修正信息: 如果发现某个旧的提交信息写错了,但代码已经推送到公共分支无法
amend,可以用 Note 补上一句说明。 -
Bug 追踪: 将 Bug 数据库中的工单链接关联到代码提交。
4. 常用操作指令
-
添加/编辑 Note:
git notes add -m "这是一条附加说明" <commit-id> -
查看 Note:
直接运行
git log(较新版本默认显示)或使用:git show <commit-id> -
同步 Note:
Notes 不会自动推送,你需要像推送分支一样操作:
git push origin refs/notes/* git fetch origin refs/notes/*:refs/notes/*
总结建议
-
优先使用 Commit Message: 描述代码变更的核心逻辑应当始终放在 Commit Message 中。
-
慎用 Notes: 除非你需要记录一些与代码逻辑无关、且可能随时间变化的元数据,否则不要依赖 Notes,因为它在团队协作中的可见性(同步成本)比 Commit 低得多。
checkout
checkout 这个词在英语里的本意是“(从图书馆)借出图书”、“(在酒店)退房/结账”或“(在超市)结账买单”。 它在 Git 诞生之初的底层设计里,代表的是一个物理实体的提取与覆盖(物理搬运):
“我要把这个 Commit 里的所有文件,全部「借(检)出来」放进我的工作区,直接覆盖我眼前的桌子。”
这就导致了旧版 checkout 极其割裂和危险的用户体验:
-
切换分支(安全):
git checkout dev(只是换个分支,安全)。 -
覆盖文件(极危):
git checkout -- main.c(没有任何警告,直接用版本库的内容覆盖你写了一下午的main.c,代码瞬间物理蒸发!)。
对新手来说,这简直是灾难:“我以为我只是像往常一样 checkout(切换视角)看一下,结果我刚写完的代码怎么全没了?!”
是的,你这个概括极其精准!
git checkout 就是这两个风马牛不相及的功能被强行塞进了一个命令里。我们拆开来看:
1️⃣ 切换视角(操作“指针/分支”)
-
命令:
git checkout master或git checkout <commit-hash> -
本质:移动
HEAD指针,让你“切换到”另一个分支或历史节点去查看/开发。 -
效果:改变的是你当前工作的上下文(整个项目的文件都会切换成那个分支的样子)。
-
安全性:相对安全(只要不带着脏工作区强制切换)。
2️⃣ 覆盖文件(操作“文件内容”)
-
命令:
git checkout -- v4l2-dev.c或git checkout master -- v4l2-dev.c -
本质:用 仓库里(某个 Commit/分支)的特定文件,去强制覆盖你 工作区(WD) 里的同名文件。
-
效果:只影响你指定的那个文件,不切换分支。
-
危险性:非常危险! 因为你工作区里所有没提交的修改会被直接覆盖掉,且无法找回。
🔥 为什么会被拆分成新命令?
git restore 和 git switch 命令是在 Git v2.23.0 版本中正式引入的。这次更新于 2019年8月16日 发布,是Git发展历程中一次重要的命令拆分。在 Git 2.23 版本之前,git checkout 是一个职责过重的“多功能”命令,主要干了两种截然不同的事:
-
切换分支(如
git checkout master) -
恢复文件(如
git checkout -- <file>)
正是因为这两个功能长得太像,导致无数新手敲错命令(比如想切分支,结果手滑把文件覆盖了),Git 官方在 2.23 版本后将其彻底拆分:
| 你要做的事 | 旧命令 (git checkout) | 新命令 (推荐) |
|---|---|---|
| 切换视角(换分支/看历史) | git checkout master | git switch master |
| 覆盖文件(恢复单个文件) | git checkout -- file | git restore file |
💎 一句话记住它的“今天”
git checkout现在已经退居二线了。只有在你要“查看某个历史提交的快照(分离头指针)”这种极少数场景下,它还算顺手;日常切换分支请用git switch,恢复文件请用git restore。你问的这两个作用,正是它被“肢解”的根本原因。现在你用
switch和restore,就像用两把专用的螺丝刀,再也不用担心拿错刀头伤到手了。
使用场景
1. 添加远程 push 目的仓
如果你想补上“另一个源头”(比如 GitHub)
如果当初你的本意是想同时托管到 Gitee 和 GitHub,现在可以手动补加。假设你想把 GitHub 的命名为 github:
git remote add github https://github.com/你的用户名/你的远程仓库名.git
添加完成后,再用 git remote -v 检查,你就会看到两个了(一个是 origin 指向 Gitee,一个是 github 指向 GitHub)。
如果 github 这个远程名字已经存在:
你不需要删除它,直接修改它指向的地址即可(把令牌带进去):
git remote set-url github https://<你的令牌>@github.com/远程仓库名.git
执行完再用 git remote -v 检查,URL 就更新好了,可以直接推送。
也可以先删除,再重新添加
如果你觉得之前添加的配置比较乱,想彻底删掉重来:
1. 删除名为 github 的远程仓库:
git remote remove github
(remove 也可以换成 rm,效果一样)
2. 重新添加(这次确保 URL 带令牌):
git remote add github https://<你的令牌>@github.com/远程仓库名.git
2. 远程推送
# 其中 :<远程分支名> 可以省略,省略时表示本地与远程分支同名
# [参数1] [参数2]
git push <远程仓库名> <本地分支名>:<远程分支名> [选项参数]
1. 解析 [参数1]:远程仓库名
-
作用:告诉 Git “我要把代码送到哪个服务器去”。
-
来源:必须是在
git remote -v中显示的名字,比如你这里的origin或未来的github。 -
你的例子:
git push origin main中,origin就是参数1,代表推送到 Gitee。
2. 解析 [参数2]:分支映射关系(Refspec)
这是最核心、最灵活的部分,它包含三种写法:
-
写法 A(最常用——省略远程分支名):
git push origin main格式解析:参数2只有
main。
含义:将本地的main分支,推送到远程的main分支(同名)。 -
写法 B(完整指定——本地与远程名字不同):
git push origin main:master格式解析:
main是本地分支名,master是远程分支名。
含义:将你本地的main分支内容,推到远程仓库的master分支里(常用于将本地特性分支合并到远程主分支)。 -
写法 C(特殊——删除远程分支):
git push origin :feature/branch格式解析:冒号左边为空。
含义:删除远程仓库里的feature/branch分支。 -
当参数2不是分支名时
如果参数2写的是
HEAD,比如:git push origin HEAD这里的
HEAD是指“当前所在的本地分支”,Git 会自动把当前分支推送到远程同名分支。这是一种偷懒且安全的写法,避免切错分支推送错地方。
3. 解析 -u 或 -f 等选项参数(非位置参数)
这些是附加标志,不占位置参数名额,可以写在开头或结尾:
-
-u(--set-upstream):推送的同时,建立本地分支和远程分支的追踪关系。第一次推送时必加。
git push -u origin main(此时-u是选项,origin是参数1,main是参数2)。 -
-f(--force):强制覆盖远程仓库,高危操作。
git push origin main -f或git push -f origin main。 -
--all:推送所有本地分支到远程同名分支。
git push origin --all(此时没有参数2,因为--all本身就是动作指令)。
git push origin main # 推给 Gitee
git push github main # 推给 GitHub
最后
-
git push origin main依然会推给 Gitee(因为origin就是 Gitee)。 -
直接敲
git push依然只推给 Gitee(因为只有origin被设为了默认的追踪上游),绝不会自动推给 GitHub。GitHub 需要你显式指定git push github main才会推送。
成功
PS C:\Users\EricEdward\Desktop\Linux\04_xxx> git push github
Enumerating objects: 12640, done.
Counting objects: 100% (12640/12640), done.
Delta compression using up to 12 threads
Compressing objects: 100% (11175/11175), done.
Writing objects: 100% (12586/12586), 126.73 MiB | 4.85 MiB/s, done.
Total 12586 (delta 1367), reused 11578 (delta 1247), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (1367/1367), completed with 41 local objects.
To https://github.com/xxx.git
3. 前进与后退
为了帮你把零散的知识点沉淀成“肌肉记忆”,我按照你的关键词,帮你提炼出 4 个公式 和 1 张总表,直接背下来就能应对 90% 的日常场景。
🧮 公式一:本质定律(底层逻辑)
git restore和git reset的本质,都是【用本地仓库(某个 Commit)的内容】去【覆盖】工作区(WD)和/或暂存区(SA)的数据。但是从本质上他们却是两种截然不同的工作原理!殊途同归!
-
数据源(Source):只能是本地仓库(HEAD 或其他 Commit)。
-
目标地(Target):工作区(WD)、暂存区(SA),或两者兼有。

📌 图中命令速查
| 命令 | 数据源 | 目标区域 | 效果 |
|---|---|---|---|
git add | 工作区 | 暂存区 | 前进,准备提交 |
git commit | 暂存区 | 本地仓库 | 前进,生成提交 |
git restore <file> | 本地仓库 | 工作区 | 丢弃工作区修改(危险) |
git restore --staged <file> | 本地仓库 | 暂存区 | 撤销 add(安全) |
git restore -SW <file> | 本地仓库 | 暂存区+工作区 | 同时覆盖两个区域(危险) |
git reset --soft HEAD^ | 本地仓库 | 暂存区 | 撤回提交,保留改动(安全) |
git reset --hard HEAD^ | 本地仓库 | 暂存区+工作区 | 强制回退(极度危险) |
💡 核心记忆点
-
所有
restore和reset的“源头”都是本地仓库(HEAD),绝不会从“暂存区”恢复数据到工作区(那是旧版checkout的行为,已废除)。 -
绿色箭头是“提交数据”(正常流程),红色箭头是“恢复数据”(撤销/回退),方向相反。
-
-SW参数是--staged --worktree的简写,如果你需要一次性丢弃某个文件的所有未提交改动,可以用它。
🧮 公式二:职责分工(应用场景)
| 命令 | 应用场景 | 核心职责 | 针对目标 |
|---|---|---|---|
git restore | 未提交(Uncommitted) | 撤回 add 或 丢弃工作区改动 | 只动 文件内容(WD/SA) 不动 指针(历史不变) |
git reset | 已提交(Committed) | 撤回 commit 或 回退历史 | 先动 分支指针(移动 HEAD) 再顺便 同步文件(WD/SA) |
🧮 公式三:restore 命令速查(目标决定参数)
git restore <file> = 仓库(HEAD) → 工作区(WD) (丢弃修改,危险) git restore --staged <file> = 仓库(HEAD) → 暂存区(SA) (撤销 add,安全) git restore -SW <file> = 仓库(HEAD) → 暂存区 + 工作区(双清,极危险)
🧮 公式四:reset 命令速查(模式决定文件是否保留)
git reset --soft HEAD^ = 移动指针到上个提交,改动留在【暂存区】(SA) (安全,保留代码) git reset --mixed HEAD^ = 移动指针到上个提交,改动退回【工作区】(WD) (默认,保留代码) git reset --hard HEAD^ = 移动指针到上个提交,改动【彻底删除】 (极度危险,慎用!)
⚠️ 公式五:可恢复性铁律(安全边界)
只要在 Git 数据库里存在过的 Commit,都能用
reflog追回。 但没执行过git add或git commit的“新代码”,一旦被--hard覆盖,就会永久消失,无法恢复。
-
reset --hard历史回退:可以恢复(因为 Commit 还在数据库里)。 -
reset --hard覆盖新代码:无法恢复(因为代码根本不在数据库里)。
📊 终极知识架构总表
| 对比维度 | git restore | git reset |
|---|---|---|
| 1. 操作对象 | 文件(File) | 分支指针(HEAD) |
| 2. 是否影响历史 | ❌ 不影响(Commit 列表不变) | ✅ 影响(指针移动,历史记录变) |
| 3. 数据流向 | 仓库(HEAD)→ 覆盖目标区域 | 仓库(指定 Commit)→ 覆盖目标区域 |
| 4. 处理阶段 | 提交前(Uncommitted) 的后悔药 | 提交后(Committed) 的时光机 |
| 5. 主要功能 | ① 撤销 add ② 丢弃工作区修改 | ① 撤销 commit(软/混合) ② 强行回退历史(硬) |
| 6. 危险等级 | 不带参数(危险,丢工作区修改) 带 --staged(安全) | --soft/--mixed(安全,可恢复) --hard(极危,慎用) |
| 7. 恢复依据 | 无法恢复(覆盖的是未跟踪/未提交的数据) | 可以用 git reflog 恢复已删除的 Commit |
💎 总口诀(背下来)
改错之前先看状态(
git status), 还没add用restore, 已经commit用reset,--hard出手前必须看一眼还有没有没保存的代码!
🧠 总结对比
| 命令 | 移动的对象 | 影响的范围 | 是否改变历史 | 现代替代品 |
|---|---|---|---|---|
git reset | 分支指针(如 master) | 可能影响历史 + 文件 | 是(回退/改变历史) | 无(依然保留) |
git checkout | HEAD 指针(换分支/换提交) | 文件内容切换,历史不变 | 否(只是切换视角) | git switch |
git restore | 不动指针 | 只覆盖文件内容(WD/SA) | 否 | 本身就是新版 |
| 名称 | 是否是指针? | 指向谁? | 会不会变? |
|---|---|---|---|
| 分支名 (master/hotfix) | ✅ 是(引用) | 指向某个 Commit 对象 | ✅ 会变(每次 git commit 或 reset 时移动) |
| Commit 对象内部 | ✅ 包含指针 | 指向它的父 Commit(即上一个版本) | ❌ 不会变(Commit 一旦生成,内容永远固定) |
| HEAD | ✅ 是(引用) | 指向当前分支名(或直接指向 Commit) | ✅ 会变(git checkout/switch 时移动) |
💎 一句话终极定论
checkout确实能切换 HEAD,但现在请改用git switch来切换分支,用git restore来恢复文件。 老的checkout就像一把瑞士军刀,啥都能干但容易割手;switch和restore是两把专用螺丝刀,更安全、更清晰。至于
reset,它永远是那个“改变历史”的重型机械,跟“切换视角”的checkout/switch有着本质的哲学差异。
更多推荐




所有评论(0)