git 命令学习

git commit 与 git notes

虽然 git commitgit notes 都与提交信息有关,但它们的设计初衷和存储方式有着本质的区别。简单来说:Commit 是历史的“骨架”,而 Note 是贴在骨架上的“便利贴”。

以下是它们的详细对比:

1. 核心定义与永久性

  • Git Commit(提交):

    • 它是 Git 的基本组成单元。每个 Commit 包含指向树对象的指针、作者信息、时间戳以及提交说明(Commit Message)

    • 不可变性: 一旦生成,Commit 的任何内容(包括消息)都不能直接修改。如果要改,就会生成一个新的 Commit ID(哈希值),这会改变项目历史。

  • Git Notes(附注):

    • 它允许你为现有的 Commit 附加额外的信息,而无需修改 Commit 本身

    • 灵活性: 你可以在不改变 Commit ID 的情况下,随时添加、删除或更新 Note。


2. 关键区别对比表

特性Git Commit MessageGit 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 mastergit checkout <commit-hash>

  • 本质:移动 HEAD 指针,让你“切换到”另一个分支或历史节点去查看/开发。

  • 效果:改变的是你当前工作的上下文(整个项目的文件都会切换成那个分支的样子)。

  • 安全性:相对安全(只要不带着脏工作区强制切换)。

2️⃣ 覆盖文件(操作“文件内容”)

  • 命令git checkout -- v4l2-dev.cgit checkout master -- v4l2-dev.c

  • 本质:用 仓库里(某个 Commit/分支)的特定文件,去强制覆盖你 工作区(WD) 里的同名文件。

  • 效果:只影响你指定的那个文件,不切换分支

  • 危险性非常危险! 因为你工作区里所有没提交的修改会被直接覆盖掉,且无法找回。


🔥 为什么会被拆分成新命令?

git restoregit switch 命令是在 Git v2.23.0 版本中正式引入的。这次更新于 2019年8月16日 发布,是Git发展历程中一次重要的命令拆分。在 Git 2.23 版本之前,git checkout 是一个职责过重的“多功能”命令,主要干了两种截然不同的事:

  1. 切换分支(如 git checkout master

  2. 恢复文件(如 git checkout -- <file>

正是因为这两个功能长得太像,导致无数新手敲错命令(比如想切分支,结果手滑把文件覆盖了),Git 官方在 2.23 版本后将其彻底拆分:

你要做的事旧命令 (git checkout)新命令 (推荐)
切换视角(换分支/看历史)git checkout mastergit switch master
覆盖文件(恢复单个文件)git checkout -- filegit restore file

💎 一句话记住它的“今天”

git checkout 现在已经退居二线了。只有在你要“查看某个历史提交的快照(分离头指针)”这种极少数场景下,它还算顺手;日常切换分支请用 git switch,恢复文件请用 git restore

你问的这两个作用,正是它被“肢解”的根本原因。现在你用 switchrestore,就像用两把专用的螺丝刀,再也不用担心拿错刀头伤到手了。

使用场景

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 restoregit 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^本地仓库暂存区+工作区强制回退(极度危险)

💡 核心记忆点

  • 所有 restorereset 的“源头”都是本地仓库(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 addgit commit 的“新代码”,一旦被 --hard 覆盖,就会永久消失,无法恢复。

  • reset --hard 历史回退:可以恢复(因为 Commit 还在数据库里)。

  • reset --hard 覆盖新代码无法恢复(因为代码根本不在数据库里)。


📊 终极知识架构总表

对比维度git restoregit 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), 还没 addrestore 已经 commitreset --hard 出手前必须看一眼还有没有没保存的代码!

🧠 总结对比

命令移动的对象影响的范围是否改变历史现代替代品
git reset分支指针(如 master)可能影响历史 + 文件(回退/改变历史)无(依然保留)
git checkoutHEAD 指针(换分支/换提交)文件内容切换,历史不变(只是切换视角)git switch
git restore不动指针只覆盖文件内容(WD/SA)本身就是新版
名称是否是指针?指向谁?会不会变?
分支名 (master/hotfix)(引用)指向某个 Commit 对象✅ 会变(每次 git commitreset 时移动)
Commit 对象内部包含指针指向它的父 Commit(即上一个版本)不会变(Commit 一旦生成,内容永远固定)
HEAD(引用)指向当前分支名(或直接指向 Commit)✅ 会变(git checkout/switch 时移动)

💎 一句话终极定论

checkout 确实能切换 HEAD,但现在请改用 git switch 来切换分支,用 git restore 来恢复文件。 老的 checkout 就像一把瑞士军刀,啥都能干但容易割手;switchrestore 是两把专用螺丝刀,更安全、更清晰。

至于 reset,它永远是那个“改变历史”的重型机械,跟“切换视角”的 checkout/switch 有着本质的哲学差异。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