如何重命名Git中的存储?

319

我有一个名称不正确的储藏。我想要修正这个名称,以使其准确。

如何重命名储藏?


5
将其打包并用不同的名称重新保存? - Bartlomiej Lewandowski
11
再次弹出和存储并不总是可行的选择,因为存储可能基于过时的状态,导致在弹出时发生冲突。(过时的状态甚至不必再历史记录中存在。) - Tom
1
你可能会对稍微不同的工作流程感兴趣:只需从stash条目中创建一个新分支。类似这样:git branch stash/myNewName stash@{13},它将创建一个新分支,其头提交是您的stash列表中第13个条目。 - James Moore
@JamesMoore 你应该将它添加为一个答案!我会给它点赞。 - mikemaccana
12个回答

394

假设你的储藏清单看起来像这样:

$ git stash list
stash@{0}: WIP on master: Add some very important feature 
stash@{1}: WIP on master: Fix some silly bug

首先,您必须删除要重命名的存储条目:

$ git stash drop stash@{1}
Dropped stash@{1} (af8fdeee49a03d1b4609f294635e7f0d622e03db)

现在使用返回的提交哈希值添加新消息后再次添加它:

$ git stash store -m "Very descriptive message" af8fdeee49a03d1b4609f294635e7f0d622e03db

就是这样:

$ git stash list
stash@{0}: Very descriptive message
stash@{1}: WIP on master: Add some very important feature

这个解决方案需要 git 1.8.4 或更高版本,并且它可以在脏的工作目录下工作。


28
使用git show命令获取哈希值,并使用git stash store命令开始储藏。然后,通过运行git stash list命令,您将看到旧的和新的储藏。最后,您可以使用git stash drop命令清除旧的储藏。 - hogi
21
使用git stash drop命令会丢失更改吗? - Shravya Boggarapu
16
不,Git在运行git gc之前不会删除提交。在执行stash drop后,您可以使用git fsck | grep commit命令轻松找到此通常无法访问的提交。 - qzb
6
@ÐerÆndi 简单地应用和保存是一种容易的选择,但当由于冲突而无法重新应用更改时,这种方法就不起作用了。与此同时,放弃并存储在任何情况下都可以使用。我再次测试了我的解决方案——它在最新的Git版本(2.17.0)上运行良好。 - qzb
5
当我看到git stash drop stash@{1}时,我说:“你疯了吗?”但它完美地运行了!谢谢 :) - Positive Navid
显示剩余10条评论

79

除非您手动执行或为Git做出改进贡献,否则可以使用别名:

git config --global alias.stash-rename '!_() { rev=$(git rev-parse $1) && git stash drop $1 || exit 1 ; git diff-index --quiet HEAD; s=$?; [ $s != 0 ] && git stash save "tmp stash from stash-rename"; git stash apply $rev && shift && git stash save "$@" && [ $s != 0 ] && git stash pop stash@{1}; }; _'

用法: "git stash-rename <stash> [save options] [<message>]"

[save options]中可以使用git stash save的任何选项: [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [-u|--include-untracked] [-a|--all]

示例:

$ git stash list
stash@{0}: On master: Pep8 format
stash@{1}: On master: co other than master with local changes
stash@{2}: On master: tests with deployAtEnd

# Let's say I want to rename the stash@{2} adding an issue reference:
$ git stash-rename stash@{2} NXP-13971-deployAtEnd

$ git stash list
stash@{0}: On master: NXP-13971-deployAtEnd
stash@{1}: On master: Pep8 format
stash@{2}: On master: co other than master with local changes

即使您有本地未提交的更改,这也能起作用 :)

编辑 2016/02/22

简化脚本,感谢qzbhttps://dev59.com/Al8e5IYBdhLWcg3wPYlA#35549615

git config --global alias.stash-rename '!_() { rev=$(git rev-parse $1) && git stash drop $1 || exit 1 ; git stash store -m "$2" $rev; }; _'

用法:"git stash-rename <存储> [<消息>]"


