7

I wanted to "follow-up" with another question about this matter: Checkout old commit and make it a new commit

But they say "Don't Do That!" so it seems I must ask a new question. (Even though it is the same question where the answer I think is best for me didn't work as expected...

If I have commits A-B-C-D-E-F (imagine these are all SHA's). I want to make the entire repository exactly as C and then commit that to create 'G' which is exactly like C. So that when I am done the log is A-B-C-D-E-F-G even though C & G are identical.

One answer indicated cherry-pick, which seemed perfect, except it made me resolve every conflict between C and F. I looked at the documentation and I tried --force anyway, and then the recommendation for --patch. --patch was closest but it wasn't absolute. So is there a way to make cherry-pick just choose the C version of the conflict?

The other closest answer would be to checkout C, but I couldn't find the magic word that would keep it on the same branch. The recent training I completed said use the "magic dash dash" at the end to tell git you want this on the current branch, but no matter what I do it creates a "(no branch)" branch.

As I am sure you can tell, I am pretty new to git (and command line in general) but I tried to figure it out on my own. If you could use the verbose versions of what you recommend then it sticks in my head better and would be greatly appreciated. (-a = --all or -a = --annotate or -a = --albuquerque?)

It seems so simple, and exactly what you might want to do with git -- go back a previous commit without losing the intermediary commits in case you change your mind.

Community
  • 1
  • 1
Brad Lowry
  • 69
  • 4

8 Answers8

5

Do you mind generating: A-B-C-D-E-F-F'-E'-D' where the state of the code at D' is exactly as it was at C (so G == D')? In that case, just revert F, E, and D. If you do not want the intermediate steps, revert but do not apply, and then commit. That is:

$ git revert -n HEAD
$ git revert -n HEAD~
$ git revert -n HEAD~2
$ git commit -m 'Revert D,E,and F'

After this, the current HEAD is G, and the two commits G and C should contain the same code tree. You can verify by checking the output of git cat-file -p HEAD | grep ^tree and git cat-file -p C | grep ^tree. Both of those commands should give the same output.

But it's not really clear why you want to keep the intermediate commits. If you want them for posterity, then do something like: git branch old-stuff, git reset C That will set your current branch back to C, but D, E, and F can still be viewed in the branch named old-stuff.

William Pursell
  • 204,365
  • 48
  • 270
  • 300
  • Again, perhaps the answer is "No, there is not a simple one-step command to place the entire repository back to a previous commit-state _as_ a new commit". I know I _can_ do it with branching or successive reverts, but those are overkill for my use case. Thanks! – Brad Lowry Jul 03 '13 at 17:02
1

I thihk you need to use git cherry-pick.

https://www.kernel.org/pub/software/scm/git/docs/git-cherry-pick.html

urmaul
  • 7,180
  • 1
  • 18
  • 13
  • Yes. I would _love_ to use cherry-pick. The goal is to find the sequence of parameters that I can add to `git cherry-pick C` so that it doesn't ask me to resolve all the conflicts along the way. Thanks! – Brad Lowry Jul 03 '13 at 17:05
1
git cherry-pick --strategy=recursive -X ours C

git-merge(1):

The recursive strategy can take the following options:

ours

This option forces conflicting hunks to be auto-resolved cleanly by favoring our version. [...]

The "ours" and "theirs" refer to the two commits being merged. However, I'm not sure which way around git cherry-pick treats the commits, so you might need -X theirs instead. You could make a new branch, to try it and see.

13ren
  • 11,887
  • 9
  • 47
  • 64
0

If you want to end up with: -G-D-E-F (not sure, I'm confused by "Where D, E and F are right there.") Then the best way to get there is rebase -i, and squash A-B-C together.

ptyx
  • 4,074
  • 1
  • 19
  • 21
  • Sorry, that phrase was not precise. What I want in the end is the Full Commit History of A-B-C-D-E-F-G, even though C & G just happen to be identical. I'm excited to find a good use case for rebase and I hear how great it is, but from what you write I don't think it is a good fit here. Thanks! – Brad Lowry Jul 03 '13 at 17:10
  • I'm a bit confused about why you would want to do that? It's not something you'd want to push, as it's a disguised revert of D-E-F. You could actually go there with git revert D E F. But why? If you want to keep D E F for reference, then push them to a separate branch and then reset hard to C seems like a better solution. – ptyx Jul 03 '13 at 18:55
0

Judging from your comment to my other answer (which I am not deleting, since that solution is more correct than this, but this seems to be what you want), you can just use the plumbing command commit-tree as follows:

git reset $( git commit-tree $tree -p F -m 'Revert D,E, and F' )

where $tree is the hash of the tree object underlying commit C. (The output of git cat-file -p C | grep ^tree ). This will create a new commit in which the underlying tree is identical to that of commit C, but the parent of the new commit is commit F. It also sets the current branch to point to that new commit, making it the new HEAD.

William Pursell
  • 204,365
  • 48
  • 270
  • 300
0

Could you describe what you want as: "make your working tree the same as C, then commit that"? If so, here's a way to do that:

git checkout C     # working tree same as C. But also moves HEAD to C...
git reset F        # ...so move HEAD back to F, leaving working tree alone
git commit -a      # commit working tree

EDIT this might produce a bunch of merge conflicts, so it doesn't address that part of your question.

EDIT3 Again, the selected answer to your previous question solves this: by deleting everything first, there is no conflict.


BTW: it would be nice to make the working tree equal to commit C without changing HEAD, but I don't think there's a (porcelain) to do that. Both git checkout and git reset change the current branch if you want the whole commit. git checkout can extract individual files to the working tree without moving the current branch, but then you have to extract each file individually, not the whole commit...

EDIT2 oh I see from the selected answer to your previous question, you can do this. I had tried git checkout C -- "*", but you need git checkout C -- . So it would just be:

git checkout C -- .    # working tree same as C, without moving branch (NB ".")
git commit -a
Community
  • 1
  • 1
13ren
  • 11,887
  • 9
  • 47
  • 64
0

Looks like you want C to be the final state of your git repository.

Take "A-B-C-D-E-F-G" as an example,

You can just revert G,F,E,D in order using

git revert [commitID]

Then use git commit

After G,F,E,D are all reverted, your repository will be staying in the state same as C.

An alternative way for local branch only is:

git resert --hard HEAD~4

This will reset your local branch to commit C and drop D,E,F,G.

Jun
  • 426
  • 1
  • 7
  • 16
0

I actually submitted my own question about it before I found about yours, but anyway - you can do that using tree hashes.

The idea is that each commit has a "tree object" associated with it which describes the entire working tree, so all you need to do is create a new commit that has the same tree object as the old commit, and add it to HEAD.

Here's a script that does it:

#/bin/bash
COMMITID=$1
git reset --hard $(git commit-tree -m "Revert to commit $COMMITID" -p $(git rev-parse HEAD) $(git rev-parse $COMMITID^{tree}))
sashoalm
  • 75,001
  • 122
  • 434
  • 781