Luke Usherwood
Luke Usherwood

Reputation: 3141

When rewriting git history, how can I apply a commit that returns everything to its prior state?

Often I want to break up a large commit:

git commit -a -m "Monolith"

I know how to split a commit (git gui is my friend) but sometimes I actually want to rework it by hand...

git branch temp          # Keep a reference to all my hard work
git reset --hard HEAD~   # Rewind 'my-topic' branch

# hack hack
git commit -a -m "Refactor 1"
# hack hack
git commit -a -m "Refactor 2"

To complete it, I then want to apply another commit that brings the code back to exactly the state stored in branch temp, usually including the original commit message.

Is there an easy way to do this?

I imagined that a merge-strategy on cherry-pick might get the job done, but alas none of these did:

  git cherry-pick --strategy=theirs temp   # I am told about conflicts
  git status                               # ... But here I find no changes to commit!
  git cherry-pick --strategy=ours temp     # Worth a try. Nope, empty commit.
  git cherry-pick --strategy=recursive --strategy-option=ours temp  # Partial result, bits are missing

I know of one way to achieve this:

git log -1 temp # note the SHA
git checkout temp
git reset --soft my-topic
git commit -C <SHA-from-above>  # branch 'temp' now contains the desired commit
git checkout HEAD -B my-topic
git br -d temp

However this seems a very error-prone way to go about it. Any little mistake and the commits I want to keep might not be in any branch (git reflog to the rescue...). I'd like to find something more logical and easier to remember/do.

Upvotes: 1

Views: 88

Answers (4)

Mark Adelsberger
Mark Adelsberger

Reputation: 45679

j6t's answer has some reasonable options, but I prefer a simplified version of the procedure you yourself suggest. (I'm not sure what errors you're afraid of making, but I suspect they arise from the fact that you're taking unnecessary steps.)

git checkout temp
git reset --soft my_topic
git checkout my_topic
git commit -C temp
git branch -D temp

This may seem like a strange preference, since git checkout -- . (one of their suggestions) looks like a one-liner; but note my comment on their answer. And their are other subtle gotchas that can creep in - like what if you forget you're not at the worktree root. You can work around all of those, of course...

So you can either be in the habit of doing git rm first, or "know" when you need to (an opportunity to make mistakes). And you can use :/: instead of . as a matter of habit (and get used to explaining it to everyone you work with), or always double-check that you're in the root (another opportunity to make mistakes). And then you get

git rm -- :/:
git checkout temp -- :/:
git commit -C temp
git branch -D temp

which is only one simple command "less" than the solution I'm suggesting.

Upvotes: 1

jthill
jthill

Reputation: 60305

The direct route to a straight import of another commit's snapshot is the core command to do exactly that:

git read-tree -u temp

followed by

git commit -C temp

to commit that with temp's commit message.

git read-tree is what underlies almost every command that updates the index and work tree from existing repo contents. git reset is some option-picking logic around git update-ref HEAD and git read-tree; git checkout is also some option-picking logic around git update-ref HEAD and git read-tree; git merge is some fairly heavy-duty cleanup work on the results of a git read-tree ... yeah. By the way, git commit is some option-picking logic around git update-ref HEAD git write-tree and git commit-tree (which latter adds a single tiny object to the object db).

Upvotes: 0

j6t
j6t

Reputation: 13387

To recreate the tree at temp, there are (at least) two fairly simple ways: One is

git checkout temp -- .

and the other is

git diff temp | git apply --index

After that, you create the commit with the same commit message using

git commit -C temp

The two options each having their pros and cons. The checkout route does not remove files that are not present in temp anymore. The apply route does not work with binary files.

Side note: With modern Git you would use git restore instead of git checkout, but I'm and old-timer and haven't learnt to use it.

Upvotes: 2

matt
matt

Reputation: 535306

Forget about your temp branch. Instead of reset hard, reset mixed.

The result is that the index is reset but the working tree is not. Now you can add and commit in any combinations you like, and then just add everything else and commit to finish up.

See my https://stackoverflow.com/a/59675191/341994 for more.

Upvotes: 0

Related Questions