2
太棒了!如果你能做到 git stash-rename 'tests with deployAtEnd' 'NXP-13971-deployAtEnd' 就更酷了。 - mikemaccana
4
所以答案是:1)清理工作副本,2)应用要重命名的存储,3)从存储列表中删除它,4)创建一个新的存储并附上正确的消息。 - gcb
2
请澄清一下,您是在重命名最后一个存储,并在此操作后将其变为顶部存储吗? - onebree
2
我删除暂存区以重命名,如果有任何更改则保存当前更改,使用所需名称重新创建已删除的暂存区,如果有任何更改,则重新应用当前更改。 - Julien Carsique
7
这个版本会检查两个参数是否都存在,以避免意外删除你最后的存储。它仅需要指定储藏号码,而不是整个“stash@{0}”引用。 https://gist.github.com/jdforsythe/f248bf6c72fc020225cc3e315a32e922 git config --global alias.stash-rename '!_() { if [ -z \"$1\" ] || [ -z \"$2\" ]; then echo \"git stash-rename 0 NewName\" && echo \"\" && git stash list && exit 1; else stash=\"stash@{$1}\"; rev=$(git rev-parse \"${stash}\"); git stash drop \"${stash}\" || exit 1; git stash store -m \"$2\" \"$rev\" || exit 1; git stash list; fi }; _' - jdforsythe
显示剩余13条评论

15

很简单。首先使用以下命令撤销最后一个“stash”:

git stash pop

完成后,您可以通过以下方式使用自定义名称保存存储:

git stash save "your explanatory name"

我希望它对你有用。:)


15
重命名的暂存区可能不是最新的。 - mikemaccana
这更为简单,因此对于最近的存储更为直接。 - Kaihua
1
如果不是最近的存储,只需执行 git stash apply {N},您可以通过 git stash list 找到 {N}。然后,您可以使用 git stash drop {N+1} 删除旧的存储,因为一旦使用新名称存储更改,索引就会增加。 - kano

6

为了读者的方便,这是当前已被接受且正确答案的扩展

如果你不仅想更正储藏信息,还想更正储藏的提交信息,使之变成:

git stash list

并且

git log --oneline -1 stash

虽然双方都同意所显示的内容,但还需要更多。也许有更好的方法,但这个教程很容易理解,希望对您有所帮助。

要使用 git commit --amend 命令,需要位于分支的最新提交(TIP)位置。因此解决方案是:

git checkout -b scratch stash@{1}
git stash drop stash@{1}
git commit --amend -m "$MESSAGE"
git stash store -m "$MESSAGE" HEAD
git checkout master
git branch -D scratch

解释:

  • 从“目标stash”创建一个新的(尚不存在的)“scratch”分支并切换到该分支
  • 移除旧的stash。由于我们仍然在该分支上,因此这是安全的。
  • 使用git commit --amend替换提交消息,从而更改“目标stash”的SHA。
  • 根据qzb's answer存储stash
  • 切换回来(假设您来自“master”)并进行清理

