What is a unified diff? How to read the ---, +++ and @@ lines

By TechCompare · Updated · How we check

Lines that start with a minus sign were removed, lines with a plus sign were added, and lines that start with a space are unchanged context. Each @@ line says where that part of the patch sits in the old and new file.

The example

2 changes: 2 lines removed, 3 added

Original: greet.pyChanged: greet.py
1Removeddef greet(name):1Addeddef greet(name, greeting="Hello"):
2Removed print("Hello, " + name)2Added print(f"{greeting}, {name}")
3 3
4 4
5def main():5def main():
6 greet("world")6 greet("world")
7Added greet("team", greeting="Hi")
7 8
8 9
9if __name__ == "__main__":10if __name__ == "__main__":
10 main()11 main()

A unified diff is the plain text format that diff -u, git diff and code review tools use to describe a change. It's also what Copy as unified diff gives you in this tool. Once you know what its header lines mean, you can read any patch, check it before you apply it, and catch a mistake in a code review.

Changes in the example
2
2 lines removed, 3 added
Lines
10 and 11
Original and changed text
Category
Diff format
Text diff guides

How this is calculated

Here's the patch for the example, a small Python file where greet gained a second argument and main gained a second call:

Unified diff
--- a/greet.py
+++ b/greet.py
@@ -1,9 +1,10 @@
-def greet(name):
-    print("Hello, " + name)
+def greet(name, greeting="Hello"):
+    print(f"{greeting}, {name}")
 
 
 def main():
     greet("world")
+    greet("team", greeting="Hi")
 
 
 if __name__ == "__main__":

The first two lines name the files. --- a/greet.py is the old version and +++ b/greet.py is the new one. The a/ and b/ prefixes are a convention from git. They let git apply and patch -p1 strip one folder level and find the real path.

Next comes a hunk, one section of the change. Its header is @@ -1,9 +1,10 @@. The part after the minus sign is about the old file: the hunk starts at line 1 and covers 9 lines. The part after the plus sign says the same for the new file, line 1 and 10 lines. When a count is 1, it's left out, so @@ -4 +4 @@ means one line on each side.

Inside the hunk, every line starts with one character:

  • A space marks context, a line that's the same in both files.
  • A minus sign marks a line that's only in the old file.
  • A plus sign marks a line that's only in the new file.

Context lines are there so a tool can find the right spot even if the file shifted a little since the patch was made. This tool keeps up to three lines of context, like diff -u and git diff. When two changes sit within six lines of each other, their context would overlap, so they share one hunk. That's why the example has 2 changes but a single hunk.

One special line can show up at the end: \ No newline at end of file. It means the line above it is the last line of a file that doesn't end with a line break. Some editors add that final line break and some don't, so it's a common cause of small, confusing diffs.

Verdict

Read a patch from the top: the file names, then each @@ header, then the minus and plus lines under it. The context lines tell you where you are. Before you apply a patch from someone else, check that the file names match your paths and that the removed lines match your copy, since that's what git apply checks too.

More Text Diff scenarios

Frequently asked questions

What's the difference between a unified diff and a context diff?
Both show changes with the lines around them. A context diff (diff -c) prints the old and new version of each section in two separate blocks. A unified diff (diff -u) merges them into one block with minus and plus signs, which is shorter and easier to read. Git and most code review tools use the unified format.
What do a/ and b/ mean in a diff?
They're placeholder folders for the old and new version of the file. Git adds them by default, and git apply or patch -p1 strips them off to find the real path. The rest of the path should match where the file sits in your project.
What does @@ -0,0 +1,3 @@ mean?
The old side has zero lines, so the file was empty or didn't exist, and the new side has three lines starting at line 1. You see it when a patch creates a file or fills an empty one.