pick to a commit beyond a merge
This is my situation:
I am trying to place an exact copy of an earlier commit on top of my latest commit. However,
git reset --hard
(or, I would be happy to do so if I could somehow then add the earlier commit back on top of the latest commit) I know this is probably a simple problem, but after much searching/trial and error I still cannot find a solution. Any help would be greatly appreciated.
In case you're wondering why I want to do something like this, the original motivation was in order to easily use git-latexdiff
to compare two versions separated by a merge.
To reproduce this scenario/see an example:
In order to make this scenario reproducible, the commands below will make a folder called tryrevert
with the necessary structure:
mkdir tryrevert; cd tryrevert; git init; echo 'line 1 commit 1' > file1.txt; git add .; git commit -m 'commit 1'; echo 'line 2 commit 2 only want these two lines' >> file1.txt; git add .; git commit -m 'commit 2 - WANT THIS ONE'; git branch sidebranch; git checkout sidebranch; echo 'another file - only in sidebranch' > sidefile.txt; git add .; git commit -m 'commit 3 - sidebranch work'; git checkout master; echo 'extra work' >> file1.txt; git add .; git commit -m 'commit 4 - some more work on master'; git merge sidebranch -m 'commit 5 - the merge'; echo 'final work after the merge' >> file1.txt; git add .; git commit -m 'commit 6 - latest commit '
git log --graph --all --decorate --oneline
and will output the following commit tree (excluding origin/master
):
* 7c4c7a1 (HEAD, origin/master, master) commit 6 - latest commit
* 5b0077c commit 5 - the merge
|
| * 20a1164 (sidebranch) commit 3 - sidebranch work
* | 3a5a24f commit 4 - some more work on master
|/
* 925228b commit 2 - WANT THIS ONE
* 52af39e commit 1
(I left sidebranch
where it is rather than moving it up to master
for clarity.)
So what I want is :
* abcdefg (HEAD, master) COPY OF commit 6
* hijklmn COPY OF commit 2
* 7c4c7a1 (origin/master) commit 6 - latest commit
* 5b0077c commit 5 - the merge
|
| * 20a1164 (sidebranch) commit 3 - sidebranch work
* | 3a5a24f commit 4 - some more work on master
|/
* 925228b commit 2 - WANT THIS ONE
* 52af39e commit 1
Notes:
i) sidebranch
creates sidefile.txt
, and all efforts I have at reverting to commit 2 end up containing sidefile.txt
(and I do not want COPY of commit 2 to contain sidefile.txt
). I would also like to avoid having to deal with merge conflicts in file1.txt
.
ii) I've tried rebase -i
and cherry-pick
, but without success (perhaps I am doing it incorrectly) - I keep running into conflicts.
iii) If I did not have the merge, something like:
git revert --no-commit master...HEAD~3; git add .; git commit -m 'reverting to commit 2'
would work for me, but the merge seems stop this exact line from working.
iv) I have spent time looking at many posts on stackoverflow, but couldn't find one that dealt with this specific scenario...
Edit:
(apologies in advance for the long edit, but want to clarify the state of the two commits)
In response to @Juan's question, and to make it clear, commit 6 contains
file1.txt
:
line 1 commit 1
line 2 commit 2 only want these two lines
extra work
final work after the merge
sidefile.txt
another file - only in sidebranch
On the other hand, in commit 2 file1.txt
contains only the first two lines:
line 1 commit 1
line 2 commit 2 only want these two lines
and there is no sidefile.txt
in commit 2.
@Juan's answer below works well for reverting/cherry-picking file1.txt
, but the created commit still contains sidefile.txt
(which I don't want).
After playing around with "theirs", "ours" and other flags, the following post provided a solution:
git checkout master~1^1~1
git checkout -b commit2branch
git merge -s ours master -m 'revert to commit2'
git checkout master
git merge commit2branch
git branch -D commit2branch
which works, in terms of getting the files to be in the correct state (correct file1.txt
, no sidefile.txt
but then the commit history contains an extra branch:
* 4236c61 (HEAD, master) revert to commit2
|
| * 7c4c7a1 (origin/master) commit 6 - latest commit
| * 5b0077c commit 5 - the merge
| |
| | * 20a1164 commit 3 - sidebranch work
| |/
|/|
| * 3a5a24f commit 4 - some more work on master
|/
* 925228b commit 2 - WANT THIS ONE
* 52af39e commit 1
while I do not want this extra branch...
Final edit
I have an answer, which I have posted below, and is based on @Juan's second solution. However, the solution uses several reverts, while I would really like a one-line solution .
First of all, I ran your script to generate the repo, I will post it here and use my commits in the answer to avoid copy/paste/change errors:
* f2cb927 (HEAD, master) commit 6 - latest commit
* 93e6ede commit 5 - the merge
|
| * 61f0cfe (sidebranch) commit 3 - sidebranch work
* | c1d2916 commit 4 - some more work on master
|/
* 79570e8 commit 2 - WANT THIS ONE
* 5e7f2fb commit 1
Then there are different possibilities:
First: to end up with your second graph, you could do it with two cherry-picks, first of commit 2, then of commit 6. This will have conflicts and as you are taking a commit contained in the same branch history you would probably want to use their changes in case of conflict, so assuming that you are checked out in master
you would probably want to do this:
$ git cherry-pick --strategy=recursive -X theirs 79570e8
and afterwards the same for commit 6:
$ git cherry-pick --strategy=recursive -X theirs f2cb927
With this you would end up with a graph as you want:
* f6e3ff7 (HEAD, master) commit 6 - latest commit
* 7563975 commit 2 - WANT THIS ONE
* f2cb927 commit 6 - latest commit
* 93e6ede commit 5 - the merge
|
| * 61f0cfe (sidebranch) commit 3 - sidebranch work
* | c1d2916 commit 4 - some more work on master
|/
* 79570e8 commit 2 - WANT THIS ONE
* 5e7f2fb commit 1
But I understand correctly this is not what you want, as the repo would contain File1:
line 1 commit 1
line 2 commit 2 only want these two lines
extra work
final work after the merge
And File2:
another file - only in sidebranch
Git will take into account every single commit contained in the branch, giving preference to newer commits, but a cherry-pick will not 'undo' commits from the branch, for that purpose you would need to use revert.
Second possibility: This is complex as requires somehow dealing with conflicts and this can be messy when reverting things. Well, I created a new branch called 'new' in commit 5:
$ git checkout 93e6ede
$ git branch new
$ git checkout new
and from it I ran:
$ git revert c1d2916
$ git revert 61f0cfe
$ git revert -m 1 93e6ede
And I ended up with the following tree (note new branch):
* 9eeac5c (HEAD, new) Revert "commit 3 - sidebranch work"
* e1f3678 Revert "commit 4 - some more work on master"
| * f6e3ff7 (master) commit 6 - latest commit
| * 7563975 commit 2 - WANT THIS ONE
| * f2cb927 commit 6 - latest commit
|/
* 93e6ede commit 5 - the merge
|
| * 61f0cfe (sidebranch) commit 3 - sidebranch work
* | c1d2916 commit 4 - some more work on master
|/
* 79570e8 commit 2 - WANT THIS ONE
* 5e7f2fb commit 1
Having in 'new' only File1:
line 1 commit 1
line 2 commit 2 only want these two lines
Then, if you want commit 6 as well without having to modify the history, you would need to add your work after commit 6 now that you can deal with conflicts properly, which is the last one:
$ git rebase f2cb927
Here there is no other solution than dealing with the conflicts and manually selecting what you want.
Third possibility and probably best seen how messy the second is: With a different branch (to see the changes), say new2, checkout in commit 2. Then simply do:
$ git cherry-pick f2cb927
Here you might have conflicts but they should be quite straight forward to solve, as all you want in the actual content of commit 6. Once conflicts are solved:
$ git add files
$ git cherry-pick --continue
Then your final tree with all three possibilities would look like this:
* 2ded558 (HEAD, new2) commit 6 - latest commit
| * ec8ef14 (new) Revert "commit 3 - sidebranch work"
| * 9f8bb91 Revert "commit 4 - some more work on master"
| | * f6e3ff7 (master) commit 6 - latest commit
| | * 7563975 commit 2 - WANT THIS ONE
| |/
| * f2cb927 commit 6 - latest commit
| * 93e6ede commit 5 - the merge
| |
| | * 61f0cfe (sidebranch) commit 3 - sidebranch work
| |/
|/|
| * c1d2916 commit 4 - some more work on master
|/
* 79570e8 commit 2 - WANT THIS ONE
* 5e7f2fb commit 1
Just go for your preferred option, you obviously don't need the three of them. Assuming that you want you would need to force the push git push --force origin branchName. Doing this whatever else was in remote before would be lost and replaced by the new data on the push.
Thanks to @Juan - his second solution could be rewritten as:
git checkout master
git revert --no-commit HEAD...HEAD~1
git revert --no-commit -m 1 HEAD~1
git revert --no-commit --strategy=recursive -X theirs HEAD~1^1
git commit -m 'COPY of commit 2 using revert'
and with:
git revert --no-commit HEAD
git commit -m 'COPY of commit 6 using revert'
this would produce the desired commit tree:
* a30d0b1 (HEAD, master) COPY of commit 6 using revert
* bcbd36f COPY of commit 2 using revert
* 7c4c7a1 (origin/master) commit 6 - latest commit
* 5b0077c commit 5 - the merge
|
| * 20a1164 commit 3 - sidebranch work
* | 3a5a24f commit 4 - some more work on master
|/
* 925228b commit 2 - WANT THIS ONE
* 52af39e commit 1
but, I wish there were a way to do this in one line...
链接地址: http://www.djcxy.com/p/94814.html上一篇: Git将主分支替换为另一个分支
下一篇: 选择合并之外的提交