Git allows you to lose the history of file changes during merge with conflicts

I have been using Git for a long time, but recently I came across an interesting trick that allows you to revert the history of file changes during merge. Here are the steps to play:

I have a Git repository with two files and one commit:

$ git branch * master $ git log --oneline 80c8d5a Initial commit $ git log --oneline -- README 80c8d5a Initial commit $ ls README conflict_file 

I create a new experimental branch and modify the README files and contact_file:

 $ checkout -b experiment Switched to branch 'experiment' $ vim README $ vim conflict_file $ git commit -am "Experimental changes" [experiment cdbc988] Experimental changes 2 files changed, 2 insertions(+) $ git log --oneline -- README cdbc988 Experimental changes 80c8d5a Initial commit 

Returning to the master branch and editing the conflict file (for conflict during merge):

 $ git checkout master Switched to branch 'master' $ vim conflict_file $ git commit -am "Master changes" [master ad8b68e] Master changes 1 file changed, 1 insertion(+) 

The status of my repository is as follows:

 $ git log --oneline --decorate --graph --all * ad8b68e (HEAD, master) Master changes | * cdbc988 (experiment) Experimental changes |/ * 80c8d5a Initial commit 

Attempt to merge with the experimental branch:

 $ git merge experiment Auto-merging conflict_file CONFLICT (content): Merge conflict in conflict_file Automatic merge failed; fix conflicts and then commit the result. $ git mergetool <merging conflict in conflict_file> 

Here is the trick:

 $ git reset HEAD README Unstaged changes after reset: M README $ git commit [master 909139f] Merge branch 'experiment' $ git log --oneline -- README 80c8d5a Initial commit 

I lost the changes and the history of the README file that was introduced into the experimental branches in Experimental changes . Can anyone comment on how this relates to the idea that Git knows about all the changes? Is it possible to avoid this scenario? This can be a problem in our company, because the merger is allowed for developers, but they can accidentally delete some changes.

+7
source share
2 answers

History is not lost: a change in README made on the experimental branch is still contained in commit 210fdc1 . The fact that it is not part of the commit 909139f association occurs solely because you explicitly returned it before the merge was completed.

You did not say that you expect me to be able to guess, so I can only guess what exactly surprised you. I just point out two likely candidates.

  • Merge approval is not limited to modifying only files affected since the merge database was created, or even only files that have conflicting changes. In fact, he can be completely different from all his parents.

    “Absolutely different”, of course, is not something that could be recommended, because it would break all assumptions about what the merger is doing. However, it may make sense to expand the documentation files in a merge transaction, and additional files may need to be modified to resolve certain merge conflicts.

  • When you call git log -- some_path , a process called history simplification is called. As the man page describes, git log will try to display only those commits "that are enough to explain how the files that match the specified paths become." In your case, the initial commit is enough to explain the current state of README (since README still has the same content or again), so that’s all that is shown.

    You might want to use --full-history , perhaps along with --simplify-merges or some other options to simplify the story. These, as well as the exact rules for including commits (which are more complex than previously implied), are described in man git log , with an extended example.

+7
source

In fact, you have not lost the changes, you have not yet noted the changes in the README , so they are not in any commit displayed by your git log commands:

 * 9b8ede8 (HEAD, master) Merge branch 'experiment' |\ | * 210fdc1 (experiment) Experimental * | d0c1637 Master changes |/ * b558d42 Initial commit 

(my SHAs are, of course, different) and:

 $ git status # On branch master # Changes not staged for commit: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: README # # Untracked files: # (use "git add <file>..." to include in what will be committed) # # conflict_file.orig no changes added to commit (use "git add" and/or "git commit -a") 

As @Chronial noted in the comments, you explicitly told git to do git reset --mixed ( --mixed by default) the README for then- HEAD , which was (in your case) ad8b68e .

If you ran git status in the middle of your conflict (before git mergetool ), you would see this:

 $ git status # On branch master # You have unmerged paths. # (fix conflicts and run "git commit") # # Changes to be committed: # # modified: README # # Unmerged paths: # (use "git add <file>..." to mark resolution) # # both modified: conflict_file # 

The git mergetool step resolves the conflict by moving the conflict_file to the same commit ready state as the README above, but after reset --mixed , the README moves from "changes to be made" to "changes not set for commit".

+1
source

All Articles