缺点:

  • 这会暂时切换分支。因此,只有在git status --porcelain干净(即:不会输出任何内容)时才能应用此方法

  • 它会重新编号stashes,因此更改后的stash变为stash@{0}

  • 需要两次输入$MESSAGE或使用某些环境变量(例如:在示例中:MESSAGE

  • 需要找到未使用过的分支名称

有一些方法可以在不切换分支的情况下完成此操作,但这超出了本答案的范围。

示例

git init scratch
cd scratch
for a in A B C D; do date >$a; git add $a; git commit -m $a; done
for a in X Y; do echo $a > Z; git stash save --all; done
git log --oneline --graph --decorate --all; git stash list

输出

*-.   e0e281b (refs/stash) WIP on master: 8bdcc32 D
|\ \  
| | * 4d62f52 untracked files on master: 8bdcc32 D
| * 096f158 index on master: 8bdcc32 D
|/  
* 8bdcc32 (HEAD, master) D
* c84c659 C
* 49bb2da B
* b1852c6 A
stash@{0}: WIP on master: 8bdcc32 D
stash@{1}: WIP on master: 8bdcc32 D

现在不改变提交记录(请注意:下面的SHA值在您的环境中可能会有所不同):

git stash drop stash@{1}
git stash store -m ...changed... 2fbf9007dfdfb95ae269a19e13b8b9ca3e24181c
git log --oneline --graph --decorate --all; git stash list

输出

*-.   2fbf900 (refs/stash) WIP on master: 8bdcc32 D
|\ \  
| | * 246dc5c untracked files on master: 8bdcc32 D
| * 80c5ea0 index on master: 8bdcc32 D
|/  
* 8bdcc32 (HEAD, master) D
* c84c659 C
* 49bb2da B
* b1852c6 A
stash@{0}: ...changed...
stash@{1}: WIP on master: 8bdcc32 D

如您所见,在git log中仍然显示stash@{0}2fbf900 (refs/stash) WIP on master: 8bdcc32 D。如果您仔细观察,将会发现有几个提交已更改SHA值。这是由于处理stashes的方式(包括SHA值的父级,以及stashes将其stashes作为父级)所致。

修改方法:

git checkout -b scratch stash
git stash drop
git commit --amend -m ...changed...
git stash store -m ...changed... HEAD
git checkout master
git branch -D scratch
git log --oneline --graph --decorate --all; git stash list

输出

*-.   4d55186 (refs/stash) ...changed...
|\ \  
| | * 246dc5c untracked files on master: 8bdcc32 D
| * 80c5ea0 index on master: 8bdcc32 D
|/  
* 8bdcc32 (HEAD, master) D
* c84c659 C
* 49bb2da B
* b1852c6 A
stash@{0}: ...changed...
stash@{1}: WIP on master: 8bdcc32 D

正如您所看到的,refs/stash的SHA也发生了变化。

1
值得一提的是:这将破坏原始存储的索引,并用与原始存储的父提交匹配的新索引替换它。如果不打算使用原始保存的索引(或者它已经与原始存储的父提交匹配),那么这不是一个问题。 - torek

5

多年后回答自己的问题: 这个功能最近被宣布,所以我想在这里补充一下。

许多GUI git客户端(例如,Fork 1.58及更高版本)现在支持直接重命名存储。

输入图像描述


6
对于那些想知道的人,Fork使用了我在我的回答中描述的方法。 - Brecht Machiels

4

这里有一些简单的方法,适用于简单情况。例如git stash apply

$ # Stash any pending changes you have, if applicable.
$ git stash -um "temporary stash"
$ # Re-apply the stashed changes whose message you want to change.
$ git stash apply stash@{1}
$ # Now stash again with the message you want.
$ git stash push -um "good message"
$ # Now you can pop your temporary stash and drop your poorly named one.
$ git stash pop
$ git stash drop stash@{1}

在简单情况下,这是一个很好且易于操作的技巧。但如果想要修改的stash是一段时间以前或在另一个分支上创建的,那么可能无法干净地应用,这可能会使此技术变得非常麻烦。

使用git stash store似乎也很简单。只需执行git stash store -m "$msg" $ref,然后即可进行操作。但这似乎在现代版本的git中不起作用。这可能是因为它会在.git/logs/refs/stash中创建一个条目,其中包含您的新消息,但不会修改存储在$ref位置实际提交中的消息,而git stash list现在显然会显示存储在stash提交中的消息,而不是日志中的消息。

以下是一个示例。我使用符号将较长的行换行。

$ git init
$ touch foo
$ git add foo
$ git commit -m foo foo
[master (root-commit) a839f24] foo
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 foo

$ git stash push -m "bad message"
Saved working directory and index state On master: bad message

$ git stash list
24e1f0a - stash@{0} (4 seconds ago) On master: bad message

$ cat .git/logs/refs/stash
0000000000000000000000000000000000000000 24e1f0aa39ede6e3065dd5c7bbb337af5b2a5d91 ⏎
PDaddy <pdaddy@not-my-real-email-address.com> 1655905352 -0400 bad message

$ git stash drop
Dropped refs/stash@{0} (24e1f0aa39ede6e3065dd5c7bbb337af5b2a5d91)

$ git stash store -m "good message?" 24e1f0a
$ git stash list
24e1f0a - stash@{0} (16 seconds ago) On master: bad message

什么?它仍然有相同的糟糕消息。

$ cat .git/logs/refs/stash
0000000000000000000000000000000000000000 24e1f0aa39ede6e3065dd5c7bbb337af5b2a5d91 ⏎
PDaddy <pdaddy@not-my-real-email-address.com> 1655905366 -0400 good message?

虽然这里有新的消息,但为什么stash list仍然显示错误的消息呢?

$ git show stash@{0}
Merge: a839f24 7b91532
Author: PDaddy <pdaddy@not-my-real-email-address.com>
Date:   Wed Jun 22 09:40:27 2022 -0400

    On master: bad message

diff --cc foo
index e69de29,e69de29..d1d375f
--- a/foo
+++ b/foo
@@@ -1,0 -1,0 +1,1 @@@
++Wed Jun 22 09:40:21 AM EDT 2022

你可以看到,坏消息仍然是实际提交的元数据的一部分,我们没有修改它。要这样做,需要用一个新的提交替换提交24e1f0a。对于可以从当前分支到达的提交,如果最近的提交是它们,则可以使用git commit --amend实现,否则可以使用交互式变基。但是24e1f0a不仅不是我们正在使用的任何分支中最新的提交,而且根本无法从我们的分支到达。它形成了一个独立的类似分支的树的头。 所以我们不希望使用变基操作合并到它上面。

但是,我们可以检出来然后修改它。这很容易做到。

$ # If we have any pending changes, stash them first.
$ git stash -um "temporary stash before rewording another stash commit"
Saved working directory and index state On master: temporary stash...

$ git checkout 24e1f0a
Note: switching to '24e1f0a'.

You are in 'detached HEAD' state.
...snip: protracted warning message
HEAD is now at 24e1f0a On master: bad message

$ git commit --amend -m 'On master: good message!'
[detached HEAD 714d9be] On master: good message!
 Date: Wed Jun 22 09:40:27 2022 -0400

$ git switch -
Warning: you are leaving 2 commits behind, not connected to
any of your branches:

  714d9be On master: good message!
  7b91532 index on master: a839f24 foo

If you want to keep them by creating a new branch, this may be a good time
to do so with:

 git branch <new-branch-name> 714d9be

Switched to branch 'master'

$ # If we made a temporary stash earlier, pop it now.
$ git stash pop
blah blah blah

现在我们可以使用git store将经过重新提交的提交放入暂存列表中。

$ git stash store -m 'On master: good message!' 714d9be
$ git stash list
714d9be - stash@{0} (2 minutes ago) On master: good message!
24e1f0a - stash@{1} (10 minutes ago) On master: bad message

让我们放心,我们不会失去任何东西:

$ git merge-tree @ stash@{0} stash@{1}
$ # No output is good.  But if we're still not certain...
$ diff <(git show stash@{0}) <(git show stash@{1})
1c1
< commit 714d9be27dee44e96ddd3c5a4a43b35cff34a4cc
---
> commit 24e1f0aa39ede6e3065dd5c7bbb337af5b2a5d91
6c6
<     On master: good message!
---
>     On master: bad message

看起来唯一的区别在于提交哈希值(预期的)和提交消息(这正是我们想要的)。

现在,我们应该放心地丢弃旧的存储。

$ git stash drop stash@{1}
Dropped stash@{1} (24e1f0aa39ede6e3065dd5c7bbb337af5b2a5d91)

$ git stash list
714d9be - stash@{0} (5 minutes ago) On master: good message!

任务完成!


另请参阅:

  • Tino的答案,使用类似的技术,但是创建了一个临时分支而不是使用分离头

  • Brecht Machiels的答案,使用一个管道命令来更直接地进行修改。虽然较难理解,但它是更快和更直接的解决方案,因为它不需要隐藏任何可能存在的挂起更改并检出不同的HEAD。

    这种方法唯一的缺点是它会创建一个新的提交,从而改变了日期,并在列表中更改了stash的位置和其reflog选择器(重命名的stash变成stash@{0})。

下面是一个神奇的单行代码(为了可读性而分成多行),可以完成Brecht Machiels的方法:

_commit=$(git stash drop ${stash:-0} | sed -Ee 's/Dropped.+\((.+)\)$/\1/') &&
git stash store -m "$msg" $(
    git commit-tree $(
        git show -s --pretty=%T\ %P $_commit |
        sed -Ee 's/ / -p /g'
    ) -m "$msg"
)

请注意,这取决于Shell对内部命令替换输出的单词拆分。
以下是更易读的脚本。
#!/bin/bash

message=$1
stash=${2:-0}
commit=$(
    git stash drop $stash 2>/dev/null |
    sed -nEe 's/Dropped.+\((.+)\)$/\1/p'
)

if [[ -z $message || -z $commit ]]; then
    echo >&2 "usage: $(basename "$0") MESSAGE [STASH]"
    exit 1
fi

args=(
    # Suppress diff output.
    --no-patch
    # Get the tree hash and the hashes of all the parents.
    --pretty=%T\ %P
)
refs=$(git show "${args[@]}" $commit)

# Format the refs as arguments to `git commit-tree` by inserting "-p" in front of
# each of the parent hashes and then splitting on spaces to separate arguments.
args=($(sed -Ee 's/ / -p /g' <<< "$refs"))

# Create a new commit with the same tree and parents as the old stash, but with
# the new message.
new_commit=$(git commit-tree "${args[@]}" -m "$message")

# Store the new commit in the stash.
git stash store -m "$message" $new_commit

4

git stash store方法在qzb的回答中描述, 只更新了两个地方之一中的stash消息,导致许多Git前端仍显示旧消息。但是,可以创建一个新提交(commit),它复制了原始stash提交的所有内容但更改了其消息。

  1. Find the hashes for the stash commit's tree and parents:

    $ git show -s --pretty=raw stash@{0}
    commit f2adfc7bbebe852693ad8f6ac889e4923230c872
    tree 8160d88c6e00e90fcfa183e09d2563f3cdfb304b
    parent a013bd8052d3260fbc95608ed69d0b9cfa0c77cb
    parent 5d5eb80dc03bea8ff2bdd38962a1259b7725d169
    author ...
    committer ...
    
        Test stash
    
  2. Create a new commit with the same tree and parents but a different message:

    $ git commit-tree 8160d88c6e00e90fcfa183e09d2563f3cdfb304b \
      -p a013bd8052d3260fbc95608ed69d0b9cfa0c77cb \
      -p 5d5eb80dc03bea8ff2bdd38962a1259b7725d169 \
      -m "Renamed stash"
    f2adfc7bbebe852693ad8f6ac889e4923230c872
    
  3. Store this commit as a new stash

    $ git stash store \
      -m "$(git show -s --format=%B f2adfc7bbebe852693ad8f6ac889e4923230c872)" \
      f2adfc7bbebe852693ad8f6ac889e4923230c872
    
新的贮藏消息需要同时提供给git-commit-treegit-stash-store,因为Git将其存储在两个位置(提交和logs/refs/stash)。
请注意,使用 --keep-index 创建的贮藏将有3个父级,因此在这种情况下,您需要向git-commit-tree提供第三个父级!
将此过程转换为可用于Git别名的单行命令留给读者作为练习;-)。请确保仅使用管道命令进行此操作(因此避免使用例如git-showgit-log)。

