36

I've got a branch that renames many files, and I'm trying to rebase it onto master where the original files have been modified (preferably without this devolving into a manual conflict-resolution nightmare).

Situation

  1. I've been porting a JavaScript project to TypeScript, in my local typescript branch. All of the .js files have now become .ts files, and some of the syntax has been upgraded.

  2. Meanwhile, changes to the original .js files have happened on the master branch.

  3. I want to rebase my typescript branch onto master -- but the changes are not merging correctly, as the file renames hadn't been detected -- so we're getting conflicts where changes are made to .js files which git thinks had been deleted (however they have actually been renamed to the .ts file).

What I think I know

Git graph:

o-[typescript] .js files become .ts
|
| o-[master] changes to the .js files
| |
| o
|/
o-[root] common ancestor

So, while standing in the root branch:

  • I can run this command to view all of the renames: git diff --name-status --find-renames=10% typescript

  • I understand that the git merge command has the same sort of --find-renames functionality, though I'm having difficulty getting that to work.

    • Update: git merge -X find-renames=10% mybranch appears to be the expected syntax.
  • I understand the git rebase might support find-renames functionality, though I'm still not clear about how that might be used.

Solution ideas?

  1. (Nope) Perhaps from root, I could merge in typescript while detecting the renames (like this: git merge -X find-renames=10% typescript). Then, root will be just like typescript, except with renames (rather than mass deletions/additions).

    • From there, I'm hoping I could just git rebase master, and with the renames having been in place, the operation will go smoothly and place the edits into the correct files.
      • Update: I tried this, but my subsequent rebase didn't go over any better than before.
  2. (Yep) I think perhaps the rebase itself needs to be performed with the find-renames option.. I'm investigating this...

    • git rebase -X find-renames=10% master -- nope
    • git rebase -X --find-renames=10% master -- nope
    • git rebase --find-renames=10% master -- nope
    • git rebase -X recursive --find-renames=10% master -- nope
    • git rebase --merge -X find-renames=10% master -- nope
    • git rebase --strategy-option="rename-threshold=10" master -- that's the ticket! it works!
  3. (Nope) Perhaps I need to look at the problem differently? Maybe I should start from master, and then do some kind of squash merge of typescript with the rename detection (rather than any kind of rebase?)

    • I don't need the typescript branch history, it will be squashed down to one fat commit for review anyways..
    • Update: It looks like Git stops working when I attempt this strategy.. When I'm standing in the root branch, I can run git merge -X find-renames=10 typescript -- and it fast-forwards to typescript.. (not quite what I was hoping for, I was hoping for a new commit that had renames rather than additions/deletions..)
      • When I'm standing in the master branch, and I run the exact same command, git tells me this: fatal: Unknown option for merge-recursive: -Xfind-renames=10...
      • Now, this bothers me, because I actually did not enter what it quoted (I did include the space), and the only thing that seems different to me, is which branch I'm currently standing in -- if the option is "Unknown", then why does it work when in a different branch?
      • Anyways, so that's creepy, and seems to make this approach a dead-end.
ChaseMoskal
  • 7,151
  • 5
  • 37
  • 50
  • what does bunk mean? – loneshark99 Mar 03 '17 at 00:04
  • The rebase code got smartened up in several iterations of Git 2.x, so you might need to mention your particular Git version. (Similarly for `git merge`, the `-X` rename detection threshold argument was not in 1.something as I recall, at least. Not that it should be *branch*-dependent.) – torek Mar 03 '17 at 00:32
  • @loneshark99 — I just meant that solution path wasn't viable. I reworded things for clarity's sake. – ChaseMoskal Mar 03 '17 at 01:09
  • 1
    In retrospect, I see that I tend to make posts like this, where I sort of openly explore possible solutions in the question area, and continually edit until I reach a conclusion, and then I post an answer. Perhaps in the future, I'll post a thinner, leaner, more focused question, and do my investigations and ramblings in a lengthy answer instead. Flip things around. Eh, maybe not. – ChaseMoskal Mar 03 '17 at 01:18

3 Answers3

41

You can rebase your branch while detecting renames like so:

git rebase --strategy-option="rename-threshold=10" master

Edit: Since Git 2.8.0, the term 'rename-threshold' has been deprecated in favor of 'find-renames'.

At the moment I'm using Git 2.7.4, and so I could only actually verify that the above command worked in my case — you may need to use the term 'find-renames' if the above command doesn't work in your case on your newer version of Git...

In this example,

  • the current branch is rebased onto master
  • the rename threshold of 10% is specified
  • changes in master to the original files will be placed in the new (renamed) files (as opposed to running merely git rebase master).
  • note how the syntax is different compared to similar rename-detection functionality found in the git diff, git log, and git merge commands

The git rebase documentation isn't very clear about this rename-detection aspect in particular.. I thought I read in another command somewhere, that the term "rename-threshold" was deprecated, though in this context find-renames did not seem to work as a synonym -- so if anybody knows about a better way to run this same command, please mention so :)

