5

Suppose I put a stash into git too quickly and did not provide an informative stash message. I'd like to amend the message in the stash but cannot find a way to do this from the git docs. I could get the repo to a clean state and then pop the stash and reapply w/git stash save "my save message" but was wondering if anyone had a solution to modify the message in place.

Lucas Roberts
  • 1,252
  • 14
  • 17
  • 3
    A git stash entry *is* a commit (well, really, at least two, sometimes three, commits) and like all commits it is read-only—it's technically impossible to change any commit. `git commit --amend` doesn't actually amend a commit either, it just makes a new one to use instead of the old one. So that's pretty much what you have to do with `git stash` as well, which is what you described. – torek May 16 '18 at 18:56
  • @torek Thanks for the quick and clear explanation. If you formulate that as an answer I will accept/mark as correct. – Lucas Roberts May 16 '18 at 18:58
  • 2
    But also like any other commit, you can use `git notes` to add information to the relevant commits. (It may not be obvious that the note exists, though.) – chepner May 16 '18 at 19:03
  • @chepner useful to know, thanks for the tip – Lucas Roberts May 16 '18 at 19:52
  • An alternative to the accepted answer is [here](https://stackoverflow.com/a/26604221/800151). – Mike C Aug 23 '22 at 18:04

2 Answers2

5

It's technically impossible, but easy to accomplish the desired goal via trickery. Git just doesn't have any built in mechanism to accomplish the trickery, even though git commit has the --amend option that does just that. (There's no clean way to hijack git commit --amend for this either.)

In the end, you have to do what you're suggesting. You could hide this in a script, if you wanted, but for a one-off case that's more work than just doing it by hand.

Technical details, in case you want to build a script

Under the covers of Git command line commands and options and so on, a Git stash entry actually is a commit. More precisely, it's at least two commits, and sometimes three. The git stash documentation describes this (rather lightly and carelessly) in the section labeled DISCUSSION, The thing that makes git stash commits special is that they are on no branch—well, that, and that the w (work-tree) commit in the cluster, which is the one that the refs/stash reference locates, has the form of a merge commit, so that it can list multiple parent commit IDs. I prefer to draw these as:

...--F--G   <-- branch
        |\
        i-w   <-- (the stash)

or:

...--F--G   <-- branch
        |\
        i-w   <-- (the stash)
         /
        u

which is a bit different from the Git documentation drawing, but shows how what I call the stash bag (with optional u commit holding untracked files) dangles from the commit that was current when you made the stash. (If you have not moved your HEAD elsewhere yet, that commit is still the current commit.) The uppercase letters here stand in for actual commit hashes, which are big and ugly and impossible to remember.

As with all commits, the special stash commits are read-only—they can never be changed, they can only have their hash IDs be forgotten (which is wht git stash drop does).

Note that git commit --amend actually makes a new commit, shoving the current (HEAD) commit aside, out of the way. That is, if we start with:

...--F--G   <-- branch (HEAD)

and run git commit --amend, Git makes a new commit—let's call this G2—whose parent is F rather than the normal idea of using G as G2's parent, then writes G2's ID into the branch, giving us:

       G
      /
...--F--G2  <-- branch (HEAD)

As long as the old commit G has no name by which we can find it, Git never shows it to us, and eventually drops it entirely, making it seem as though we somehow changed commit G into G2.

To make an existing stash entry have a different commit comment, we would have to do the same thing: we'd copy our existing w commit to a new commit, with a different commit message, but retain w's content and all of its parent hashes. If we call the replacement w2 we get:

...--F--G_  <-- branch
        |\`-.
        i-w2 \
         \    \
          -----w   <-- (the stash)

If we then re-point refs/stash to point to w2 instead of w, and pretend that w no longer exists, we get what we want:

...--F--G   <-- branch
        |\
        i-w2  <-- (the stash)

and in fact we can write that as a script, starting with these fragments of code:

# get the parents of refs/stash as $1 and $2; $3 exists if there is a u commit
set -- $(git rev-parse refs/stash^@)
# convert these to "-p $1 -p $2 -p $3"
case $# in
2) parents="-p $1 -p $2";;
3) parents="-p $1 -p $2 -p $3";;
*) fatal "refs/stash does not appear to be a valid stash";;
esac
# find the stashed w commit's tree
tree=$(git rev-parse refs/stash^{tree}) || exit
# optional: for editing purposes, gather the current message
existing_message=$(git log --no-walk --pretty=format:%B refs/stash)

# obtain an updated message in some fashion
[snip]

and then:

# create a new w commit, suitable for "git stash store"
new_w_commit=$(git commit-tree $parents "$new_message" $tree)

and eventually:

git stash drop --quiet
git stash store --quiet -m "$new_message" $new_w_commit

which uses the git stash script itself to replace stash@{0} with the new stash. (In case of interrupt, it might be wiser to reverse the order of these two operations. Figuring out how to do this with a stash@{n} reference is left as an exercise. None of this is even remotely tested.)

torek
  • 448,244
  • 59
  • 642
  • 775
0

You can edit the stash short messages in the .git/logs/refs/stash file.

Its also possible to add a normal long commit message for a stash. e.g. by:

    git rebase -i stash~1 refs/stash --rebase-merges

    # change the `merge -C ...` to `merge -c ...` in the protocol
    # then running the rebase edit the full msg

    git update-ref refs/stash @ stash

    # Then edit `.git/logs/refs/stash file` for the stash short message.

Similar for older stashes - use stash@{N} vs stash as ref in the the rebase; and omit the update-ref.

To view the full commit msg of a stash:

    git show stash
    git show stash@{1}
    git show stash@{2}
    ...
kxr
  • 4,841
  • 1
  • 49
  • 32