Git 命令可以在不修改工作树的情况下保存存储?

发布于 2024-11-14 23:51:04 字数 377 浏览 10 评论 0 原文

我一直想使用一个 git 命令来保存存储而不修改我的工作树,作为一个轻量级备份,可以安全地免受任何 git 重置或任何我可能会做的事情来搞砸我的索引。基本上与“git stash save && git stash apply”的功能等效,只是工作副本从未被触及,因为这可能会使某些文本编辑器/IDE 变得暴躁。

像这样的东西正在接近我想要的,但不完全是:

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

这在功能上有效,但我遇到的问题是“git stash list”中没有显示任何存储消息,即使实际的存储提交确实包含我的消息。考虑到存储的大小,存储消息非常重要。

I have been wanting to use a git command that saves a stash without modifying my working tree, as a lightweight backup that's safe from any git resets or whatever I might do to screw up my index. Basically the functional equivalent of "git stash save && git stash apply" except that the working copy is never touched, since this can make certain text editors/IDE's cranky.

Something like this is approaching what I want, but not quite:

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

This works functionally, but the issue I'm having is that no stash message shows up in "git stash list" even though the actual stash commit does have my message in it. Considering how large a stash can get, stash messages are pretty important.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

镜花水月 2024-11-21 23:51:04

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 创建代表存储条目的提交,并返回 对象名称提交对象(有 2 或 3 个父对象的对象)的 (SHA-1)。这是一个 悬空提交(您可以通过调用 <在git stash create之后的code>git fsck)。您需要使 refs/stash 指向此悬空提交,并通过 git stash store (或通过 git update-ref 像其他答案,因为 git stash store 使用 git update-ref 来做它的工作)。

最好查看一下 git stash push 的实际源代码,看看它基本上是在调用 git stash 创建和 git stash store 然后执行 一些逻辑来清理文件(哪一个取决于您在 git 中使用的选项存储推送)。

git stash store "$(git stash create)"

Will create stash entry similar to what you would get with git stash without actually touching and clearing your working directory and index.

If you check stash list or look at all commit graph (including stash) you'll see that it's similar result to what you would get with normal call to git stash. Just the message in stash list is different (normally it's something like "stash@{0}: WIP on master: 14e009e init commit", here we'll get "stash@{0}: Created via "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

A bit more explanation:

A git stash entry is represented using normal commits with some defined structure. Basically it is a regular commit object that has 2 parents (or 3 if you use --include-untracked option) (more info 1,2).

git stash create creates this commits that represents stash entry and returns you the object name (SHA-1) of commit object (the one that has 2 or 3 parents). It is a dangling commit (you can verify it by calling git fsck after git stash create). You need to make refs/stash point to this dangling commit and you do it by git stash store (or by git update-ref like in other answers, because git stash store uses git update-ref to do its work).

It's good to look at actual source code of git stash push and see that it's basically calling git stash create and git stash store and then does some logic to clean files (which one depends on what options you used in git stash push).

似梦非梦 2024-11-21 23:51:04

感谢 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 的新存储子命令,因此我已将此脚本的命名建议从 git-stash-push 更新为 git-stash-快照。

Thanks to Charles' tip, I whipped up a bash script to do exactly what I wanted (I was running into issues implementing this as only an alias). It takes an optional stash message just like git stash save. If none is supplied it will use the default message generated by 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

Edit: As pointed out in a comment below, saving this script as git-stash-snap somewhere in your path is sufficient to be able to invoke it by typing git stash-snap.

The nice thing here is that even if you drop a stash made with this method, you will still be able to see the stash message using git log [commit-hash] of the dangling commit!

Edit: since git 2.6.0 you can add --create-reflog to update-ref and then git stash list will show this even if git stash was not used before.

Edit: Git has introduced a new stash subcommand called stash push so I have updated my recommendation for naming this script from git-stash-push to git-stash-snap.

街角卖回忆 2024-11-21 23:51:04

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

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

You need to pass the message to update-ref, not stash create as stash create doesn't take a message (it doesn't update any ref, so it has no reflog entry to populate).

git update-ref -m "Stash message" refs/stash "$(git stash create)"
夜访吸血鬼 2024-11-21 23:51:04

