Git命令保存一个stash而不修改工作树?

kognpnkq  于 2023-05-12  发布在  Git
关注(0)|答案(5)|浏览(147)

我一直想使用一个git命令来保存一个stash,而不修改我的工作树,作为一个轻量级的备份,它是安全的,不会被任何git重置或任何我可能做的事情搞砸我的索引。基本上相当于“git stash保存&& git stash apply”的功能,除了工作副本永远不会被触及,因为这会使某些文本编辑器/IDE变得古怪。
这样的东西接近我想要的,但不完全是:

git update-ref refs/stash `git stash create "Stash message"`

这在功能上是可行的,但我遇到的问题是,即使实际的stash提交中确实有我的消息,“git stash list”中也没有显示stash消息。考虑到一个隐藏可以有多大,隐藏消息非常重要。

vu8f3i0k

vu8f3i0k1#

git stash store "$(git stash create)"
将创建类似于git stash的stash条目,而无需实际接触和清除工作目录和索引。
如果你检查stash list或者查看所有提交图(包括stash),你会发现它的结果与你正常调用git stash得到的结果相似。只是stash list中的消息不同(通常类似于 “stash@{0}:主文件上的WIP:14 e009 e 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创建这个表示stash条目的提交,并返回提交对象(有2或3个父对象)的对象名称(SHA-1)。这是一个悬置提交(您可以通过在git stash create之后调用git fsck来验证它)。你需要让refs/stash指向这个悬空提交,你可以通过git stash store来完成(或者像其他答案一样通过git update-ref,因为git stash store使用git update-ref来完成它的工作)。
最好看看git stash push的实际源代码,看到它基本上是调用git stash creategit stash store,然后执行一些逻辑来清理文件(哪一个取决于你在git stash push中使用了什么选项)。

tez616oj

tez616oj2#

多亏了Charles的建议,我创建了一个bash脚本来完成我想要的任务(我在实现这个别名时遇到了问题)。它接受一个可选的stash消息,就像git stash保存一样。如果没有提供,它将使用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来调用它。
这里的好处是,即使你删除了一个用这个方法创建的stash,你仍然可以通过dangling commit的git log [commit-hash]看到stash消息!
编辑:从git 2.6.0开始,你可以将--create-reflog添加到update-ref,然后git stash list会显示这个,即使之前没有使用过git stash
编辑:Git引入了一个新的stash子命令stash push,所以我将这个脚本的名字从git-stash-push更新为git-stash-snap

ffscu2ro

ffscu2ro3#

您需要将消息传递给update-ref,而不是stash create,因为stash create不接受消息(它不更新任何引用,因此没有要填充的reflog条目)。

git update-ref -m "Stash message" refs/stash "$(git stash create)"
cedebl8k

cedebl8k4#

下面将answer by @Mariusz Pawelskia similar answer结合到一个相关的问题中,让您轻松地隐藏消息。

使用git stash store和消息

1.使用git stash create创建一个stash提交,然后使用git stash store将其保存到stash。这不会更改工作树中的任何文件。您可以添加一条有用的消息,以便稍后再次查找正确的版本。总计:

git stash store -m "saving intermediate results: my note 1" $(git stash create)

1.当您决定要丢弃当前的工作并恢复到以前的隐藏状态时,首先执行另一个git stash。这将“丢弃”任何未提交的更改,将它们隐藏在存储中,以便在接下来的步骤中应用另一个存储状态时不会发生合并冲突。

git stash

1.现在查看您保存在您的stash ref中的内容:

$ git stash list

stash@{0}: saving intermediate results: my note 1
stash@{1}: saving intermediate results: my note 2

1.最后,要恢复到隐藏的先前工作状态,* 丢弃当前工作树状态 *,您可以这样做:

git stash apply 1

而不是1,选择要恢复的stash commit的索引号,如上一步列表中stash@{…}中所示。
1.如果你发现你想要回你扔掉的工作:它也被保存在藏匿处,并且可以用与上述相同的技术来恢复。

封装为Git别名

上面步骤1中使用的命令可以与git别名一起使用,以使其更方便。创建别名(注意final # technique):

git config --global alias.stash-copy \
  '!git stash store -m "saving intermediate results: $1" $(git stash create) #'

从现在开始,你可以像这样使用Git别名:

git stash-copy "my note"
kb5ga3dv

kb5ga3dv5#

受艾略特解决方案的启发,我将他的脚本扩展了一点:

#!/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

我对艾略特的脚本实现了以下更改:
1.当工作目录为干净时,脚本将正常退出
1.当使用开关-c时,如果与上一次stash相比没有变化,脚本将退出。如果您将此脚本用作“时间机器”,每隔10分钟自动进行一次存储,则此功能非常有用。如果没有任何变化,则不会创建新的stash。如果没有这个开关,你可能会得到n个连续的相同的stashes。
并不是说,为了使交换机-c正常工作,至少必须存在一个stash,否则脚本会在git diff stash@{0}上抛出错误并且什么都不做。
我将此脚本用作“时间机器”,使用以下bash循环每10分钟进行一次快照:

while true ; do date ; git stash-push ; sleep 600 ; done

相关问题