2

I work on a project where we have several releases in progress at any given time. As PRs are merged, those changes are also auto-merged into "downstream" releases, e.g. a PR into release/10.ABC gets downstream merged into release/11.XYZ and release/12.001. Here, release/12.001 is "downstream" of release/10.ABC and release/11.XYZ.

In release/12.001, our technology changed, and dir_a/api.py was moved to dir_b/api.py. Now, as PRs are being merged in to release/11.XYZ based on the former technology, we're getting downstream merge conflicts.

We'd like the repository to not block downstream merging because of these falsely perceived merge conflicts every time an upstream branch changes the old implementation.

In retrospect, we should have done this:

  1. remove dir_a/api.py and commit
  2. add dir_b/api.py and commit

Unfortunately, this was not done; they were added and removed in the same commit. Is there any way to retroactively perform equivalent actions (preferably without rewriting former commit history), e.g. from this commit forward, mark these files as separate for future merges?

Note

Git doesn't store the fact of whether it's moved vs deleted and added, and instead it relies upon similarity detection; see How to make git mark a deleted and a new file as a file move? and all of its linked questions for more information about the opposite issue: how to force git to recognize a rename.

Our issue is that we intended for something to be an add and a remove of separate files, and git is detecting similarities we'd prefer it not to detect going forward. We're not interested in changing the global threshold on the similarity index for git merge, the problem is limited to one file.

Does git have a file-specific flag which can impact merge strategy that might be useful for us in preventing merge conflicts from upstream branches?

Of course, I realize that git has no concept of upstream / downstream branches, only commits, but I'm not sure how better to state the question.

If this is not possible, then we'll just have to resolve merge conflicts on a one-by-one basis as upstream PRs come in.

Myer
  • 3,670
  • 2
  • 39
  • 51

1 Answers1

0

In retrospect, we should have done this:

  1. remove dir_a/api.py and commit
  2. add dir_b/api.py and commit

That might well be better in some sense, but it would have made no difference to your git merge operation. Git only looks at the merge base (starting point for both branch tips) and the two branch tips. (However, you could do two separate merges: one to merge the deletion, and a second to do the creation, for which the two-step method would have helped.)

Does git have a file-specific flag which can impact merge strategy that might be useful for us in preventing merge conflicts from upstream branches?

No. What you'll need to do, absent the two-merge method I mentioned parenthetically above, is run the merge without committing the result and then manually fix the merge result and commit, or run the merge and let Git do the "wrong thing", and then manually fix the result and commit. Both produce the final snapshot you want. The two-commit method takes an extra commit step, but avoids creating an evil merge. If the merge would complete on its own, the evil merge method has the drawback that a later operation that repeats the merge silently generates Git's merge, rather than your manually constructed evil merge, so that can make the two-step method superior.

Is there any way to retroactively perform equivalent actions (preferably without rewriting former commit history), e.g. from this commit forward, mark these files as separate for future merges?

Not exactly, but you can use git replace to insert some grafted history. Note that git clone by default does not copy the replacements, so that clones of a grafted repository show the original (un-grafted) commits.

If you want to use the two-separate-commits, two-separate-merges strategy, and can make a grafted repository in which to do that, that's fine: just remember to use this grafted repository (and the two separate merges) each time. I'm not sure what the "best" (or easiest) way would be to build the grafted replacements, but you could certainly set up the two commit snapshots by doing the merge "right" this time. It looks like git replace <commit-object> <replacement-second-merge> would do the job afterward, but I have never actually tried this.

torek
  • 448,244
  • 59
  • 642
  • 775