基础知识
git如同一颗树,每个树上都可以有多个分支,每个分支上又可以有多个节点。
- 每个 commit 是你整个工作区所有文件的一个完整快照(snapshot), 但 Git 使用哈希指向机制让它看起来像是"增量"。
基础命令
git commit 提交一个新节点
git commit:在当前分支,创建一个新的提交记录(一个新的的节点)
git commit --amend
修改最近一次的commit提交,比如忘了添加文件,想要修改提交信息。不适合对已经push到远程仓库的分支执行这样的操作。
AI解释
git commit --amend 是 Git 中一个非常常用且强大的命令,它的作用是修改(或替换)当前分支的最后一次提交,而不是新建一个提交。简单来说:它让你"假装"上一次提交其实是你现在想要的样子,常用于"我刚刚提交错了,想赶紧改一下"这种场景。常见使用场景
-
忘记添加文件(最常见)
git add forgotten-file.txt
git commit --amend
这会把 forgotten-file.txt 加入到上一次提交中,而不是新建一个提交。
-
修改上一次的提交信息(比如写错了 typo、消息不清晰)
git commit --amend
直接运行不加参数,会打开编辑器让你修改上一次的 commit message。或者一行改掉(推荐):
git commit --amend -m "新的正确的提交信息"
-
同时修改文件和提交信息
git add modified-file.txt git commit --amend -m "修复 bug 并添加新功能" -
只改文件,不改消息(消息保持不变)
git add new-file.txt git commit --amend --no-edit
--no-edit 表示不修改提交消息,直接沿用上一次的。
它到底是怎么工作的?
- Git 会把上一次的 commit 内容 + 你现在的暂存区(staging area)内容 + 新的 commit message,重新生成一个全新的 commit 对象。
- 这个新 commit 会完全替换原来的最后一次提交(旧 commit 会被"抛弃",但还没推送的话还能找回来)。
- HEAD 和分支指针会指向这个新的 commit。
重要注意事项(非常重要!)
-
已经推送到远程仓库的提交,尽量不要 amend!
git commit --amend # 改完后 git push --force # 或 git push --force-with-lease
因为你改了历史,其他合作者会出问题(他们的历史和你对不上)。 → 小项目、个人项目、还没别人 pull 的话,可以随便 amend + force push。 → 团队协作中,已推送的提交尽量用 git revert 或新建 commit 修复。
-
如果只是想加文件但不想改消息,最佳实践:
git add . git commit --amend --no-edit -
amend 后查看效果
git log -1 # 看最新的提交 git reflog # 可以看到旧的 commit 还在(万一想找回来)
总结一览表
| 需求 | 命令 |
|---|---|
| 漏加了文件 | git add 文件 → git commit --amend --no-edit |
| 提交消息写错了 | git commit --amend -m "新消息" |
| 既加文件又改消息 | git add 文件 → git commit --amend -m "新消息" |
| 只改消息,不碰文件 | git commit --amend(会打开编辑器) |
| 已 push 过又 amend 了 | git push --force-with-lease(谨慎!) |
一句话总结:
git commit --amend 就是"后悔药",让你能优雅地修改"刚刚那次提交",保持历史整洁。用得越多,你的历史就越干净,越专业!
git branch 节点
- git branch <分支名> :在当前分支,创建一个新分支。
- git branch -f <要移动的分支名>
<想将分支名移动到的位置HEAD~x>:将你指定的分支强行移到,HEAD上面的x个节点- -f(-force)强制
- git branch -f <要移动的分支名>
- git branch -u <远程分支> [本地分支]
-u:--set-upstream-to的简写,git branch --set-upstream-to=<远程分支> [本地分支]<远程分支>:要设置的远程分支(如origin/main)[本地分支]:可选参数,指定要设置的本地分支。如果省略,则使用当前分支- 选择本地分支要跟踪的远程分支
git checkout 移动头部
- git checkout <分支名>: 切换到相应分支/节点
-
git checkout <commit提交的哈希值> :切换到对应的提交节点上。
-
git checkout
<HEAD^>:指向HEAD的上一个节点,有几个^,就先向上指多少节点。 -
git checkout
<HEAD~x>:指向HEAD上面的x个节点 -
git checkout -b <新分支名>:是一个组合命令,它创建 一个新的分支,并立即切换 到这个新分支上。-b选项代表"create a new branch",即创建新分支。git checkout部分负责切换分支。
这个命令相当于连续执行以下两个命令:
git branch <新分支名>(创建分支)git checkout <新分支名>(切换分支)
-
git checkout -b <本地分支名> <远程仓库>/<远程分支名>
- 创建一个本地分支
- 选择其基于哪个远程分支创建
- 将本地分支跟踪你指定的远程分支
- 切换到该远程分支
-
git merge 合并
git merge <分支名>: 创建一个新的节点,将你指定的分支合并到当前分支。注意该节点有两个父节点。
下图中的C4是main分支合并bugFix分支后的新节点,属于main分支

