Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git rebase with renamed files

Tags:

git

merge

rebase

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.
like image 290
ChaseMoskal Avatar asked Mar 02 '17 23:03

ChaseMoskal


People also ask

Does rebase merge changes?

Reading the official Git manual it states that rebase “reapplies commits on top of another base branch” , whereas merge “joins two or more development histories together” . In other words, the key difference between merge and rebase is that while merge preserves history as it happened, rebase rewrites it .

How do I use git interactive rebase?

You can run rebase interactively by adding the -i option to git rebase . You must indicate how far back you want to rewrite commits by telling the command which commit to rebase onto. Remember again that this is a rebasing command — every commit in the range HEAD~3..


2 Answers

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

like image 132
ChaseMoskal Avatar answered Sep 29 '22 22:09

ChaseMoskal


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. 
like image 42
VonC Avatar answered Sep 29 '22 21:09

VonC