2
鉴于提问者可以随时更改所接受的答案,也许你需要在第一句中使用更稳定的标识符。你是指qzb的回答吗? - Toby Speight

4
很多复杂的答案在这里。我会采取这种方法:
首先让我们找到您的Stash的索引:
git stash list

现在使用git stash apply {N}来应用它,例如:

git stash apply 2

现在您可以使用新消息隐藏更改

git stash push -m 'My descriptive stash message'

如果你想清理原始存储库,请记得将索引值增加1,因为新的存储库会递增所有现有索引值(所以我们要使用 N + 1)。

git stash drop 3

3
我认为这是不可能的。已经有一个关于stash重命名的提案,但它尚未实施。
我的一般想法是:
1. 实现一个新的git reflog update命令,用于更新与特定reflog条目相关联的消息。为此,在reflog.c中创建一个新的update_reflog_ent()函数,会更改与要更新的特定reflog条目相关联的消息。一个update_reflog()函数将使用for_each_reflog_ent()update_reflog_ent来实际进行更改。
2. 然后,git stash rename命令只需要调用适当的ref和新消息的git reflog update
或者,您当然可以弹出stash并执行git stash save [message]

1
这是 Julien的别名的修改版,可以正确处理通常添加到贮藏名称前缀的On <branch>
git config --global alias.stash-rename '!_() { newmsg="$1" && stash=${2:-"stash@{0}"} && newbranch="$3" && sha=$(git rev-parse "$stash") && olddesc="$(git stash list --format=%gs -1 "$stash")" && newdesc="$(if [[ "$newbranch" = "." ]]; then echo "$newmsg"; else if [[ -n "$newbranch" ]]; then echo "On $newbranch: $newmsg"; else if [[ "$olddesc" =~ ":" ]]; then echo "$(echo "$olddesc" | cut -f1 -d":"): $newmsg"; else echo "$newmsg"; fi; fi; fi)" && git stash drop "$stash" > /dev/null || exit 1; git stash store -m "$newdesc" "$sha" && git stash list; }; _'