ChaseMoskal
  • 7,151
  • 5
  • 37
  • 50
  • `rename-threshold` *is* a deprecated synonym for `find-renames`, but it really does depend on your Git version. What does `git --version` say? – torek Mar 03 '17 at 00:48
  • 1
    **Git version `2.7.4`.** I went back and tried the following alternatives, none of which succeeded: (1) `git rebase --strategy-option="find-renames=10" master` (2) `git rebase --strategy-option="find-rename=10" master` (3) `git rebase --strategy-option="find-renames=10%" master` (4) `git rebase --strategy-option="find-rename=10%" master` ---- all of these fail with "unknown option". I assume the deprecation occurred after this version of Git I am using. – ChaseMoskal Mar 03 '17 at 01:06
  • @torek ^-- see comment above, I forgot to tag you there *(and apparently comments can't be edited after 10 minutes or something)* – ChaseMoskal Mar 03 '17 at 01:20
  • (It's 5 minutes, and it bites me at times as I am often multitasking on these things and get busy with something else.) The "deprecation" line was added to the documentation in commit 1b47ad160b55f50a7a98c180e18d80f0f8f17a67 which was first part of Git 2.8.0, so, right after yours (!)—2.7.4 was the last of the 2.7 releases. – torek Mar 03 '17 at 01:36
  • 7
    That's what source control tools are for. We find some interesting pattern, such as the word "deprecated" in the documentation, and then ask the version control system: "when did this word get added to this file?" (`git log -S Deprecated Documentation/merge-strategies.txt`) It spits out a commit ID and then we ask our version control system: "which tagged releases have that commit?" (`git tag --contains `) and voila. – torek Mar 03 '17 at 09:01
  • This is great, now I wish it would imply "find-copies" :-( And yes I did change the original a bit when testing so git didn't have to find-copies-harder. – MarcH Jan 27 '18 at 23:12
  • Unfortunately this doesn't work for deleted files. I.e. if I delete a file in my diff, and someone renames it in another, when I rebase onto that I just want the renamed file to be deleted, but it doesn't just do it - it gives me a merge conflict. :-/ – Timmmm Mar 22 '21 at 09:41
  • Hi chase. I bumped into a similar challenge. Could you devote some time checking this out. https://stackoverflow.com/questions/75094702/unified-updates-update-private-repo-with-updates-from-local-filesrefactored – Earthling Jan 13 '23 at 12:57
  • @ChaseMoskal. Since the JS files get always updated in your case, How should any entirely new file_name.js be handled with ? My use case is : Cloning an open source project (which updates regularly), i.e create a local copy of it and make modifications(including renaming files and folders), pushing the changes to my private repo but at the same time I require regular updates from the open source project reflected in renamed format. Do i have to rename the files & folder every time the project release an update and push it to my private repo ? – Earthling Jan 13 '23 at 14:01
2

In addition of the find-rename rebase/merge strategy, with Git 2.29 (Q4 2020), the commit labels used to explain each side of conflicted hunks placed by the sequencer machinery have been made more readable by humans.

See commit 7d056de (12 Aug 2020) by Elijah Newren (newren).
(Merged by Junio C Hamano -- gitster -- in commit 6cceea1, 19 Aug 2020)

sequencer: avoid garbled merge machinery messages due to commit labels

Signed-off-by: Elijah Newren
Reviewed-by: Taylor Blau
Acked-by: Johannes Schindelin

sequencer's get_message() exists to provide good labels on conflict hunks;
see commits

  • d68565402a ("revert: clarify label on conflict hunks", 2010-03-20, Git v1.7.1-rc0)
  • bf975d379d ("cherry-pick, revert: add a label for ancestor", 2010-03-20, Git v1.7.1-rc0)
  • 043a4492b3 ("sequencer: factor code out of revert builtin", 2012-01-11, Git v1.7.1-rc0).
    for background on this function.

These labels are of the form ... or parent of ...

These labels are then passed as branch names to the merge machinery.

However, these labels, as formatted, often also serve to confuse.

For example, if we have a rename involved in a content merge, then it results in text such as the following:

<<<<<<<< HEAD:foo.c
  int j;
========
  int counter;
>>>>>>>> b01dface... Removed unnecessary stuff:bar.c  

Or in various conflict messages, it can make it very difficult to read:

CONFLICT (rename/delete): foo.c deleted in b01dface... Removed
unnecessary stuff and renamed in HEAD.  Version HEAD of foo.c left
in tree.  

CONFLICT (file location): dir1/foo.c added in b01dface... Removed
unnecessary stuff inside a directory that was renamed in HEAD,
suggesting it should perhaps be moved to dir2/foo.c.  

Make a minor change to remove the ellipses and add parentheses around the commit summary; this makes all three examples much easier to read:

<<<<<<<< HEAD:foo.c
  int j;
========
  int counter;
>>>>>>>> b01dface (Removed unnecessary stuff):bar.c  

CONFLICT (rename/delete): foo.c deleted in b01dface (Removed
unnecessary stuff) and renamed in HEAD.  Version HEAD of foo.c left
in tree.  

CONFLICT (file location): dir1/foo.c added in b01dface (Removed
unnecessary stuff) inside a directory that was renamed in HEAD,
suggesting it should perhaps be moved to dir2/foo.c.
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
-1

It might help to first redo the file renaming in the master branch. It seems like a rather mechanical task that could be automated if required.

Then it will probably be rather easy to rebase the typescript branch on top of that, as there will already be a common ground. Some commits of the rebased typescript will end up (almost) empty, because their changes are already contained in master.

For this reason, you may want to consider merging typescript into master instead, in order to keep the original .js.ts transition history intact.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65