Git - How to edit old (not previous) commit with s

2020-02-10 07:13发布

I've taken a look at these previous questions already:

They don't exactly address a particular issue though - there are other changes in the index! When running the rebase command, git complains: Cannot rebase: You have unstaged changes.

Scenario:

The commit previous to the last one (do I refer to that as "2 HEADs ago"?) was a refactor commit. I currently have in the index many unstaged changes, but only some of which I want to add to the previous to last commit.

I'm imagining the way to do this would be to:

  1. stash all of my current changes
  2. rebase -i to the previous to last commit (changing index and moving Head, right?)
  3. load the stash into my index without changing Head (how?)
  4. use add -p and commit --amend to selectively modify this old commit
  5. rebase --continue to finish (updates children, moves Head back to where I started, what happens to index?)
  6. then pop/clear the stash (index back to where I started).

Is this correct? If it is, how do I do step 3? If it isn't, what should I be doing instead?

Also, note that I'm still learning about git and am still not 100% sure I'm referencing things in git (Head, index, stash, etc) properly.


Solution:

For anyone else this may help, these are the steps I actually took:

  1. git stash all of my current changes
  2. git rebase -i <ID> to the parent of the previous to last commit, changing index and moving Head
  3. git stash apply load the stash into my index without changing Head
    • if you have conflicts, git reset HEAD <file> to unload files staging. Make sure staging is clear.
  4. use add -p and commit --amend to selectively stage changes and commit them
  5. git reset --hard to discard index so it matches Head
  6. git rebase --continue to finish. updates children, moves Head back to very start, but with changes
    • history is now forked into two versions. The other branch ends at the WIP previously stashed
  7. then pop the stash to bring index back to where I started. The other branch is also cleared.

标签: git
4条回答
冷血范
2楼-- · 2020-02-10 07:29

Your plan sounds good. A git stash apply or git stash pop will modify the working tree and/or index, but will not change HEAD, so you should be able to do it while in a rebase edit.

查看更多
家丑人穷心不美
3楼-- · 2020-02-10 07:42

With git1.8.4 (July 2013), you can choose to:

  • rebase
  • while keeping your local unstaged changes!

"git rebase" learned "--[no-]autostash" option to save local changes instead of refusing to run (to which people's normal response was to stash them and re-run).

So in your case, this could work (and save your work in progress in the meantime):

git rebase --autostash -i <ID>

See commit 587947750bd73544a6a99811f0ddfd64e1ff1445:

This new feature allows a rebase to be executed on a dirty worktree or index.
It works by creating a temporary "dangling merge commit" out of the worktree and index changes (via 'git stash create'), and automatically applying it after a successful rebase or abort.

rebase stores the SHA-1 hex of the temporary merge commit, along with the rest of the rebase state, in either .git/{rebase-merge,rebase-apply}/autostash depending on the kind of rebase.
Since $state_dir is automatically removed at the end of a successful rebase or abort, so is the autostash.

The advantage of this approach is that we do not affect the normal stash's reflogs, making the autostash invisible to the end-user.
This means that you can use 'git stash' during a rebase as usual.

When the autostash application results in a conflict, we push $state_dir/autostash onto the normal stash and remove $state_dir ending the rebase.
The user can inspect the stash, and pop or drop at any time.


Note: git 2.0.1 (Jult 25th, 2014) handle the autostash case.
See commit e4244eb from Ramkumar Ramachandra (artagnon) (also in his blog):

rebase -i: handle "Nothing to do" case with autostash

When a user invokes

$ git rebase -i @~3

with dirty files and rebase.autostash turned on, and exits the $EDITOR with an empty buffer, the autostash fails to apply.

Although the primary focus of rr/rebase-autostash was to get the git-rebase--backend.sh scripts to return control to git-rebase.sh, it missed this case in git-rebase--interactive.sh.
Since this case is unlike the other cases which return control for housekeeping, assign it a special return status and handle that return value explicitly in git-rebase.sh.


With the latest git, simply do git config rebase.autostash true to make rebase [-i] work in a dirty worktree.

This new feature allows a rebase to be executed on a dirty worktree or index.
It works by creating a temporary "dangling merge commit" out of the worktree and index changes (via 'git stash create'), and automatically applying it after a successful rebase or abort.

rebase stores the SHA-1 hex of the temporary merge commit, along with the rest of the rebase state, in either .git/{rebase-merge,rebase-apply}/autostash depending on the kind of rebase. Since $state_dir is automatically removed at the end of a successful rebase or abort, so is the autostash.

The advantage of this approach is that we do not affect the normal stash's reflogs, making the autostash invisible to the end-user. This means that you can use 'git stash' during a rebase as usual.

查看更多
Root(大扎)
4楼-- · 2020-02-10 07:54

If I understand what you want to do, it's not much different than the answer on your first link, you just need to stash the changes you don't want to add to the older commit. You can do it like this:

  1. Commit the changes you want added to your penultimate commit. Just give it a simple commit message like "add this," as it won't be around for long.
  2. Stash the remaining changes that you don't want added to the commit.
  3. Look up the parent of the commit you're adding to. For example, if the commit you want to modify has an SHA1 of aaaaaaa and its parent is bbbbbbb, you want bbbbbbb.
  4. Do git rebase -i bbbbbbb (substituting the correct commit as determined in step 3). Move the most recent commit ("add this") up to just below the commit you're modifying and change it from pick to fixup. This will add it to that commit without change that commit's message.
  5. Unstash the changes from step 1, and carry one from there.
查看更多
干净又极端
5楼-- · 2020-02-10 07:54
`git stash`
`git checkout <COMMIT HASH from 2 commits ago>`
`git stash pop`
`git add` (selectively--one file at a time)
`git commit --amend`
`git stash` (to re-stash all the changes you didn't commit)
`git branch temp`
`git checkout master`
`git rebase temp -i`

This will open a text editor with a list of commits--one commit per row. Delete the first row, which is the old commit that you want to replace. Save and close.

`git branch -d temp` (to clean up the branch you created)
查看更多
登录 后发表回答