Home > Blockchain >  How to create merge conflict between up-to-date - but differing - branches?
How to create merge conflict between up-to-date - but differing - branches?

Time:04-29

I want to restore old piece of code into the current code.

Old piece of code is at an old commit that is a far parent of the current commit. Therefore there is no merge conflicts between these commits, merge results in "Already up-to-date.".

I would like to:

  • use merge conflict view for the difference between commits and use conflict resolution to track what contemporary changes I want to keep and what old code I want to restore.
  • if possible - preserve git commit history: recent commits for unrelated code and old commit history for this old snippet I want to restore.

With git checkout [--merge] I succeeded only in getting either the old or the new version of a given file.

What is best way to achieve this or similar? Should I use tools outside of git?

Thanks in advance!

CodePudding user response:

I think you'll need to craft merge commit and the file as separate actions.

To craft the commit, you can build the merge you wanted, keeping the newer file and then amend the commit with changes to the file.

To create the changes to the file, you can use git diff to look at how the file has changed over the commit range and then either patch the file or edit it manually.

If there's a lot of changes you want to pick through, you can have a new ref on the branch and then cherry pick off of the commits you don't want.

CodePudding user response:

You want, as mkrieger1 said in this comment or jthill said in this comment, either git checkout -p or git cherry-pick. My guess is that the latter is likely to serve you better, but here is what you should understand about both operations:

  • Git is all about commits, and each commit holds a full snapshot of all of your files. (There's more to it than this but this is the key item here.) These files are frozen for all time, and are otherwise like a tar or zip archive that you (or whoever) made of the source at the time you ran git commit. They're stored in a special, compressed, read-only, Git-only, de-duplicated format, so they don't take extra space in spite of the fact that most snapshots mostly re-use most of the files from previous snapshots, but they're still full snapshots of all files.

  • Git shows a commit by comparing the snapshot in that commit to the snapshot in its parent commit. That is, suppose commit b789abc has parent a1234546. (Each commit has a unique number, represented as a big ugly hexadecimal hash ID; these are abbreviated hash IDs.) There are two snapshots for these two commits. Because the files in these snapshots are de-duplicated, Git can very quickly and easily discard, from the display, all the files that are 100% identical. For the remaining (non-identical) files, Git compares them, as if playing a game of Spot the Difference, and shows you which lines differ.

This is why it looks like a commit holds a change, but in fact it doesn't. (This is particularly important for merge commits, which have two parents but just one snapshot. The commit-showing code has a problem here because there is no obvious parent to use to do a comparison, so by default, git log -p just doesn't bother showing anything at all about the merge. The git show command by default does something clearly better, but arguably still wrong or misleading.)

The difference engine in Git is quite efficient and powerful, and can compare any two commit snapshots. It can also handle a commit snapshot and a checked-out tree, for instance, and some other important cases, but let's just concentrate on the snapshots here.

This means you can run:

git diff <hash1> <hash2>

and Git will produce a diff—a change recipe—that, if applied to the snapshot from hash1, will produce the snapshot in hash2. You can take this snapshot and edit it down to just a small set of changes and feed that to git apply, if you like.

But you don't have to do that. The git cherry-pick command will take any given single-parent commit—it has trouble with merge commits for the obvious reason—and compare it to its parent, the way git log -p or git show does, to see what changed. Then it will attempt to apply the same change to the current commit. So you just check out what you want modified, then git cherry-pick the original commit that made that change, and now you have the same change made again. The cherry-pick command even copies the original commit's log message (and author!) for you. You can edit the message with --edit and reset the author with --reset-author.

If the change you want is mixed with other changes, and/or requires cherry-picking many commits, you can instead have Git run a diff of just the one file—the equivalent of:

git diff <hash> -- my/one/file.ext

—and then interactively let you pick each change shown here to the one selected file. To get that, you just run:

git checkout -p my/one/file.ext

or:

git restore -p my/one/file.ext

(Both commands do the same thing here; git restore is just the new, less-error-prone way to do this. The old git checkout command does too many jobs: it implements both git switch and git restore. To avoid having just one device that both applies a band-aid and replaces your entire body, the Git folks finally split this up into two separate commands.)

  • Related