Git命令在不修改工作树的情况下保存存储?
Posted
技术标签:
【中文标题】Git命令在不修改工作树的情况下保存存储?【英文标题】:Git command to save a stash without modifying working tree? 【发布时间】:2011-09-13 00:20:49 【问题描述】:我一直想使用一个 git 命令来保存一个存储而不修改我的工作树,作为一个轻量级备份,它可以防止任何 git 重置或任何我可能会搞砸我的索引的事情。基本上相当于“git stash save && git stash apply”的功能,除了工作副本永远不会被触及,因为这会使某些文本编辑器/IDE 变得暴躁。
这样的事情正在接近我想要的,但并不完全:
git update-ref refs/stash `git stash create "Stash message"`
这在功能上有效,但我遇到的问题是“git stash list”中没有显示任何隐藏消息,即使实际的隐藏提交确实包含我的消息。考虑到存储空间的大小,存储消息非常重要。
【问题讨论】:
【参考方案1】:以下将answer by @Mariusz Pawelski 和a similar answer 组合到一个相关问题中,让您轻松地隐藏消息。
在消息中使用git stash store
使用git stash create
创建一个存储提交,然后使用git stash store
将其保存到存储。这不会更改工作树中的任何文件。并且您可以添加有用的消息,以便稍后再次找到正确的版本。
git stash store -m "saving intermediate results: my note 1" $(git stash create)
当您决定要丢弃当前的工作并恢复到以前隐藏的状态时,请先执行另一个git stash
。这会“丢弃”任何未提交的更改,将它们隐藏在 stash 中,这样当您在接下来的步骤中应用另一个 stashed 状态时就不会发生合并冲突。
git stash
现在查看您在 stash ref 中保存的内容:
$ git stash list
stash@0: saving intermediate results: my note 1
stash@1: saving intermediate results: my note 2
最后,要恢复到隐藏的先前工作状态,丢弃当前的工作树状态,您可以使用上面输出中 stash@…
内部的索引号来识别隐藏承诺恢复到:
git stash apply 1
如果您发现您想要收回您丢弃的作品:它也会保存在存储库中,并且可以使用与上述相同的技术进行恢复。
包装为 Git 别名
上面第 1 步中使用的命令可以与 git 别名一起使用,以使其更方便。创建别名(注意final #
technique):
git config --global alias.stash-copy '!git stash store -m "saving intermediate results: $1" $(git stash create) #'
从现在开始,你可以这样使用它:
git stash-copy "my note"
【讨论】:
【参考方案2】:git stash store "$(git stash create)"
将创建类似于您使用git stash
获得的存储条目,而无需实际接触和清除您的工作目录和索引。
如果您检查存储列表或查看所有提交图(包括存储),您会发现它与正常调用 git stash
得到的结果相似。只是存储列表中的消息不同(通常类似于 "stash@0: WIP on master: 14e009e init commit",这里我们将得到 "stash@0 :通过“git stash store”创建”)
$ git status --short
M file.txt
A file2.txt
$ git stash list
$ git stash store "$(git stash create)"
$ git stash list
stash@0: Created via "git stash store".
$ git stash show 'stash@0'
file.txt | 2 +-
file2.txt | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
$ git log --oneline --graph --all
* 85f937b (refs/stash) WIP on master: 14e009e init commit
|\
| * 26295a3 index on master: 14e009e init commit
|/
* 14e009e (HEAD -> master) init commit
$ git status
M file.txt
A file2.txt
再解释一下:
一个 git stash 条目使用具有一些定义结构的正常提交来表示。基本上它是一个常规提交对象,有 2 个父对象(如果您使用 --include-untracked
选项,则为 3 个)(更多信息 1,2)。
git stash create
创建表示存储条目的提交,并返回提交对象的object name (SHA-1)(具有 2 或 3 个父对象的对象)。它是一个dangling commit(您可以通过在git stash create
之后调用git fsck
来验证它)。您需要让refs/stash
指向这个悬空提交,然后通过git stash store
(或其他答案中的git update-ref
来完成它,因为git stash store
uses git update-ref
来完成它的工作)。
最好查看git stash push
的实际源代码,发现它基本上是在调用git stash create
and git stash store
,然后执行some logic 来清理文件(哪个取决于您在git stash push
中使用的选项)。
【讨论】:
很好的答案和解释。绝对是我工作流程的一个新部分。当我想做sed -i 's/oldname/newname/'
时,它非常有用。无需担心备份足够或类似的事情。【参考方案3】:
感谢 Charles 的提示,我编写了一个 bash 脚本来执行我想要的操作(我遇到了仅将其作为别名实现的问题)。它需要一个可选的存储消息,就像 git stash save 一样。如果没有提供,它将使用 git stash 生成的默认消息。
#!/bin/sh
#
# git-stash-snap
# Save snapshot of working tree into the stash without modifying working tree.
# First argument (optional) is the stash message.
if [ -n "$1" ]; then
git update-ref -m "$1" refs/stash "$(git stash create \"$1\")"
else
HASH=`git stash create`
MESSAGE=`git log --no-walk --pretty="tformat:%-s" "$HASH"`
git update-ref -m "$MESSAGE" refs/stash "$HASH"
fi
编辑:正如下面评论中所指出的,将此脚本保存为 git-stash-snap
在您的路径中的某处足以通过键入 git stash-snap
来调用它。
这里的好处是,即使您删除了使用此方法创建的存储,您仍然可以使用悬空提交的 git log [commit-hash] 查看存储消息!
编辑:从 git 2.6.0 开始,您可以将 --create-reflog
添加到 update-ref
,然后 git stash list
将显示此内容,即使之前未使用过 git stash
。
编辑:Git 引入了一个名为 stash push
的新 stash 子命令,因此我已将此脚本的命名建议从 git-stash-push
更新为 git-stash-snap
。
【讨论】:
如果您将该文件称为git-stash-push
并将其放在您的PATH 中某处,则无需为其创建别名。 git stash-push
会找到(并调用)git-stash-push
真的吗?有意思,我试试看。
对上述脚本的一个警告,当存储为空时,它似乎无法正常工作。
基于这个答案,我将以下内容添加到我的~/.gitconfig
的[alias]
部分,这应该提供相同的行为:stash-push = "!f() if (( $# > 0)); then branch=$(git branch | sed -n 's/^\\* //p'); git update-ref -m \"On $branch: $*\" refs/stash $(git stash create \"On $branch: $*\"); else hash=$(git stash create); msg=$(git log --no-walk --pretty=\"tformat:%-s\" $hash); git update-ref -m \"$msg\" refs/stash $hash; fi;; f"
@Eliot,我认为 stash-save
会是一个好名字,如果不是因为 git 已经在使用它,而他们应该使用 push
来匹配 pop
。 ;) 可能是stash-snapshot
,或者简称stash-snap
?【参考方案4】:
受艾略特解决方案的启发,我稍微扩展了他的脚本:
#!/bin/sh
#
# git-stash-push
# Push working tree onto the stash without modifying working tree.
# First argument (optional) is the stash message.
#
# If the working dir is clean, no stash will be generated/saved.
#
# Options:
# -c "changes" mode, do not stash if there are no changes since the
# last stash.
if [ "$1" == "-c" ]; then
CHECK_CHANGES=1
shift
fi
if [ -n "$1" ]; then
MESSAGE=$1
HASH=$( git stash create "$MESSAGE" )
else
MESSAGE=`git log --no-walk --pretty="tformat:%-s" "HEAD"`
MESSAGE="Based on: $MESSAGE"
HASH=$( git stash create )
fi
if [ "$CHECK_CHANGES" ]; then
# "check for changes" mode: only stash if there are changes
# since the last stash
# check if nothing has changed since last stash
CHANGES=$( git diff stash@0 )
if [ -z "$CHANGES" ] ; then
echo "Nothing changed since last stash."
exit 0
fi
fi
if [ -n "$HASH" ]; then
git update-ref -m "$MESSAGE" refs/stash "$HASH"
echo "Working directory stashed."
else
echo "Working tree clean, nothing to do."
fi
我对 Eliot 的脚本进行了以下更改:
-
当工作目录干净时,脚本将正常退出
当使用开关
-c
时,如果与上次存储相比没有变化,脚本将退出。如果您将此脚本用作“时间机器”,这将非常有用,每 10 分钟自动存储一次。如果没有任何变化,则不会创建新的存储。如果没有此开关,您最终可能会得到 n 个相同的连续存储。
并不是说要让开关-c
正常工作,至少必须存在一个存储区,否则脚本会在git diff stash@0
上引发错误并且不会执行任何操作。
我将此脚本用作“时间机器”,使用以下 bash 循环每 10 分钟拍摄一次快照:
while true ; do date ; git stash-push ; sleep 600 ; done
【讨论】:
【参考方案5】:您需要将消息传递给update-ref
,而不是stash create
,因为stash create
不接收消息(它不更新任何引用,因此它没有要填充的引用日志条目)。
git update-ref -m "Stash message" refs/stash "$(git stash create)"
【讨论】:
谢谢,这就是我想要的!但是,您对 git stash create 不接收消息是不正确的。提供给它的消息成为提交消息。因此,为了完美模拟 git stash save 创建的 stash 提交,我认为以下命令可以完成这项工作:git update-ref -m "Stash message" refs/stash "$(git stash create 'Stash message')"
。你能想出一个聪明的方法把它直接添加为 git 别名吗?如果没有,我可以把它做成一个简单的 bash 脚本。
@Eliot:git stash list
中显示的消息来自 reflog,而不是提交消息。这就是我所指的。你是对的,create
确实发生了一条消息,但这不是一个记录的功能,所以我不知道这是否是故意设计的,或者它是否只是实现的产物。
是的,好点。我偶然发现了它。我在 git wiki 上找到了一些关于在别名中使用参数的文档,所以我很高兴!
感谢@Charles Bailey 和 +1,您帮助我解决了另一个 git 问题。我重新混合了这一切***.com/questions/6589050/…。以上是关于Git命令在不修改工作树的情况下保存存储?的主要内容,如果未能解决你的问题,请参考以下文章
如何在不从磁盘中删除文件的情况下 git rm 文件? [复制]
如何在不修改 .git/index 的情况下运行 git status - 例如在 PROMPT_COMMAND 中