%% # 纲要 > 主干纲要、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)