How to "reopen" an ancient commit into worktree that was reverted in a recent commit?

Viewed 107

My goal is to revisit the original changes from an old buggy commit (which have since been reverted), but have those ancient changes in my worktree at the repo's HEAD since many things have changed, fix the problems, and submit a new better commit sans the problems.

A thousand commits ago (a month ago), I had committed a change.

A hundred commits ago (a week ago), someone discovered a subtle-but-unacceptable issue with the commit, so as to quickly unblock that person I reverted the commit (git revert hash ... worked like a charm).

A sprint has passed, and now at HEAD in my repo, I'd like to re-open the changes from the original commit that have since been reverted, so I can comb through the changes and figure out the root cause of the discovered problem.

What is a good way to "re-open" the commit from a thousand commits ago into my local worktree? (So git status would be modified in my worktree.)

Worse case scenario would just be to git checkout the old commit side-by-side in another repo and do a manual diff (e.g., vi -d current/foo.cpp ancient/foo.cpp) and copy over the differences by hand. Sounds tedious, and higher chance of manual error.

There will be a few collisions, but they'll be relatively trivial like fixed typos or whitespace changes.

I do not want to re-commit the ancient commit as-is, because it needs to be scrutinized and fixed.

2 Answers

I'd suggest finding in your log history the hash for the revert commit, then with this <commit_hash> you can

git revert --no-commit <commitHash>
# or the shorter
git revert -n <commitHash>

...which will bring into your working tree the changes from the original commit without actually committing, which lets you test, write more code, and then commit when you're ready to.

You already have a working solution, but I think it would be valuable to expand on that answer and also highlight a specific point in your question:

I do not want to re-commit the ancient commit as-is, because it needs to be scrutinized and fixed.

It's important to realize the Git wants you to make a commit; Git loves commits, and you should too! (I'm only half joking here.) The reason to lean towards committing, is, once you commit, you've locked in that change (locally on your machine only) and can easily get it back in the future if you decide not to use it, but then change your mind later. So I would suggest tweaking the mindset in that sentence to something similar to:

I do not want my final version to have the ancient commit as-is, because it needs to be scrutinized and fixed.

The subtle difference here is that you're in hacking mode and you don't know what the final version looks like yet. Therefore I would recommend you do commit the ancient commit as is (after resolving conflicts). And then you can either amend that commit, or add a new commit with your fixes, and squash those two commits down into a single commit later. The end result is the same as if you didn't "commit" the original commit in the first place (with the --no-commit option), but this way it's easier to keep undoing your attempts back to the starting point if you need to.

My proposed solution would therefore be, if you start from HEAD (either in a new branch or on the same branch), either of these, which will have the same result:

  1. Revert the revert commit.
  2. Cherry-pick the original commit that was reverted.

At this point you'll likely have conflicts. My recommendation would be to resolve them, and then --continue the revert or cherry-pick to create the new commit. Now you can hack away at the fix and perhaps put that fix into a new commit (or amend the previous commit). After more testing and tweaking you can amend again or make a third commit with the final tweaks. When satisfied, if you have multiple commits which you'd like to present as one to the shared repository, squash those 3 commits down into one and push it out.

Obviously this is just personal preference. If you wish to go against Git's grain and limit making local commits, of course you can. The --no-commit option exists, and it's available with both the cherry-pick and revert commands. But the primary reason the --no-commit option exists is for when you are reverting or cherry-picking a range of multiple commits, and you wish to put them together into a single commit. (Without that option you'd have to create multiple commits and then manually squash them down into one yourself, so this way it's more efficient.) For your particular use case, I wouldn't recommend --no-commit here. Perhaps a good rule of thumb might be to:

Use the --no-commit option when you know exactly what change you are going to make afterwards, and you intend to commit the change shortly after running the command.

Food for thought...

Related