Skip to main content

Renaming Files in Git

Renaming Files

I recently had to rename a lot of files in brf-mode for submission into MELPA. These files have 10+ years of version control history I wanted to keep. In the process I realised retaining all that history in Git isn't as simple as I thought 😀

I thought it was as simple as using git mv rather than filesystem mv and Git would know everything had been renamed. It turns out I was wrong and I should have known that from my knowledge of how Git works 😖

In reality, Git works by storing snapshots rather than file or directory metadata and git mv is just a convenience shortcut for typing:

$ mv <old name> <new name>
$ git rm <old name>
$ git add <new name>

That's all there is to it!

Now the "magic" happens when you git log or git blame a file and Git works out the file was renamed by diffing the contents.  If files have the same contents (within a certain threshold %) it thinks a rename happened. See here in the Git SCM Book for the gory details.

A consequence of this is if you rename files and then make a whole load of changes to the contents of those files, Git will not recognise this as a rename.

Therefore it is important to make the rename in one commit and any changes to the contents in another commit.

Of course Git being Git, there is a further wrinkle. While git blame will follow renames and give you a consistent annotation by default, git log does not.  git log will only show history to the last rename unless you supply the --follow argument. You really couldn't make this stuff up...

You can work around this by setting log.follow in .gitconfig.
However when I'm doing command line Git I like to have full control of what I'm doing, so instead of doing that I just tell Emacs VC to follow by default:

(setq vc-git-print-log-follow t)

Moving Files

Unfortunately moving files between directories creates further problems - history is only partially retained. git log --follow allows you to see the history of the moved files, but git diffbisect etc can't find the revisions. You can still diff commits from the top-level log, just not for an individual file. Hopefully future Git revisions will fix this - until then the only way around it I know about is to destructively edit the commit history with filter-branch etc. That will have to be the subject of a future post 😀

Comments

Popular posts from this blog

Merging Git Repositories

No project of significant size that I've ever seen has retained its initial structure. Restructuring projects is a fact of life, but unfortunately Git doesn't make it easy. Fundamentally this stems from the way Git works, treating changes as a succession of snapshots and not storing any other metadata. Of course this is part of what makes Git fast and efficient, but at the expense of making some common operations more difficult for users. Git really is a perfect 21st century illustration of the classic  "Worse Is Better"  paradigm of successful software 😀 Previously I discussed how to split a Git project apart into separate repositories . Now I'm going to discuss how to do the opposite and merge separate repositories into one. On the face of it, this would seem a simpler task as Git has powerful support for merging... Let's take the opposite example to my splitting apart article - say you have a main Git repo (ProjA) and a second repo (ProjB) in...

East Devon Continued

Some iPhone pictures: Seaton Bay from Beer Hill at Sunset Gulls on Beer Beach We also had a pair of Pheasants in the garden, which was a bit of a surprise. There are always plenty of rabbits and wild birds, but this is the first time I've seen game birds. Here is the male, sitting on the garden wall, wondering what I'm up to: Male Pheasant I also spotted these attractive white Cyclamen in the garden: Cyclamen

Setting Environment Variables and the PATH on MacOS

Time Was setting environment variables and the PATH on a Mac running OSX was just like any other *IX.  However with successive OS releases Apple have changed how this works (more than once) and generally made it more difficult 😢  This article discusses how I go about setting environment variables on Mojave and Catalina. Why does this matter? MacOS doesn't add  /usr/local/bin to the PATH by default, which is unfortunate as most *IX-style programs you build yourself will be installed in there. If you only ever launch stuff from Terminal, all you have to do is set environment variables and the path from Shell startup files in the time immemorial fashion. However, this doesn't help with native Mac Apps like Emacs, which aren't launched from a shell and where you may still want to access custom environment variables and programs in /usr/local/bin . Setting the PATH In the past you could add to the path via /etc/paths (or paths.d ), however this no longer works...