语法:

git stash-rename <new-name> [<stash> [<new-branch-name> | .]]

示例用法:

repo[master] % touch tmp && git add tmp && git stash save first
Saved working directory and index state On master: first
HEAD is now at bd62064 Initial commit
repo[master] % touch tmp && git add tmp && git stash save second
Saved working directory and index state On master: second
HEAD is now at bd62064 Initial commit
repo[master] % git stash list
stash@{0}: On master: second
stash@{1}: On master: first
repo[master] % git stash-rename renamed
stash@{0}: On master: renamed
stash@{1}: On master: first
repo[master] % git stash-rename also-renamed stash@{1}
stash@{0}: On master: also-renamed
stash@{1}: On master: renamed
repo[master] % git stash-rename branch-changed stash@{0} new-branch
stash@{0}: On new-branch: branch-changed
stash@{1}: On master: renamed
repo[master] % git stash-rename branch-name-persists
stash@{0}: On new-branch: branch-name-persists
stash@{1}: On master: renamed
repo[master] % git stash-rename no-branch stash@{0} .
stash@{0}: no-branch
stash@{1}: On master: renamed
repo[master] % git stash-rename renamed
stash@{0}: renamed
stash@{1}: On master: renamed
repo[master] % git stash-rename readd-branch stash@{0} develop
stash@{0}: On develop: readd-branch
stash@{1}: On master: renamed

大部分命令用于解析参数并确定应对分支名称执行的操作。使用的git工具如下:
使用git rev-parse <stash>命令查找存储区的SHA值。 使用git stash list --format=%gs -1 <stash>命令查找存储区的reflog主题。请注意,这与存储区的提交消息不同,该命令不会更改提交消息。reflog主题是在git stash list中显示的内容,并且可以更改reflog主题而不更改与存储区关联的提交哈希。但是,您始终可以找到原始提交消息,因此不要使用git stash-rename删除敏感信息! 使用git stash drop <stash>命令删除旧的存储区引用(但我们仍然有SHA,因此它不会丢失)。 使用git stash store -m <new-message> <sha>命令保存一个新的存储区引用,其中包含相同的提交信息,但具有不同的reflog主题。 使用git stash list命令在操作完成后列出存储区。请注意,新的存储区始终推送到列表的开头。为了恢复其原始位置,需要重新推送所有感兴趣的存储区之前的所有存储区。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接