%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
# commit 相对引用
| | 说明 | 示例 |
| ------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `<commit/refs>^` | 引用该提交的 "**父提交**" | `HEAD^`: 引用 HEAD 的上一个提交 |
| `<commit/refs>^n` | 当其有**多个父提交**时,指示第 n 个父提交 <br>(例如合并提交) | - `commit^1`:第一个父提交(通常是当前分支的末端)<br>- `commit^2`:第二个父提交(通常是合并进来的分支的末端)<br> |
| `<commit/resf>~[n]` | 引用该提交的 "**第 n 个祖先提交**" | - `HEAD~0` 即 HEAD 本身 <br>- `HEAD~` 等价于 `HEAD~1` 等价于 `HEAD^` <br>- `HEAD~2`:HEAD 之前的第 2 个提交,等价于 `HEAD^^` |
> [!NOTE] `^` 与 `~` 可作用于某一具体 commit(ID),也可用于各类 "引用" 如 HEAD、分支名——本质上仍是作用于引用所指向的提交
<br><br>
# commit 范围区间
| | 说明 | 示例 |
| ------- | --------------------------------------------------- | --------------------------- |
| `A..B` | 引用 "**B 不同于 A 的所有提交(==不包括 A==)**,等价于 `B --not A` | - `git log A..B`:<br> <br> |
| `A...B` | 引用 **A 和 B 的共同祖先之外的所有提交**——即 **A 和 B 的 "不同提交" 的并集** | - `git log A...B` |
> [!NOTE] 要选取 `A` 到 `B` 之间的所有提交,包括 A,可用 `A^..B`。
> [!example] `A..B` 示例
> ![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-985C7FF2C663AD8260A9F7E57ABEE1A8.png|571]]
> [!example] `A...B` 示例
>
> ![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-5EB6A2D809561FB206E4A9852A77129E.png]]
>
<br><br><br>
# 回退至特定提交——git reset ⭐
> 参见:[Git - 重置揭密](https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-%E9%87%8D%E7%BD%AE%E6%8F%AD%E5%AF%86#_git_reset)(生动形象的图例说明)以及 [Git Reset 三种模式 - 简书](https://www.jianshu.com/p/c2ec5f06cf1a)
![[Excalidraw/Excalidraw-Solution/git commit 管理_0.excalidraw.svg|447]]
%%[[Excalidraw/Excalidraw-Solution/git commit 管理_0.excalidraw.md|🖋 Edit in Excalidraw]]%%
版本回退:**令 ==HEAD== 指向当前分支上==某一历史提交==**,并 **==丢弃其之后的所有提交==** 。
有三种方式,差异在于回退时对**工作区**和**暂存区**的处理:
| | 方式 | 说明 |
| ------------------------------ | ---------------- | ----------------------------------------------------------------------- |
| `git reset [--mixed] <commit>` | **混合重置**<br>(默认) | 仅**清空==暂存区==**,保持**当前工作区不变** |
| `git reset --soft <commit>` | **软重置** | **==不重置/不改变==** 暂存区和工作区(仅回退 HEAD 指向而**保留当前所有更改,可供重新提交**) |
| `git reset --hard <commit>` | **硬重置** | 同时**重置==暂存区&工作区==** ——即 **==丢弃==当前==所有修改==**,**工作区重置为与 HEAD 新指向的提交匹配**。 |
> [!danger] 若当前不存在任何历史 commit,则 `git reset --hard` 将==删除整个工作区中所有已跟踪文件==
![[Excalidraw/Excalidraw-Solution/git commit 管理.excalidraw.svg|435]]
%%[[Excalidraw/Excalidraw-Solution/git commit 管理.excalidraw.md|🖋 Edit in Excalidraw]]%%
## 示例说明
- `git reset HEAD^` 或 `git reset HEAD~1`:**回退当前分支到上一个提交**,**清空暂存区**,但**保留工作区内容**
- `git reset --hard HEAD~2`:**回退两个提交**(**撤销这两个提交中的所有变更**)
> [!example] 示例说明 [^2]
>
>
> 初始时:
> ![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-FC6A51104D87CE77A599BD81A57F30CC.png|345]]
>
> (1)执行 `git reset HEAD^` (混合重置/**默认选项**)
>
> ![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-C22A5A1D84570E45B3FEE9B4CCCD9F0A.png|337]]
>
>
> (2)执行 `git reset --soft HEAD^`(软重置)
>
> ![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-E0F22720D2226A1F6B0862B524AC830A.png|338]]
>
>
> (3)执行 `git reset --hard HEAD^` (硬重置)
>
> ![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-28997508731808D12FED1AD04F4969D5.png|338]]
>
> [!example] 示例:使用 `git reset soft` "压缩/合并" 多个提交
>
> 初始时:
>
> ![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-161D000D6DB22F3B5370C83124115837.png|356]]
>
>
> 执行 `git reset --soft HEAD~2` (保留工作区/暂存区不变)
>
> ![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-9ED0083841A5F6698266E9346C59DD04.png|382]]
>
> 重新提交(原先两次提交中的更改被压缩/合并成了这一新提交)
>
> ![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-E6E236A2DD01CEB839F8CBC054F7844F.png|375]]
>
<br><br><br>
# 撤销既有提交——git revert
`git revert` 用于**撤销 "指定提交" ==所引入的修改==**,其会**产生一个新的提交**并在该提交中**消除了 "指定提交" 所引入的变更**。
`git revert` 并不会影响其他版本的修改,仅仅**针对指定的提交做了一个 undo 操作**。
`git revert [option] <commit>...`
> [!NOTE] 执行 `git revert` 时要求工作区&暂存区干净,若存在 "未暂存的修改或未提交的暂存" 时将会报错
>
> ![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-D48BE6B6C521259F387124A97420DB5C.png]]
>
>
<br><br><br>
# 拣选复制特定提交——git cherry-pick ⭐
`git cherry-pick` 用于 **==拣选一个或多个特定提交==** 应用到**当前分支**,**为每个拣选的提交==依次产生一个对应的新提交==**。
`git cherry-pick [options] <commit>...`:将**指定提交**应用到**当前分支**——**产生新的提交**
- `-e| --edit`:编辑**提交信息**
- `-x`:在**提交信息末尾自动添加一行** "**cherry picked from commit ...**" 内容(便于查看该提交是如何产生的)
- `-n | --no-commit`:拣选**应用于==工作区&暂存区==**,**不产生提交**(可用于**拣选合并多个提交的内容**,而后手动进行一次提交)
- `<commit>...` :支持**指定单个、多个不连续的 commit 或者范围形式**如 `<commit1>..<commit2>`
- `-X <strategy>`:指定**产生合并冲突**时的策略,可选项为
- `ours`:选择当前分支的更改。
- `theirs`:选择 cherry-pick 提交中的更改。
> [!example] 常见使用场景——**合并多个修复到当前分支**
>
> 在 `Feature` 分支做了几个重要的修复 or 特性,并希望**将这些修复应用到当前分支**:
>
> ![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-0FD51CE432235ED288A3C1F38FB74276.png|775]]
> [!example]
>
> ![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-AF33E427E3DEEA01B36C747D8E1606A0.png|917]]
>
>
>
### 冲突处理
在 cherry-pick 应用过程中,当**某一 commit 存在==合并冲突==** 时,可应用下列命令进行处理:
- `git status`:检查**当前合并状态,冲突文件**;
- `git cherry-pick --continue`:需**手动消除冲突**后**通过 git add 添加到暂存区**,再通过该命令**指示继续进行**。
- `git cherry-pick --skip`:**直接跳过当前冲突的 commit**;
- `git cherry-pick --abort`:**中止拣选过程,恢复到 rebase 前的状态**;
<br><br><br>
# 检出特定提交——git checkout
> `git checkout` 是一个多功能命令,可用于**切换分支、检出文件或恢复文件到某个特定状态**。
> 在较新 Git 版本中, `checkout` 的部分功能**已有等价的 `git switch` 和 `git restore` 命令实现**。
`git checkout [OPTION] <commit> [<pathsepc>...]` :检出**特定的提交、标签或分支**,**恢复到工作区**
使用示例:
```shell
# 撤销工作区中未暂存的修改, 恢复至暂存区或上一版提交
git checkout -- [file]
# 将指定分支下指定文件的最新版本应用/覆盖至当前"工作区"&"暂存区";
git checkout [source_branch] -- [file]
# 等价的git restore 命令(Git 2.23+版本)
git restore --source [source_branch] -- [file]
# 将指定分支下指定文件的最新版本应用/覆盖至仅当前"工作区"
git checkout [source_branch] -- [file] && git reset
# 等价命令:
git show [source_branch]:[file] > [file] # 打印指定分支的文件内容并覆盖当前文件
# 检出特定提交
git checkout adb1234
# 检出特定tag
git checkout v0.1
# 切换到branch分支
git checkout branch
# 创建并切换到新分支brach
git checkout -b branch
# 检出远程跟踪分支, 创建同名的本地分支作为跟踪分支
git --track origin/some_branch
```
> [!NOTE]
>
> 检出一个特定提交(而不是分支)会使仓库进入 **分离头指针状态**(Detached HEAD)。
> 在该状态下,可**浏览历史提交**或**在==某个特定提交的基础上创建新的分支==**。
![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-6810128840ECBDB460597293BE0BDAB0.png|725]]
# 待定——git reflot
#TODO
![[_attachment/05-工具/git 使用/git commit 管理.assets/IMG-git commit 管理-160410F50D6BDA10AEF42432B37C3792.png|661]]
<br><br><br>
# commit 管理总结 🌠
| 需求 | 相应命令 |
| ------------------------------------------ | -------------------------------- |
| **回退**到**某一历史提交**,**丢弃其后所有提交** | `git reset --hard` |
| **合并从某一提交起至今的所有提交** | `git reset` 或 `git reset --soft` |
| | |
| **合并**提交历史 "**中间的某一段提交**",同时**保留其后所有提交历史** | `git rebase -i` |
| **修改**提交历史中**某一次 commit 的提交消息** | `git rebase -i` |
#### ❓修改历史 commit 内容
> 参考: https://segmentfault.com/a/1190000022926064
仅修改历史 commit 的**提交信息**:
```shell
# 修改最近n次commit
git rebase -i HEAD~n
# 在编辑器中将提交ID前的pick改为r(reword), 关闭编辑器
# 在新打开的编辑器中, 修改commit的提交信息, 关闭编辑器
# (完成, 不需要rebase continue, 此时仍位于最新的commit状态)
```
**修改并重新提交**某个历史 commit:
```shell
# 修改最近n次commit
git rebase -i HEAD~n
# 1. 在编辑器中将待修改的commit ID前的pick改为e(edit)
# 2. 关闭编辑器, 进行源码调整等处理
# 3. 执行以下命令:
git commit --amend -m "..."
git rebase --continue
```
<br>
%%
#### 合并 commit
合并几个 commit——`git rebase -i <commit>` ,其中 `<commit>-id` 是**需要合并的提交中的最早的 commit** 的“**父commit**"
> 除了使用--no-commit 在合并以前就取消提交以外,如果你已经合并了,但是不想提交,可以先 git commit 然后 git log 找到上一个版本 commit id(下文用 xxxx 指代) git reset xxxx 然后再 git add .
> git commit --amend 就可以把合并进来的内容,放进之前的 commit 中了
%%
# 参考资料
# Footnotes