我们开发过程中可能会遇到同一个项目有多个分支在并行开发, 在开发其中一个分支的时候, 另一个分支突然需要做点什么事情; 如果此时代码刚写了一半, 提交也不合适, 撤销也不舍得, 没有类似 git stash 的工具就显得很尴尬; git stash 正是用来解决此类问题的有效解决方案; git stash 灵活的堆栈风格及列表风格的命令, 让我们处理分支间并行跳跃式开发变得游刃有余;


查询操作

查看所有快照简要信息:

1
git stash list

查看快照详情

仅查看改动的类及改动行数:

1
2
3
4
# 查看最新的快照
git stash show
# 查看第 n 个快照的信息
git stash show stash@{n}

查看具体的改动 diff:

1
2
3
4
# 查看最新的快照
git stash show -p
# 查看第 n 个快照的信息
git stash show -p stash@{n}

贮存操作

1
2
3
4
# 不指定 message 直接贮存(默认使用 HEAD 的 commit id 与 commit message 作为 stash message)
git stash
# 执行完使用 git stash list 查看结果:
stash@{0}: WIP on [branch_name]: [commit_id] [commit_message]
1
2
3
4
# 使用指定 message 保存当前修改上下文
git stash save "your_stash_message"
# 执行完使用 git stash list 查看结果:
stash@{0}: On [branch_name]: [your_stash_message]

贮存部分更改内容

如果有的时候我们只是想贮存部分修改的文件, 而继续编辑剩下的文件, 可以按如下操作:

1
2
3
4
5
6
# 1. 先把不要贮存的改动加入索引
git add file_path
# 2. 使用 –-keep-index 选项将未加入 index 的改动贮存起来
git stash save –-keep-index "stash_message"
# 3. 撤销加入索引的改动
git reset HEAD .

这里使用了 –-keep-index 选项以避免 stash 暂存区的修改, 局限性是我们必须先将目标文件加入索引, stash 完成后再将其从索引中撤销, 增添了一些复杂性;

恢复操作

1
2
# 使用最新的快照恢复, 并将其弹出存储堆栈
git stash pop
1
2
3
4
# 使用最新的快照恢复, 但不将其弹出存储堆栈
git stash apply
# 指定使用第 n 个快照恢复, 但不将其弹出存储堆栈
git stash apply stash@{n}

丢弃操作

1
2
3
4
5
6
# 删除最新的快照
git stash drop
# 删除指定的第 n 个快照
git stash drop stash@{n}
# 删除所有的快照
git stash clear

使用的坑

git stash 虽然很灵活, 但是也有一些使用上的坑: git stash 不会保留贮存前的 git 状态, 恢复后统一变为工作区状态, 举两个例子:

  1. 如果我们对一批修改中的局部变更使用了 git add 加入了暂存区, 然后不加任何选项, 将所有修改全部 stash, 等后面再恢复的时候, 这些修改虽然在, 但是都将清一色变成 Changes not staged for commit, 已经区分不出来哪些变更曾加过索引了;
  2. 如果我们刚刚合并了一个分支, 冲突很大, 当我们挨个文件处理完冲突后, 此时的 git 状态应该是 All conflicts fixed but you are still merging, 如果我们不及时提交此次合并的修改, 而将其 stash 起来, 等后面恢复的时候, 灾难就开始了:
    被合并分支的修改以及解决冲突的修改确实都在, 但他们已不是 merging 状态, 而是工作区状态, 若此时再提交变更, 根本就没有合并分支, 而只是将另一个分支的修改复制到 base 分支上而已 (外加一些冲突处理);
    当然这还不是最坑的地方, 如果此时想再重新合并目标分支, 会发现冲突会比第一次要多得多: 被合并分支修改的每一个文件, 都是整片整片的冲突, 因为我们把这些修改打包复制过来了, 等于两个分支都修改了相同的地方, 便造成了大面积冲突(讽刺的是, 仔细一看, 两边冲突的代码绝大部分还都是相同的); 一般到了这个地步, 我们就只能放弃了, 重新 reset 到上次合并之前的 commit, 重新再来吧;

参考链接