%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
# 贮藏工作区&暂存区的修改——git stash ⭐
`git stash` 用于保存**当前工作目录中的未提交更改**——即"**==当前工作区和暂存区==**" 中的修改。
用法小结参考 [^1]。
##### 查看 stash 列表
- `git stash list` 查看所有 stash 条目
> [!NOTE] stash 条目说明
>
> ![[_attachment/05-工具/git 使用/git 使用.assets/IMG-git 使用-734E47A54F4701271464080D00932DB0.png]]
>
> - `stash@{n}`: 条目标识符,**其中 `stash@{0}` 为最新存储的内容**(stash 类似于栈结构)
> - `WIP`:含义为 "**Work In Progress**",即 “**正在进行的工作**”。
> - `WIP on <branch>: <commit> <msg>`: 指明了**该 stash 贮存的修改是在 "==哪个分支的哪个提交之上==" 创建的**
##### 保存 stash
- `git stash [push]`:保存 "**==当前工作区与暂存区修改==** "到 stash,清空暂存区,恢复工作区同上一版 commit
- `--keep-index`: 贮藏后**保留暂存区内容**,仅清空工作区。
- `-u | --include-untracked`:**贮藏内容包括未跟踪文件**(默认只贮藏**已跟踪文件的修改、暂存**)
- `-m <msg>`: 附带**备注信息**
- `--all`:将所有更改(包括未跟踪和忽略的文件)存储
![[_attachment/05-工具/git 使用/git 使用.assets/IMG-git 使用-B2EE02C2EB7DA5AE0FD801746D91D802.png]]
##### 弹出 stash
- `git stash pop [<stash@{n}>]` :恢复 "**最近或指定 stash 中的更改**"到工作区,同时**移除该条 stash 记录**。
- `--index`:将 **==原属于暂存区的改动==恢复到暂存区**(默认是**将原工作区与暂存区的改动都==恢复到工作区==**,冲突时保留工作区,而暂存区为空)
##### 应用 stash
- `git stash apply [option] [<stash@{n}]` :恢复 "**最近或指定 stash 中的更改**"到工作区,但**仍保留该 stash 记录**。
- `--index`:将 **==原属于暂存区的改动==恢复到暂存区**(默认是**将原工作区与暂存区的改动都==恢复到工作区==**,冲突时保留工作区,而暂存区为空)
![[_attachment/05-工具/git 使用/git 使用.assets/IMG-git 使用-798BD21E79F28283CB0FACFFE6754C7D.png|505]]
##### 应用 stash 创建新分支
- `git stash branch <branchname> [<stash@{n}>]`
##### 删除 stash 条目
- `git stash drop [<stash@{n}>]` 移除指定的 stash 条目
- `git stash clear` 清除所有 stash 条目
![[_attachment/05-工具/git 使用/git 使用.assets/IMG-git 使用-B2F81C613C1F7FE4C39A7DF552552CC6.png|475]]
<br><br><br>
# 撤销工作区/暂存区的修改——git restore⭐
**撤销文件的修改**,有以下三种情况[^2]:
- <font color="#548dd4">Discard unstaged changes in working directory</font>——**撤销 ==工作区中未暂存的修改==,保持暂存区不变**<br>(若暂存区有该文件的修改,则**工作区**将恢复至**暂存区状态**,否则恢复至**上一版提交状态**)
- `git restore [-W | --worktree] <file>`
- `git checkout -- <file>` (等价)
- <font color="#548dd4">Unstage (undo git add)</font>——**撤销 ==暂存区== 中的记录,保持工作区文件状态不变**
- `git restore <-S | --staged> <file>`
- `git reset [HEAD] <file>`(等价)
- <font color="#548dd4">Reset</font>——**重置==工作区&暂存区==所有修改,回退至上一版提交状态**
- `git restore --worktree --staged <file>`
- `git reset --hard [HEAD] <file>`(等价)
若要撤销**所有文件**的修改,则上述命令省略 `<file>` 即可。
> [!NOTE] 等价命令 `git restore <file>` 与 `git checkout -- <file>` 的效果
>
> ![[_attachment/05-工具/git 使用/git 工作区&暂存区管理.assets/IMG-git 工作区&暂存区管理-2D48C3D7C20C3FBA37B90AF4CA8E8A5A.png|443]]
>
> ![[_attachment/05-工具/git 使用/git 工作区&暂存区管理.assets/IMG-git 工作区&暂存区管理-F7E1C37A91D9C0B30E335623B093156E.png|587]]
>
### git restore 命令
![[_attachment/05-工具/git 使用/git 使用.assets/IMG-git 使用-2AD5FF5F168D652BCA0FDDCB26E7B256.png|565]]
`git restore [option] <file>` :恢复**文件内容**为 "**特定状态**"——指定提交,指定分支。
选项:
- `[-s|--soucre <tree>]` :指定 **==恢复至的版本==**,**默认为 `--source=HEAD` 即最后一版提交**
- `<tree>` 可以是指定 commit、分支、或者 tag
- `[-W|--worktree][-S|--staged]`:**指定==进行恢复==的位置**——**工作区 or 暂存区**
- (1)**均未指定时,默认为 `--worktree`**
- —— **撤销 ==工作区中未暂存的修改==,保持暂存区不变**——Discard changes in working directory
- (2)**仅指定 `--staged`** 时
- ——**撤销 ==暂存区==中该文件的暂存记录,保持工作区不变**——Unstaging;
- (3)**同时指定 `--worktree` 与 `--staged`** 时
- ——**撤销文件在==暂存区、工作区==的更改**,完全恢复至上一版提交。
> [!example] 示例:
>
> - `git restore -s HEAD~1 <file>`:将文件**工作区内容**回退到**倒数第二个提交版本**(暂存区不变)
>
```shell
# 将指定分支的文件内容还原/覆盖至仅当前工作区(不影响既有的暂存区)
git restore -s <branch/commit> <file>
git restore -s <branch/commit> --worktree <file>
# 将指定分支的文件内容还原/覆盖至当前工作区&暂存区
git restore -s <branch/commit> --worktree --staged <file>
git checkout <branch/commit> -- <file> # 等价命令
# 将指定分支的文件内容还原/覆盖至暂存区(不影响当前工作区)
git restore -s <branch/commit> --staged <file>
```
<br><br><br>
# 从工作区/暂存区中移除文件——git rm
`git rm [options] <pathspec>]`:**从==工作区和暂存区==中移除某个文件**(要求该文件**未修改、未暂存过**,否则需要 `-f`)
- `--cached`:**仅从==暂存区==中移除该文件,而仍保留其在工作区中(==文件会变为 "Untracked" 状态==)**
- `-f`:**强制移除文件**(从工作区&暂存区中,等价于执行 shell 命令 `rm <file>`)
- `-r`:**递归地移除目录下所有文件**,要求 `<pathspec>` 为目录名
> [!NOTE]
>
> - `git rm file` 只能删除**处于 unmodified 状态的文件**——即**工作区中未修改过**,**暂存区中也不存在该文件的快照**
> - `git rm -f file` 用于**强制从工作区&暂存区移除某个文件**,等价于 shell 命令 `$ rm <file>`
>
> ![[_attachment/05-工具/git 使用/git 工作区&暂存区管理.assets/IMG-git 工作区&暂存区管理-2ED7D432FAB3E6F7AC58EB39D906575D.png|403]]
>
> [!NOTE] `git rm --cached file` 将令 file 变为 "Untracked" 状态
>
> ![[_attachment/05-工具/git 使用/git 工作区&暂存区管理.assets/IMG-git 工作区&暂存区管理-02D791253F2A1ACF880A3DD98DA496B8.png|429]]
> [!caution] ❓<font color="#c00000">如何将一个已跟踪的文件从版本控制中移除,取消跟踪?</font>
> ![[_attachment/05-工具/git 使用/git 工作区&暂存区管理.assets/IMG-git 工作区&暂存区管理-02D791253F2A1ACF880A3DD98DA496B8.png|439]]
> 1. `git rm --cached <file>` 将其**从暂存区移除而保留工作区中内容**,同时使其变为 **"Untracked" 状态**。
> 2. 将该文件记录 `.gitignore` 中。此后执行 `git add .` 时,**将不会再添加该文件到暂存区**。
>
> 示例:
>
> ```shell
> # logs/目录原本已被纳入版本控制, 现需要移除.
> # (1) 执行以下命令, 将logs/改为Untrached状态
> git rm -r --cached logs/
> # (2) 在.gitignore中添加 `logs/`, 标记忽略.
> # (3) 下一次执行`git add .` 以及 `git commit` 时, `logs/`目录就会被取消追踪.
> ```
>
> ^eo9uqt
<br><br><br>
# 清理工作区——git clean
`git clean`:移除工作区中的**未跟踪文件**
![[_attachment/05-工具/git 使用/git 工作区&暂存区管理.assets/IMG-git 工作区&暂存区管理-DA337969EC955E9941E8C214EB6BC5D6.png|507]]
<br><br><br>
# git 工作区&暂存区管理总结
##### ❓ 将指定 commit/branch 中指定文件版本恢复/覆盖至当前工作区、暂存区
> 换句话说,将当前工作区中的文件 "恢复" 至特定 commit 的版本
```shell
# 将指定版本的文件恢复/覆盖至当前工作区
git restore -s <branch/commit> <file>
git checkout <branch/commit> -- <file> && git reset HEAD <file> # 等价命令
# 将指定版本的文件恢复/覆盖至当前工作区&暂存区
git restore -s <branch/commit> -W -S <file>
git checkout <branch/commit> -- <file> # 等价命令
```
> [!caution] cherry-pick 只引入单个提交所引入的**diff 变更** !而非截止至该 commit 的文件完整内容!
> [!example]
>
> ```bash
> #!/bin/bash
> # 将指定分支下的多个文件版本恢复/覆盖至当前工作区
>
> FILES=(
> src/foo/zoo/abc.h
> src/foo/zoo/abc.cpp
> conf/foo/server.conf
> conf/params/default.json
> )
>
> BRANCH="dev_yht"
>
> for file in "${FILES[@]}"; do
> echo $file
> git checkout "$BRANCH" -- "$file" && git reset HEAD $(file)
> done
> ```
>
>
<br>
##### ❓ 将指定 commit/branch 中指定文件版本与当前工作区中文件进行 "合并"(而非直接覆盖至工作区)
> 这里是指向 "合并分支" 时一样**基于文件内容 diff 进行合并**,而非直接 "**覆盖**"。
需使用 git 底层命令:`git merge-file`。
`git merge-file <current> <base> <other>`:对指定文件进行**三路合并**(即 git merge 具体应用于针对单一文件的机制)
- `<current>`:当前工作区的文件(**合并后将被修改**)
- `<base>`:共同祖先版本(用于三方合并)
- `<other>`:想合并进来的目标文件版本(如特定提交下的文件内容)
该命令会基于 `<current>` 与 `<other>` 二者相较于**共有祖先 `<base>`** 所**各自产生的变更**进行合并,将合并结果应用于 `<current>`。
> [!example]
> 假设针对当前工作区中的 foo.cpp 文件, 预期用**指定提交中的文件版本**与其进行合并, 则步骤如下:
>
> ```shell
> # 1.找出当前HEAD与目标提交的共有祖先提交的hash
> git merge-base HEAD abc1234
>
> # 2.将共有祖先中的文件版本内容暂存到临时文件
> git show <base_commit>:foo.cpp > foo.base
>
> # 3.将特定提交中的文件版本暂存到临时文件
> git show <other_commit>:foo.cpp > foo.other
>
> # 4.执行三方合并, 合并到当前工作区的foo.cpp. 若存在合并冲突,将插入冲突标记.
> git merge-file foo.cpp foo.base foo.other
> ```
>
>
<br>
##### ❓ stash 用法概览?
```shell
# `git stash`默认存入"暂存区&&工作区"的变更
# `--staged`指定仅stash暂存区中的版本
# `-m`附加备注信息
# 对指定文件进行stash
# 仅将指定文件(暂存区&&工作区内)存入一个新stash
git stash push src/main.cpp src/file2.cpp
git stash push --staged src.main.cpp src/file2.cpp #仅stash暂存区中的文件版本
git stash push -m "存储main.cpp修改" src/main.cpp
# 弹出/应用stash
git stash list
git stash pop stash@{N} # 应用并删除指定stash
git stash apply stash@{N} # 应用指定stash(不删除)
git stash drop stash@{N} # 删除指定stask
# 仅检出指定stash中的指定文件
git checkout stash@{N} -- </path/to/file>
git restore --source=stash@{N} -- </path/to/file>
```
<br><br>
##### ❓ 将一个已跟踪文件从版本控制中移除
![[05-工具/git 使用/git 工作区&暂存区管理#^eo9uqt]]
<br><br>
# 参考资料
# Footnotes
[^1]: [git-stash用法小结 - Tocy - 博客园](https://www.cnblogs.com/tocy/p/git-stash-reference.html)
[^2]: [Quick Git Tip: Git Restore | by Gec | Medium](https://medium.com/@gecno/quick-git-tip-git-restore-952165db980c)