git rebase 拜义父
-
git rebase <分支名>:放弃自己原先的分支,将自己嫁接到其他分支,生成一个新的节点。我愿称为认贼作父。
- git rebase <基分支> <目标分支>
- 基分支 :作为新的基准
- 目标分支 :要被变基的分支
bug修复分支原本是在C2分支上。

- git rebase <基分支> <目标分支>
-
git rebase -i <base_commit>:给你一个编辑器,让你决定自base_commit后的那些提交,有哪些提交,要按哪些顺序复制到当前分支下(从base_commit开始,重新排列,选择节点顺序。并将原先的分支,移到当前)。
git reset 斩断过去,新的开始
git reset <commmit的哈希值>:回到历史的某个节点,在那个历史节点后面commit都将被遗忘。
- 三种参数:
| 命令 | 作用 |
|---|---|
git reset --soft <commit> |
仅移动 HEAD,保留暂存区和工作区 |
git reset --mixed <commit>(默认) |
移动 HEAD + 清空暂存区,工作区不变 |
git reset --hard <commit> |
历史 + 暂存区 + 工作区都回到指定版本(危险) |
- 禁忌:
- 团队开发中一般禁用reset
- rest后再push上去,会破坏git历史。
- 适用场景:
- 还没 push,想:
- 撤销刚提交的代码
- 修改 commit 内容/顺序
- 清理历史,让 commit 更干净
- 还没 push,想:
git revert 取出一份存档开始,但不会删除已有存档
git revert <commmit的哈希值>:在现有git树上,创建一个新的commit节点,这个节点跟你要回退的节点一样。但git树上的旧节点不会发生改变。
-
适用场景:
-
push后:
-
想回退到之前的某版本,切不破坏历史
-
团队协作中,99%都应该使用revert
git cherry-pick 挑选你想要的提交
git cherry-pick <commit的哈希值>:将某些提交(节点)复制到当前分支下
-
适用场景:
-
将部分提交(比如对某个bug的修改)而不是整个分支移到当前分支下。
-
从多人提交中挑特定 commit 合入
-
基本用法
-
把某个 commit 摘取到当前分支:
git checkout target_branch git cherry-pick <commit-hash>。当前分支就会多一个新的 commit(内容和原 commit 相同,但 hash 不一样)。 -
一次摘取多个连续的 commit
git cherry-pick A..B -
意思是将A到B的所有提交复制到当前。注意A必须比B的提交早,且改命令不包含A节点,如果A节点也需要进行复制,用如下命令。
-
git cherry-pick A^..B -
摘取多个不连续的提交:
git cherry-pick A B C
git clone 克隆
git clone :创建一份远程仓库的拷贝。
git fetch 下载远程仓库新的提交
- git fetch:
- 从远程仓库下载本地仓库中缺失的提交记录
- 更新远程分支指针(如
o/main) - 其不会改变你当前的工作区,可以理解为只是下载了远程仓库新的更改,但并未合并。
- git fetch origin <分支名/远程节点>:
- 会将本地仓库里,没有的远程仓库新的提交下载下来,但不会更新引用。
- git fetch origin <分支名/远程节点>:<本地分支>
- 该命令很危险,很少使用
git fetch origin main:refs/remotes/origin/main # 标准例子- 左边 main → 远程仓库的引用(通常是分支)
- 右边 refs/remotes/origin/main → 本地要更新的引用位置(几乎永远是远程跟踪分支)
- git fetch 的冒号语法是用来指定"refspec"的,本质是更新本地仓库中的某个引用(ref),而不是合并到你的本地工作分支!
- 会将本地仓库里,没有的远程仓库新的提交下载下来,直接更新/创建本地分支(不是合并) 。
"refspec 更新" 或 "强制更新引用(force-update a ref)"
它根本不是 merge,而是 直接把本地某个 ref(引用)的指针强行挪到远程下载下来的 commit 上。一句话概括区别:
| 操作 | 是 merge 吗? | 会创建合并提交吗? | 会保留两条历史线吗? | 是否安全? | 实际效果 |
|---|---|---|---|---|---|
| git merge origin/main | 是 | 通常会(非快进时) | 是(两条线汇合) | 非常安全 | 把两条历史真正"合并" |
| git fetch origin main:feature | 不是 | 不会 | 不会 | 非常危险 | 把本地 feature 分支指针"硬搬家"到 origin/main 指向的 commit |
`git fetch origin :<本地分支>`当<分支名/远程节点>或者source为空时,本地仓库如果没有该远程分支,那么本地分支将被创造
git pull 下载远程仓库新的提交+合并
-
git pull:
- 作用等于git fetch +git merge
- 日常开发:先 git fetch,再手动 merge/rebase(专业选手做法)
- 完全信任远程、只想快速同步:git pull --ff-only(最安全的一键方式)
- 永远别干的事:本地改了一堆还没 commit,直接盲目 git pull
git pull --rebase # 最常用,对当前分支
git pull --rebase origin main # 明确指定远程和分支
git pull --rebase --autostash # 工作区有修改时自动暂存,非常方便!
git fetch && git rebase origin/main # 和上面完全等价,只是分两步写
git push 推送你的改变
-
git push:
- 首次推送并与远程分支确认关联
推送当前分支到远程,并建立跟踪关系
git push -u origin 分支名示例:推送当前分支到远程的 feature-branch
git push -u origin feature-branch
2. 推送已有关联的分支,如果分支已经设置了上游分支:
简单推送
git push
或者明确指定
git push origin 分支名
- git push <远程仓库> <本地分支名>将指定的本地分支,推送到远程仓库相应的分支中,此时将不受HEAD的影响
git push origin <source>:<destination>是将本地的source(可以是个分支名,也可以是节点,其可以采用相对引用)推到远程仓库origin的destinnation。- 如果要推送的远程的分支不存在,远程仓库将自动为你创造。
- 当你的source为空时,
git push origin :<destination>,如果你将空推到远程分支(该远程分支在远程仓库存在),那么该远程分支将被删除。
git tag 打标签
- git tag <标签名> <要打上标签的节点>
- 给某个commit打上标签,比如"v.1.0.1"
- commit的哈希值一长串并不好用,打上标签后更容易跳转,识别。
Git Tag 主要有两种类型:
| 类型 | 命令 | 特点 | 常见用途 |
|---|---|---|---|
| 轻量标签 (Lightweight) | git tag v1.0.0 | 只是一个指向 commit 的名字(指针) | 临时标记、私人用 |
| 带注解标签 (Annotated) | git tag -a v1.0.0 -m "正式发布版本" | 包含创建者、日期、消息等元数据,推荐使用 | 正式发布版本(强烈推荐) |
# 1. 创建轻量标签(不推荐正式发布用)
git tag v1.0.0
# 2. 创建带注解标签(强烈推荐)
git tag -a v1.0.0 -m "正式发布 1.0.0 版本"
# 3. 给历史某个 commit 打标签(比如补打标签)
git tag -a v0.9.0 9fceb02 -m "补打 v0.9.0 标签"
# 4. 查看所有标签
git tag
# 5. 查看某个标签的详细信息
git show v1.0.0
# 6. 推送单个标签到远程(默认 git push 不会推送标签)
git push origin v1.0.0
# 7. 一次性推送所有标签
git push origin --tags
# 8. 删除本地标签
git tag -d v1.0.0
# 9. 删除远程标签
git push origin :refs/tags/v1.0.0
# 或者新语法(推荐)
git push --delete origin v1.0.0
# 10. 切换到某个标签(相当于 checkout 到那个版本)
git checkout v1.0.0
# 建议创建分支再开发,避免 detached HEAD 状态
git checkout -b hotfix-1.0.1 v1.0.0
git describe 查看标签
命令格式和输出
基本语法:
git describe [参数] [某个commit]
如果不指定提交,默认使用当前 HEAD。
典型输出格式:
<最近标签>-<提交数量>-g<提交哈希缩写>
各部分解析:
<最近标签>:从当前提交往前找,找到的最近一个标签(通常是附注标签)<提交数量>:当前提交与该标签之间相差的提交次数g:固定字母,只是分隔符(git 的缩写)<提交哈希缩写>:当前提交的缩写哈希值(通常是前7位)
mian ^2 第二个爸爸
表示当前节点的上一个节点,2不表示向上两个节点,而表示该节点的第二个爸爸。一个节点有两个父节点时其会指向,^2将指向第二个父节点。
HEAD
git可以指向分支,也可以指向节点。其通常是指向当前分支最新提交,当其指向其他节点时,被称为分离的HEAD。HEAD他的作用在我看来,是作为操作时的一个基准。
git的工作区,暂存区,本地仓库,远程仓库
其工作流程为
工作区 --git add--> 暂存区 --git commit--> 本地仓库 --git push--> 远程仓库
- 工作区:当前你正在操作的那部分文件,git并不关心
- 暂存区:工作区和仓库的中间的地带。
- 你可以有选择的将修改提交到暂存区,而不是所有。对于不必要的修改,可以不加到暂存区中,只提交你所需要的修改。
- 本地仓库:你的本地git树
- 远程仓库:多人协作的远程git树
rebase与merge的区别
- rebase适合自己一个人用,其会修改树的历史。在团队协作中,不能对main进行rebase。rebase适合对自己开发,运营的分支使用。
- merge适合团队使用。
- 团队中也可以有选择地使用rebase,比如:
- 在发起Pull Request前整理提交
- 个人特性分支更新主分支代码时
- 团队中也可以有选择地使用rebase,比如:
(22 封私信 / 80 条消息) Git:图解 merge 和 rebase 的区别 - 知乎
git远程仓库
远程分支
- 一个只读指针,指向本地仓库上一次与远程仓库同步的节点,其只能通过git fetch和git push 发生改变。你平时开发应该基于它创建本地分支,而不是直接在它上面操作。
- 当远程仓库有其他的commit时,远程分支就和远程仓库最新的commit不同步了,需要通过git fetch来同步。
- 本地的HEAD并不默认指向远程分支,而是当前最新分支。
- 远程分支一般是用来看的,而不是用的。
远程分支的格式
命名规范 :远程分支有一个特殊的命名格式:<remote_name>/<branch_name>。
<remote_name>:通常是origin,这是你克隆仓库时 Git 自动为你创建的远程仓库的默认简称。你也可以添加其他远程仓库,并给它们起别的名字(如upstream)。<branch_name>:远程仓库上分支的实际名字。
在远程分支上commit会导致什么?
当你直接检出一个远程分支(如 git checkout origin/feature),Git会使仓库进入"分离的HEAD"(Detached HEAD)状态。这意味着HEAD(Git用来指向当前所在位置的内置指针)没有指向任何一个本地分支的末端,而是直接指向了一个特定的提交。
-
在此状态下提交 :你是可以执行
git commit操作的。然而,新的提交不会附加到任何分支上[]。这个提交会成为一个"悬空"的提交,在Git的版本图中是孤立的。 -
潜在风险:当你切换回其他分支时,这个没有分支引用的"悬空"提交很可能在后续的垃圾回收中被Git清除,**导致你的工作内容丢失。因此,在这种状态下提交通常是不被推荐的,除非你很清楚自己在做什么(比如临时性的实验)。
🔧 正确的做法:创建并跟踪分支
绝大多数情况下,你的目的是在一个固定的本地分支上工作,并能够方便地将更改推送回远程分支。正确步骤如下:
- 获取远程更新 :首先,使用
git fetch origin获取远程仓库的最新信息,包括所有分支的更新情况。这能让你看到远程是否存在新的分支或提交。 - 创建并切换本地分支 :使用
git checkout -b <本地分支名> origin/<远程分支名>命令。例如,git checkout -b feature origin/feature。这个命令会:- 创建一个新的本地分支(例如
feature)。 - 让这个本地分支自动"跟踪"(track)指定的远程分支(例如
origin/feature)。 - 切换到新创建的本地分支。
你也可以使用git checkout -t origin/feature,它会创建一个与远程分支同名的本地分支并进行跟踪。
- 创建一个新的本地分支(例如
- 在本地分支上提交 :现在,你在新创建的本地分支上工作。此时执行
git commit,你的提交会安全地保存在这个本地分支上。 - 推送更改 :完成提交后,你可以使用
git push将本地分支上的新提交推送到它所跟踪的远程分支。由于设置了跟踪关系,简单的git push就足够了。
如果你的本地分支已经存在并且设置了跟踪远程分支,那么直接切换到该本地分支(例如 git checkout feature)进行工作和提交即可
git fetch的常见工作流程
verilog
git fetch origin # 先把远程最新内容拿下来看看
git log HEAD..origin/main # 查看对方提交了什么(不会影响当前代码)
git diff HEAD origin/main # 或者对比文件差异
# 确认没事后再决定怎么融入本地
git merge origin/main # 或者
git rebase origin/main
偏离的提交历史
-
rebase方案

-
merge方案

-
git pull --rebase方案

锁定的Main(Locked Main)
对于需要代码审查的工程,如何进行提交?
对于需要代码审查的项目,你不能直接将commit push到主支,这将会被拒绝。
新建一个分支feature, 推送到远程服务器。然后重置你的main分支和远程服务器保持一致, 否则下次你pull并且他人的提交和你冲突的时候就会有问题.

本地有多个分支要合并push
-
rebase方案

-
merge方案

杂项/备注
只保留一个提交
假设你为了解决一个bug,创建了一个新的分支,为了进行调试,你往其中加入了一些打印调试信息的代码,并进行了一次提交。然后在此的提交的基础上,完成了真正改写bug的操作,并又进行了一次提交。这时你只想保留改写bug的那个操作到主支,你可以选择用git rebase -i <base_commit> ,又或者是git sherry-pick <commit的哈希值>。

符号
*星号表示当前分支
o 表示远程仓库orgin