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).
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.
Meanwhile, changes to the original .js
files have happened on the master
branch.
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).
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.
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.
(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).
git rebase master
, and with the renames having been in place, the operation will go smoothly and place the edits into the correct files. (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
-- nopegit rebase -X --find-renames=10% master
-- nopegit rebase --find-renames=10% master
-- nopegit rebase -X recursive --find-renames=10% master
-- nopegit rebase --merge -X find-renames=10% master
-- nopegit rebase --strategy-option="rename-threshold=10" master
-- that's the ticket! it works!(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?)
typescript
branch history, it will be squashed down to one fat commit for review anyways..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..) master
branch, and I run the exact same command, git tells me this: fatal: Unknown option for merge-recursive: -Xfind-renames=10
...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 .
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..
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,
master
10%
is specifiedmaster
to the original files will be placed in the new (renamed) files (as opposed to running merely git rebase master
). git diff
, git log
, and git merge
commandsThe 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 :)
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 labelsSigned-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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With