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 中

是否可以在不重写历史记录的情况下精简 .git 存储库?

如何在不首先克隆的情况下“连接”一个 git 存储库?

JGit:我可以在不检查的情况下找出某个特定标签是不是存在于 Git 存储库中吗?

如何在不分叉的情况下在 gitlab/github 上复制 git 存储库?