Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transfer stashed changes between copies of a repository

Tags:

git

git-stash

I have two copies of a repository, each of them with a few tens of stashed states (stash@{0}, stash@{1},...).

I need to get rid of one of the copies, and I want to transfer all stashed changes from the copy I am deleting to the copy I will keep. I want to keep all the information on parents and dates of all stashed states, as well as the state of the index.

I have been reading the docs, and I cannot find any simple/direct way to do this. Is it possible at all?


Update 1: One of the reasons I have for wanting to keep changes as stashed states, and not as commits, is that by using the --index flag I can retrieve both changes staged for commit and changes in the work directory. If I create a commit, I will also be destroying information of the stashed states where parent, index and work copy are all different one from another. My stashed states usually correspond to preliminary testing work that is very far from being compilable, not ready to be committed, and at the moment I don't have the time to go through tens of them sorting them out.


Update 2: I think I know how to view the information I want to back up, for example,

$ git show stash{5}
commit eb5731e828f467dbe9214d0e6a350f33898c1363
Merge: c9608582 1d6cb78d
Author: Author <[email protected]>
Date:   Wed Sep 20 18:54:51 2017 +0100

clearly produces the the id of the work directory state (commit line) and the date, and the ids in the Merge: line are the parent commit id and the id of the index.

What I don't know is how to transfer all this information to the second copy of the repository, as a new stashed state of it.


Update 3: Clarification: both copies of the repository already have stashed states.

like image 922
jme52 Avatar asked Aug 28 '18 14:08

jme52


1 Answers

Update - I do want to go ahead and agree with others that a workflow that leads to this probably isn't the best workflow.[1] But everyone's so busy harping on that, not a lot of people are providing practical answers about how you get from where you are to where you want to be. So:


The stash information is stored in a collection of commits, plus a ref with a heavily manipulated reflog. Dealing with the reflog will be the hardest part of what you're asking.

Not only is the reflog considered a local data structure (so no built-in behavior exists to share it), but each repo presumably has a conflicting reflog representing the local stack of stashed states, and the question of how to combine them is not straightforward.

One approach might look something like this. I'll call the repo you're dropping source, and the one you're keeping target.

First, create easily-shareable refs at each stash state in source.

$ cd /path/to/source
$ git tag stash-s0 stash
$ git tag stash-s1 stash@{1} 
$ git tag stash-s2 stash@{2}
// etc.

You also need to make notes of all of the stash messages. They're in the stash reflog.

$ git reflog stash
1111111 stash@{0}: Custom Stash Message Here
2222222 stash@{1}: WIP on master: 1234567 2

(You could store those as annotations on the tags, but IMO that's really no more convenient than anything else...)

Now you need to copy those tags (and their history) to target; that will ensure that all stashed data is present

$ cd /path/to/target
$ git fetch --tags file://localhost/path/to/source

(file://localhost/path/to/source is one possible URL for source, assuming it's locally accessible from target; you could use any git URL, or if source is already a configured remote of target, you can use the remote name instead of the url.)

Now comes the tricky part; you need to rebuild the stash reflog on target.

First you need to keep track of any entries already in targets stash reflog. You can do this with tags, the same as the stashes from source.

$ git tag stash-t0 stash
$ git tag stash-t1 stash@{1}
// etc.

And, again, make note of existing stash entry messages

$ git reflog stash
3333333 stash@{0}: WIP on master: 7654321 2

Then you can remove the stash ref. Normally I wouldn't circumvent the git interfaces but in this case, it's not like there's a "safe" way to do it.

$ rm .git/refs/stash
$ rm .git/logs/refs/stash

And finally you can build the new stash stack. Your first command will be

$ git update-ref --create-reflog -m "<stash-message-1>" refs/stash <tag-name-1>

or, on new enough versions of git

$ git stash store -m "<stash-message-1>" <tag-name-1>

where <stash-message-1> and <tag-name-1> are the stash message you recorded for what will now be the last (oldest/"bottom") stash on the stack, and the tag you used to preserve that stash state, respectively. Each subsequent command will be

$ git update-ref -m "<stash-message-n>" refs/stash <tag-name-n>

or

$ git stash store -m "<stash-message-n>" <tag-name-n>

progressing "forward through time" in the list of stashes.

And then you can do away with the tags you used.

$ git tag -d stash-s1
// ...

[1] It's feasible in git to create temporary branches then use interactive rebase as needed to clean the history up as you finally are ready to migrate it onto a "real" branch. And a stash is just as "heavy-weight" as real commits anyway, because a stash is real commits. A stash is great to push some changes out of the way for a minute so you can use your worktree for something else real quick, but a long-lived stash isn't the best thing.

like image 94
Mark Adelsberger Avatar answered Sep 30 '22 14:09

Mark Adelsberger