0

In git, how to remove file deletions and renames, starting from a certain commit in a branch, while maintaining the history of changes for each file (both deleted and renamed)?

My use case is that I took a branch and started from there a new branch where I removed some files and renamed some others. I then modified the remaining (and often renamed) files (and also added some files). Now I want to revert back to the original names and recover the deleted files (that still exist at the common base), while keeping the history of everything. My final goal is to be able to merge back in the original branch (so without the renames and file deletions that I introduced).

Can this be done? I am wondering how easy this is, especially since the renamed files were modified (so naively removing the renames would sever the history link between the original files and the modifications, which I instead want to keep)…

PS: The changes since the branch diverged are extensive (at least on the "cleaned" branch).

Community
  • 1
  • 1
Eric O. Lebigot
  • 91,433
  • 48
  • 218
  • 260
  • Git doesn't track the history of filenames. It's a content tracker; it calculates filename changes from deltas at runtime. – Todd A. Jacobs Jan 31 '16 at 20:38
  • "*since the renamed files were modified so naively removing the renames would sever the history link between the original files and the modifications*" This is not necessarily true. Git can calculate renames even with file modifications. Also if you rename the files back to their original names with a normal commit your branch should merge fine. – Schwern Jan 31 '16 at 21:12

2 Answers2

2

You don't have to rewrite history to make the merge work. You can revert the renames and reintroduce the deleted files, then commit and merge. Use git mv to rename the files back. Deleted files can be recovered using git checkout <rev-which-deleted-the-file>^ -- <filename>. See this answer for details.

Deleted and renamed files can be found using git-log's --diff-filter.

Git doesn't store renames, it calculates them on the fly with some heuristics to deal with small edits. git-log, git-diff, and git-blame all have options like -C and -M to control how hard Git tries to find copied and renamed (moved) files. So the continuity of history is not necessarily lost by the renames in the branch.


If you really want to rewrite your history, first find all the changes which delete files using git log --diff-filter=D master..branch. Then you can use git rebase -i to alter those commits and restore the deleted file.

Rewrites are a bit trickier because you'll have to undo the rename and then rename the file in each subsequent commit. This is a job for git-filter-branch which can apply the same change to a series of commits.

git filter-branch --tree-filter 'if [ -f new ]; mv new old; fi' master..branch

The --tree-filter runs the shell command on each commit and then alters the commit by adding and deleting files as necessary.

As before, to find your renames use git log --diff-filter=R master..branch.

Community
  • 1
  • 1
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • Thank you. I used the first method (no history rewrite): `git mv` and `git checkout …` for deleted files. Then I merged each branch into the other (and checked the updates). Somehow the history for a file that got lots of changes disappeared from `git log`, though. It does appear in the pull request, however. Is there a way of getting hold of such a history? – Eric O. Lebigot Feb 06 '16 at 17:11
  • @EOL I'm not sure what you mean. This would be better as a new question. – Schwern Feb 06 '16 at 20:04
  • For completeness: I renamed a file from `master`: `git mv old_name.txt new_name.txt` so that it matches the name from the other branch (`production`). After merging `production`, I didn't see the full change history of `new_name.txt` when I did `git log new_name.txt`. Now I realize that one must do `git log --follow new_name.txt` in order to follow renames and list changes. Thank you for your feedback. :) – Eric O. Lebigot Feb 07 '16 at 08:33
0

You could try doing a git rebase -i for the offending branch, at the branching point. For security's sake, I'd first create a new branch at the tip of the branch you want to mangle, in case (hah! Murphy's law is relentless) something goes wrong.

If the changes are very extensive, there are ways to automate this, but that is more iffy. See the Pro git book for details.

vonbrand
  • 11,412
  • 8
  • 32
  • 52
  • I believe the problem with a `git rebase -i` is if you undo a rename in one commit, any commits which changed the renamed file will either cause a conflict or cause a copy of the file to appear. – Schwern Jan 31 '16 at 21:36
  • @Schwern, the "don't delete a file" is what causes the conflict if some later commit creates it anew. You'll have to decide what you want, no way around that, – vonbrand Jan 31 '16 at 21:48
  • Thank you. The changes _are_ unfortunately extensive… – Eric O. Lebigot Feb 06 '16 at 15:42