以下内容结合了@Mariusz Pawelski的回答相关问题的类似答案,让您可以轻松地隐藏消息。

使用 git stash store 和消息

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

    git stash store -m "中间结果:我的笔记 1" $(git stash create)
    
  2. 当您决定放弃当前的工作并恢复到之前隐藏的状态时,请首先执行另一个 git stash。这会“丢弃”任何未提交的更改,将它们隐藏在存储中,以便在后续步骤中应用另一个存储状态时不会出现合并冲突。

    git 存储
    
  3. 现在查看您在存储引用中保存的内容:

    $ git 存储列表
    
    stash@{0}:中间结果:我的笔记 1
    stash@{1}:中间结果:我的笔记 2
    
  4. 最后,要恢复到存储的先前工作状态,丢弃当前的工作树状态,您可以这样做:

    git stash apply 1
    

    选择要恢复到的存储提交的索引号,而不是 1,如上一步列表中的 stash@{…} 所示。

  5. 如果您发现想要找回丢弃的作品:它也会保存在存储中,并且可以使用与上述相同的技术来恢复。

包装为 Git 别名

上面步骤 1 中使用的命令可以与 git 别名一起使用,以使其更加方便。要创建别名(请注意最终的#技巧):

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

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

git stash-copy "my note"

The following combines the answer by @Mariusz Pawelski and a similar answer to a related question, letting you comfortably stash with a message.

Using git stash store with a message

  1. Use git stash create to create a stash commit, and then save it to the stash using git stash store. This will not change any files in your worktree. And you can add a helpful message to find the right version again later. In total:

    git stash store -m "intermediate results: my note 1" $(git stash create)
    
  2. When you decide you want to throw away your current work and restore to a previously stashed state, first do another git stash. This "throws away" any uncommitted changes, hiding them on the stash, so that there will be no merge conflicts when you apply another stashed state in the next steps.

    git stash
    
  3. Now look up what you have saved in your stash ref:

    $ git stash list
    
    stash@{0}: intermediate results: my note 1
    stash@{1}: intermediate results: my note 2
    
  4. And finally, to restore to a stashed previous work state, throwing away your current worktree state, you would do this:

    git stash apply 1
    

    Instead of 1, choose the index number of the stash commit to restore to, as seen inside stash@{…} in the previous step's list.

  5. If you discover that you want your thrown-away work back: it is also saved in the stash and can be restored with the same technique as above.

Wrapping as a Git alias

The command used in step 1 above can be used with a git alias to make it more convenient. To create the alias (note the final # technique):

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

From now on, you can use the Git alias like this:

git stash-copy "my note"
心舞飞扬 2024-11-21 23:51:04

受 Eliot 解决方案的启发,我稍微扩展了他的脚本:

#!/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 的脚本进行了以下更改:

  1. 当工作目录干净时,脚本将正常退出
  2. 当使用开关 -c 时,如果没有与上次存储相比发生变化,脚本将退出。如果您将此脚本用作“时间机器”,每 10 分钟自动存储一次,这会很有用。如果没有任何变化,则不会创建新的存储。如果没有这个开关,您可能会得到 n 个连续的相同存储。

并不是说为了使开关 -c 正常工作,必须至少存在一个存储,否则脚本会在 git diff stash@{0} 上抛出错误,并且将什么也不做。

我将此脚本用作“时间机器”,使用以下 bash 循环每 10 分钟拍摄一次快照:

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

Inspired by Eliot's solution, I extended his script a little bit:

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

I implemented the following changes to Eliot's script:

  1. When working dir is clean, the script will exit gracefully
  2. When switch -c is used, if there no changes compared to the last stash, the script will exit. This is useful if you use this script as a "time machine", making an automated stash every 10 minutes. If nothing has changed, no new stash is created. Without this switch, you might end up with n consecutive stashes which are the same.

Not that in order for the switch -c to work properly, at least one stash must exist, otherwise the script throws an error on git diff stash@{0} and will do nothing.

I use this script as a "time machine", snapshotting every 10 minutes using the following bash loop:

 while true ; do date ; git stash-push ; sleep 600 ; done
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文