1

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.

Francis Davey
  • 740
  • 2
  • 9
  • 15
  • What you could do is `git checkout HEAD~1` to change back to the old version, save that somewhere and then `git checkout master` to get back to the latest version and merge the changes yourself. – Will Richardson Apr 19 '16 at 10:05
  • Possible duplicate of [How can I merge two commits into one?](http://stackoverflow.com/questions/2563632/how-can-i-merge-two-commits-into-one) – nisevi Apr 19 '16 at 10:34
  • It isn't a duplicate of that one because I don't want to end up with one commit or change my history in any way. Really I don't care what the history looks like. – Francis Davey Apr 19 '16 at 10:47

5 Answers5

4

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).

poke
  • 369,085
  • 72
  • 557
  • 602
  • In my case there was no X (A was the first commit), so this doesn't work, but it is quite illuminating. I can't think of another merge tool that would do the trick, so maybe a copy of each state is the only way to do it. Thanks. – Francis Davey Apr 19 '16 at 10:40
  • 1
    You could maybe create a dangling branch here instead (one without a parent), but I’m actually not sure how the merge would behave then. In any case, yes, you would be better off merging it manually by simply copying the files from each commit. – poke Apr 19 '16 at 11:43
  • 1
    The dangling branch will do the trick: when git goes to merge branches with no common ancestor, it simply uses an empty tree as the common ancestor. This means that every file from the two tip commits being merged is newly-created. You get a create/create conflict (with the usual conflict markers) if and only if the contents differ. There is a way to merge them without making the orphan branch though. – torek Apr 19 '16 at 20:23
  • Thanks. I have accepted this answer because it would solve the problem and offers a neat answer to the situation I found myself in. – Francis Davey Apr 20 '16 at 09:18
0

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
Tim
  • 41,901
  • 18
  • 127
  • 145
0

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

Community
  • 1
  • 1
nisevi
  • 627
  • 1
  • 10
  • 27
  • But if I reverse the last commit that doesn't help me merge the changes in that commit with the previous one does it? It isn't that I made a mistake, but that I added to overlapping new sets of changes to a file in two successive commits and want to merge them. I don't want to back out of the last set (far from it). – Francis Davey Apr 19 '16 at 10:42
  • mmmm sorry I think that I misunderstood what you want... so you have two commits but they are in the same branch ? if so, what you want I think that it is not to 'merge' two commits, you want to 'rebase' them!...here you have the documentation for 'rebasing' https://git-scm.com/docs/git-rebase and here a question in stackoverflow where they answer how to do it: http://stackoverflow.com/questions/435646/combine-the-first-two-commits-of-a-git-repository – nisevi Apr 19 '16 at 12:09
  • OK. Will that make me a file with the conflicts between the two commits in it, or is some other step needed? In a sense what I am trying to do is split the two commits as if neither followed the other (so that git merge would detect a conflict) at the moment git knows that one succeeds the other. – Francis Davey Apr 19 '16 at 12:22
  • Well the thing is that if you did it one after the other, and in the same branch, git it won't detect that as an error, because you change it the file one commit after the other, another thing that you can do is to reverse the last commit, save that file in other folder, and then you can reverse again the last commit since the last commit it changed.... so first reverse the last commit and save that file anywhere, then reverse again since the last commit already changed you will have the two states of the file, the one that you saved anywhere and the last-last commit. Makes sense? – nisevi Apr 19 '16 at 12:45
  • Yes. I can get copies of the two versions (eg using `git checkout`) but I wondered if I couldn't use git's conflict system from `git merge` to show me the conflicts between the two versions in an editable fashion as if this had been done by two different people. I guess I could create a fake new file in another branch. I don't know if that would work. – Francis Davey Apr 19 '16 at 12:48
  • 1
    Yup I think that it will trigger a conflict... here you have some links where they explain quite well how to trigger a conflict when git merging: http://jonathanmh.com/how-to-create-a-git-merge-conflict/ and these other two: http://gitimmersion.com/lab_29.html && http://gitimmersion.com/lab_30.html – nisevi Apr 19 '16 at 13:03
0

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.

enter image description here

Community
  • 1
  • 1
CodeWizard
  • 128,036
  • 21
  • 144
  • 167
  • I am afraid that really doesn't work. `git merge` will just overwrite with the most recent set of changes (or rather it will accept them). See poke's answer above. No conflict is created because git has no idea that I have messed up like that. – Francis Davey Apr 19 '16 at 10:44
  • I agree that there will be no conflict. This is why i showed you the diff so you can see the changes. – CodeWizard Apr 19 '16 at 10:48
0

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.)


P.S.: 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. :-)

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks. This is very useful. I accepted poke's answer but this may have been more appropriate to my question (which involved a single file). Poke's is more generic and would work for multiple files, which is also really useful. Sorry I can't accept both. – Francis Davey Apr 20 '16 at 09:21
  • @FrancisDavey: poke's method is usually the right answer. The thing about making the orphan branch is that you will still get that create/create conflict with git marking the entire file as conflicting... – torek Apr 20 '16 at 09:31
  • Aha. I think I might also find it easier to recall what I had done when looking at the history. – Francis Davey Apr 20 '16 at 09:33