简体   繁体   中英

git merging two commits

I am not sure if this is a duplicate and I am not a git expert, so forgive me for what may seem like a stupid question.

I have a file. I edited it, saved and committed those changes.

I forgot I had done this and edited the original version of the file (which happened to be open in another editor window), saved and committed a different set of changes.

I therefore have two consecutive commits.
I would like to produce a git merge style file with the conflicts between the two so that I can edit and then resolve the conflict.

Git assumes it can just use all the newer text and discard the old.
I have tried combinations of git merge and git merge -no-ff but can't really work out what to do.

Nor am I sure what search terms to use.

Update:

Just to be clear, what I wanted to end up with is a file with git merge conflicts in it. I can easily see what the diff is between the two files using git diff but the output of that is not then easy to edit. I thought git might have an easy way to say "these two commits conflict in this way..." since it must be able to do that at some level.

If you just try to merge the two commits, then Git will (correcly) recognize that one is based on the other and will not end up creating a conflict for it. After all, for Git, the history looks like this:

                   master
                     ↓
* ---- X ---- A ---- B

B is a commit that changes something that A had. Git cannot know that you created B while looking at the file state from version X .

So you have to change this fact, so Git will use X as the base of both A and B . So let's create a branch for the base first:

git branch base X

This results in this:

      base         master
       ↓             ↓
* ---- X ---- A ---- B

Now, we switch to base and checkout the file from B and commit that change (this essentially copies the state of the file from B )

git checkout base
git checkout master -- file.ext
git add file.ext
git commit
                   master
                     ↓
* ---- X ---- A ---- B
        \
         \
          C
          ↑
         base

Now you can merge A in and produce a conflict as you desired:

git merge A

After solving the conflict, this results in this:

                   master
                     ↓
* ---- X ---- A ---- B
        \      \
         \      \
          C ---- M
                 ↑
                base

At this point, you have the desired state for the file. You now have three options:

  1. Merge the base into master to keep the whole history we created artificially. For the conflict for the file (which you will receive here), you could use the theirs strategy to just keep the state of the solved solution.
  2. Copy the file contents, switch to master , and make a new commit with the merged content after B .
  3. Reset master to M (throwing away B ), and just use this new created history to continue. Of course this removes the commit B so it essentially removes history (like a rebase) which you should avoid if you already published B .

Since you are just trying to merge a single file, you're probably better of just creating a copy of each state, and merging it manually (or with some merge toool).

If you made 2 commits after one another and git didn't complain about anything, that means you have no conflicts.

I suggest you

  • soft reset the 2nd commit
  • diff against the 1st commit to see what changes the 2nd commit introduces
  • keep/discard the changes as you see fit and add / rm them
  • amend the 1st commit.

This way you will end up with one commit that contains exactly the changes you want it to contain.

In commands, it would be like this

$ git checkout sha-commit-2  # or branch name, depends on how your tree looks
$ git reset HEAD^  # soft reset, remove commit but keep changes
$ git diff  # diff against previous commit (should be the first one you mention)
--make your changes and add them--
$ git commit --amend

If you only did the commit and you haven't pushed your changes you can revert the commit that you have done, here:

How to undo last commit(s) in Git?

you will find how to undo your last commit:

$ git commit -m "Something terribly misguided"
$ git reset --soft HEAD~
<< edit files as necessary >>
$ git add ...
$ git commit -c ORIG_HEAD

Another similar info with other amenities How do I reverse a commit in git?

If the usage of git it is complicated for you an alternative is: place yourself in the branch where you edited the file, copy and save the edited file in another folder eg. "/home/your_file_here", then change your branch to master and move your edited file "/home/your_file_here" to master. Then you will have the file updated and you can check the changes using "git diff your_edited_file".

After updating your file then you can just delete the branch where you modified the file doing "git branch -d branch_to_be_removed". Additional info here: How do I delete a Git branch both locally and remotely?

So my recommendation is that instead of merging the two commits that you have, you should undo one of them, in this case the one that you have done in master. If you want to read more about 'merging' you will find really interesting info here: Git-Branching-Basic-Branching-and-Merging and here: Git-Tools-Advanced-Merging

You have several options.

The most easy one is to checkout 2 branches with the 2 version of the code and then to merge them

How to checkout 2 branches?

  1. Use the current branch as the latest version.
  2. Checkout the second branch with the old code.
    Read here a very detailed explanation on how to do it.

  3. merge the 2 branches.

Here is a demo on how to do it and what you should see (just do the merge to see the changes).

In this demo i checkout the 3rd commit back and then show you the diffrnces.

在此处输入图片说明

I have upvoted poke's answer since that is normally the way you would deal with this.

Since there is no commit before the problematic point, though, you are left with the git checkout --orphan trick as the only way to do that. This is actually a reasonable trick and you might consider it. However, there's still a problem; see below.

For completeness, let me also note that nisevi's answer 's link to Git-Tools-Advanced-Merging contains the other way to deal with this in git. Specifically, the git suite includes the command git merge-file , which is basically a clone of the old RCS file merge command.

Using git merge-file

You must feed git merge-file three input files: the current version, the common base version, and the "other" version that is to be merged-in.

You need not create a new branch or commit to use this. Simply check out both the current and previous versions.

In this case, what git would do is to use an empty file as the common base version (since there is no version previous to these two). Let's take a look at what happens:

$ git show HEAD~1:file > file.v1
$ git show HEAD:file > file.v2
$ : > file.base    # or cp /dev/null, or use /dev/null directly
$ cp file.v1 file.merged && git merge-file file.merged file.base file.v2

The flaw, and how to fix it

Note that I have merge.conflictstyle set to diff3 , so that I get the common base version here.

$ cat file.merged

<<<<<<< file.merged
This is a file
that has some text.
This is the
version that
I call
v2,
which was
created from
scratch in
the editor.
||||||| file.base
=======
This is a file
that has some text.
This is the
version that
I call
v1,
which was
created from
scratch in
the editor.
>>>>>>> file.v1

Ugh! Git treats the entire addition (from the empty base) as the two sides of the change, and finds that the entire v1 version conflicts with the entire v2 version.

The solution is simple: pick out all the common lines in the two initial versions as the common base. Actually achieving that is a bit trickier. Here are a few hints:

$ git diff --no-index -- file.v1 file.v2
[output omitted]
$ diff file.v1 file.v2
6c6
< v1,
---
> v2,

These show that line 6 is the (single) problematic line, so let's make a file.base that omits it:

$ cp file.v1 file.base && printf '6d\nw\nq\n' | ed file.base
117
113
$ cp file.v1 file.merged && git merge-file file.merged file.base file.v2
$ cat file.merged
This is a file
that has some text.
This is the
version that
I call
<<<<<<< file.merged
v1,
||||||| file.base
=======
v2,
>>>>>>> file.v2
which was
created from
scratch in
the editor.

(Note: besides git merge-file , there is git merge-one-file , which allows you to extract the files directly from the repository, but its use is not well documented, and git merge-file does what we need here, at the expense of having to clean up a bit later: namely, removing the base, v1, v2, and merged temporary files. And of course you can get away with one fewer temporary files; I used the method I did to try to make the action clearer.)


PS: You could automate creation of the common base by parsing the output of (plain) diff, or using python's difflib . I would probably choose the latter to write such a tool. It was also tempting to try to use comm somehow, but comm requires sorted inputs, and that's far too destructive. :-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM