Newlines and Version Control
Imagine you have just finished implementing a new feature in a project and before committing the work to the repo, want to see a summary of the changes. Running git diff
reveals the expected changes, but you also see the strange message:
No newline at end of file.
OK, so there is no newline at end of file… What is the big deal?
Aside: I would recommend reading The Great Newline Schism by Jeff Atwood to clear up any misunderstanding about carriage return and line feed before continuing…
For the purpose of clarity, we'll be using \n
for newlines characters - it is a common abstraction in many programming languages for newlines.
No newline at end of file
The message indicates that you do not have a newline at the end of the file. You may be asking:
So what is the significance?
Let's do a little experimenting on the command line to see how git
handles things:
Newline Experiment
Part 1 - Adding content without a newline
- We will start by creating a new directory for our experiment.
In this new directory we will create an empty
git
repository by typinggit init
.Now we will create an empty file called
foo.txt
usingtouch foo.txt
.Staging the empty file in the
git
repository will allow us to compare upcoming changes against it. Stage the file usinggit add foo.txt
.Add "Hello" to the file -
printf 'Hello' >> foo.txt
. We are explicitly adding the text without a new line at the end.echo
adds a newline by default.printf
does not add a newline by default.Now that we have added some content we can see what the diff generates. Running
git diff
will output the following:
diff --git a/foo.txt b/foo.txt
index e69de29..5ab2f8a 100644
--- a/foo.txt
+++ b/foo.txt
@@ -0,0 +1 @@
+Hello
\ No newline at end of file
Notice the \ No newline at end of file
message below the "Hello".
The diff's output is rendered with a newline at the end of the "Hello" to make it easier to consume programmatically.
The No newline at end of file
message is appended to indicate that the newline is actually missing from that file.
A newline character is always at the end of the line.
Newlines are a control character and therefore don't have a visible character. Certain editors may display a character to show that there is a newline, others will show the next line number and some will not show anything.
OK, let's get back to this experiment
Part 2 - Adding content after a missing newline
- Let's stage the file again so we can see the effect of adding more lines -
git add foo.txt
This time we will add "World" on a newline -
printf '\nWorld\n' >> foo.txt
. Note how we have added\n
before and after "World".What is the diff now? -
git diff
diff --git a/foo.txt b/foo.txt
index 5ab2f8a..f9264f7 100644
--- a/foo.txt
+++ b/foo.txt
@@ -1 +1,2 @@
-Hello
\ No newline at end of file
+Hello
+World
The diff reveals the following:
We have explicitly added a newline character to the end of "Hello". This newline character is residing at the end of the first line. The first line shows up in the diff as modified although there is no visible change. The "No newline at end of file" from the previous set of changes is gone.
We also added an explicit newline character after "World". There is no "No newline at end of file" after the second line.
What about adding another line?
OK, lets see what happens when we add another line.
Part 3 - Adding content after a newline
- We will stage the current changes so we can get a clean diff -
git add foo.txt
Let us add "!" on the third line -
echo '!' >> foo.txt
. We use echo here to get the benefit of appending a newline character by default.Now we can have a look at the diff
diff --git a/foo.txt b/foo.txt
index f9264f7..a9a7f7a 100644
--- a/foo.txt
+++ b/foo.txt
@@ -1,2 +1,3 @@
Hello
World
+!
The diff above shows that the second line was not modified. We have successfully appended to the file without other modifications. We did not need to append a newline to the second line to create the third line.
That is a much cleaner diff. The intent is clearly conveyed.
OK, there is just one thing missing…
Part 4 - Preparing a patch
- Stage the most recent changes again -
git add foo.txt
- We will now apply a patch using an existing diff. Create a new file called
add-beautiful.diff
and add following snippet to it.
diff --git a/foo.txt b/foo.txt
index a9a7f7a..9a4c951 100644
--- a/foo.txt
+++ b/foo.txt
@@ -1,3 +1,4 @@
Hello
+Beautiful
World
!
Note: We can generate our own patch by using the output generated by git diff
and send the output to a file - git diff >> <filename>
.
Part 5 - Apply a patch
- Now we can apply the patch which will update our
foo.txt
. To do this we usegit apply add-beautiful.diff
- If we now do diff -
git diff
diff --git a/foo.txt b/foo.txt
index a9a7f7a..9a4c951 100644
--- a/foo.txt
+++ b/foo.txt
@@ -1,3 +1,4 @@
Hello
+Beautiful
World
!
Notice how the output is the same as was the patch we applied.
This is not a coincidence. Remember that a diff is a format that can easily be consumed programmatically.
My take on it
"No newline at end of file", like other whitespace changes, add unnecessary noise to commits and diffs. It should be avoided and is fairly easy to avoid.
Managing different configurations across different projects can be tedious. The solution that I've found most helpful is adding a .editorconfig
file to a project and installing the EditorConfig plugins corresponding to my editors.
Conclusion
Commands that we have covered during this post:
git init
creates an empty git repository.touch <filename>
creates a blank file with the given name.git add <filename>
will stage the file. This is normally used as a way to add certain changes to particular commits. We have misused it to generate diffs by comparing the unstaged changes against the staged changes.printf
does not include a newline character at the end of the content by default.echo
does include a newline character at the end of the content by default.git diff
compares unstaged changes against the staged files (If there are no staged files, it defaults to the previous commit).git apply
allowed us to apply a patch on our files. It is important to note that the file extension.diff
is only a convention and does not impactgit
's ability to use it.
So hopefully you now have a better understanding of why git
keeps pointing out the "No newline at end of file".
Now go out and enjoy the beautiful world!