Do Git file renaming by itself

Often it would seem that you can throw anything at Git and it will automatically know exactly what you mean. Sometimes, though, Git simply does not have a fighting chance! Git recognizes file content and not names meaning it uses an algorithm to determine when two files with different names are in fact revisions of the same.

This can become a problem if you do not pay attention. You might miss it and wrongfully mess up your history.

Setup

To illustrate this principle we use a git repository containing 1 file named app.js with a simple Fibonacci function declaration and invocation.

app.js

var a = fibonacci(9);

// Fibonacci
function fibonacci(n) {
	if (n <= 1) 
		return n;
		
	return fibonacci(n - 2) + fibonacci(n - 1);
}
&#91;/javascript&#93;

<h3>1. Simple renaming</h3>
We are going to take the following steps and examine what Git concludes.

<ol>
	<li>Rename app.js to main.js</li>
</ol>

At first, when you rename a file Git will see that a file has been deleted and another file created currently untracked.

<a href="http://whoknew.dk/wp-content/uploads/2013/07/renamed1.png"><img src="http://whoknew.dk/wp-content/uploads/2013/07/renamed1.png" alt="renamed1" class="aligncenter size-full wp-image-1084" /></a>

When you stage these changes, Git will see that the deleted file and the created file have exactly the same content and thus assert that this is the same file but with a new name.

<a href="http://whoknew.dk/wp-content/uploads/2013/04/renamed2.png"><img src="http://whoknew.dk/wp-content/uploads/2013/04/renamed2.png" alt="renamed2" class="aligncenter size-full wp-image-1085" /></a>

<em>This works like we would expect and is the desired result.</em>

<h3>2. Renaming + modification</h3>
Sometimes you want to do more than file renaming. You started out by renaming app.js -> main.js and quickly forgot about it. Then you decided you wanted only the fibonacci invocation to reside in main.js so you moved the function declaration to a newly created file named fibo.js. Now you want to commit everything. WAIT!

Let's first see what Git thinks we have done.

To us our steps boil down to the following.
<ol>
	<li>Rename app.js to main.js</li>
	<li>Create a new file named fibo.js</li>
	<li>Move declaration from app.js -> fibo.js</li>
</ol>

<strong>main.js</strong> (has been renamed from app.js; content has been moved)

var a = fibonacci(9);

fibo.js (new file; contains content moved from app.js)

// Fibonacci
function fibonacci(n) {
if (n <= 1) return n; return fibonacci(n - 2) + fibonacci(n - 1); } [/javascript] Firstly Git sees that app.js has been deleted and two new untracked files main.js & fibo.js now exist. renamed2-1

But when we stage the files, this time, Git sees our steps quite differently.

renamed2-2

To Git the following steps have taken place:

  1. Rename app.js -> fibo.js
  2. Create a new file main.js
  3. Move invocation from app.js to main.js

Quite different than what we think we did! The content of the files are exactly what we want them to be, but the history “revisions” of our files will be wrong if we make this commit.

Solution

Git encourages small commits.

I have chosen to leverage this behavior by doing file renaming commits on their own. In practice this means that for every commit I am either renaming file(s) or modifying, deleting, adding a file(s).

By doing rename commits on their own, I am guaranteed the content will be 100% identical making this a no-brainer for Git to preserve the rename history I want.

Speak Your Mind

*