I was used to leaving a visible blank line at the end of Markdown files. It was more of a visual convention: the cursor could sit below the last paragraph, and the document looked complete.
Then I noticed that Neovim considered a file correctly terminated even when there was no separate line for the cursor below the text. The final newline was present, but the editor did not display it as another line.
That prompted me to compare what text, text\n, and text\n\n actually look like at the byte level. The difference is a single invisible byte that rarely affects a program, but shows up clearly in git diff and even in the output of git blame.
A newline is not a blank line
Consider three files that differ only at the end:
text
text\n
text\n\n
The first file contains nothing after text. Its last line is incomplete.
The second file contains one LF byte after the text, usually written as \n. It terminates the current line but does not create another blank line after it.
The third file contains two consecutive LF bytes. The first terminates the line containing the text, and the second terminates another, empty line.
The difference is easier to see as bytes rather than in an editor:
printf 'text' | od -An -t x1
printf 'text\n' | od -An -t x1
printf 'text\n\n' | od -An -t x1
The output is:
74 65 78 74
74 65 78 74 0a
74 65 78 74 0a 0a
The final 0a value represents LF.
In POSIX terminology, a line ends with a newline character. A fragment at the end of a file without a terminating newline is called an incomplete line.
A normal text file therefore ends like this:
text\n
Not like this:
text\n\n
The second form is valid too, but it contains an actual blank line.
Why Neovim does not display the newline as a separate line
Neovim treats a buffer as a collection of lines. The final <EOL> is not displayed as another editable line. Instead, the editor separately tracks whether the last line of the file has a line ending.
This behaviour is controlled by the endofline and fixendofline options. fixendofline is enabled by default, so Neovim restores a missing <EOL> when it saves an ordinary text file.
A file containing:
Last line\n
may therefore look like this in Neovim:
Last line
~
~
The cursor cannot be placed between the last line and the first ~ because the file contains no separate blank line. It only contains a character that terminates the line with text.
To create an editable blank line, the file needs another newline:
Last line\n\n
That is already different file content.
Why Git prints No newline at end of file
Create a file without a final newline:
printf 'alpha\nbeta' > example.txt
Commit it, then add another line and a proper final newline:
printf 'alpha\nbeta\ngamma\n' > example.txt
git diff
Git produces this diff:
alpha
-beta
\ No newline at end of file
+beta
+gamma
At first, it looks as if Git unnecessarily removed and re-added beta, even though its visible text did not change.
The old and new byte sequences are different, however:
Before: beta
After: beta\n
The old file ended immediately after beta. The new file contains an additional 0a byte after it.
Git produces line-oriented diffs, so it cannot represent this as a simple addition of gamma. It first has to mark the old final line as incomplete and then show the new, terminated beta line.
The marker:
\ No newline at end of file
does not mean Git refused to process the file. The file can still be staged and committed. The marker is needed so that the diff accurately represents the difference between the two byte sequences.
One byte can change git blame
This has more than a visual effect on the diff.
Suppose the first author creates a file without a final newline:
alpha
beta
A second author later adds gamma and a final newline:
alpha
beta
gamma
Before the change, both lines belong to the first author:
First Author alpha
First Author beta
After the change, a regular git blame reports:
First Author alpha
Second Author beta
Second Author gamma
The visible text of beta remained unchanged, but its byte representation did not. Git therefore treats the replacement of incomplete beta with terminated beta\n as a modification made by the second author.
This is rarely a serious problem on its own. It still creates unnecessary noise when investigating file history, especially when a formatter adds missing newlines to many existing files at once.
Does Git require a final newline
Git itself does not forbid files without a final newline. It can store and commit them, and the special marker only appears when a diff is generated.
Since Git 2.53, however, a missing final newline can be enabled as a whitespace error. The corresponding error class is called incomplete-line, and it is disabled by default.
It can be enabled for a repository with:
git config \
core.whitespace \
trailing-space,space-before-tab,incomplete-line
The following command can then detect changed files with an incomplete final line:
git diff --check
This is useful as a repository policy. It does not change the way Git stores file contents.
A practical rule
For regular source code, configuration and Markdown files, a sensible rule is:
Last line\n
The file should contain one final newline, but it does not need an additional blank line after its last piece of content.
Editors can enforce this through .editorconfig:
[*]
end_of_line = lf
insert_final_newline = true
The insert_final_newline property means that the last line must be terminated. It does not require the editor to create another blank line.
For Markdown files, the same problem is covered by the markdownlint rule MD047, also known as single-trailing-newline.
Blank lines added at the end of a file are a separate issue. Git classifies them as blank-at-eof whitespace errors, and that check is enabled by default.
For example, this change:
Last paragraph.
+
will be reported by:
git diff --check
as a new blank line at the end of the file.
What about Markdown
For years, I left a visible blank line after the final paragraph of my Markdown files. It looked tidy in Neovim: I could place the cursor below the text, and the document felt visually complete.
Technically, such a file ends with two newlines rather than one:
Last paragraph.\n\n
Most Markdown renderers produce the same output, so I never noticed a practical problem. But this is not a Markdown requirement or a generally accepted way to mark a properly terminated file.
I had treated the visible blank line as part of a correctly finished file. It turned out to be an editor habit, not a technical requirement. A final newline terminates the last line correctly without creating another blank one.
The broader reminder is useful: even familiar tool behaviour is sometimes worth checking against the file’s actual contents.
