Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read the output from git diff?

Tags:

git

git-diff

diff

The man page for git-diff is rather long, and explains many cases which don't seem to be necessary for a beginner. For example:

git diff origin/master 
like image 622
poseid Avatar asked Mar 27 '10 13:03

poseid


People also ask

What do the numbers in git diff mean?

- means "old", as we usually invoke it as diff -u old new . +1,4 means that this piece of the second file starts at line 1 and shows a total of 4 lines. Therefore it shows lines 1 to 4. + means "new". We only have 4 lines instead of 6 because 2 lines were removed!

What is A and B in git diff?

In most cases, Git picks A and B in such a way that you can think of A/- as "old" content and B/+ as "new" content. Let's look at our example: Change #1 contains two lines prepended with a "+". Since no counterpart in A existed for these lines (no lines with "-"), this means that these lines were added.

What does ++ mean in git diff?

When viewing a combined diff, if the two files you're comparing have a line that's different from what they were merged into, you will see the ++ to represent: one line that was added does not appear in either file1 or file2.


2 Answers

Lets take a look at example advanced diff from git history (in commit 1088261f in git.git repository):

diff --git a/builtin-http-fetch.c b/http-fetch.c similarity index 95% rename from builtin-http-fetch.c rename to http-fetch.c index f3e63d7..e8f44ba 100644 --- a/builtin-http-fetch.c +++ b/http-fetch.c @@ -1,8 +1,9 @@  #include "cache.h"  #include "walker.h"   -int cmd_http_fetch(int argc, const char **argv, const char *prefix) +int main(int argc, const char **argv)  { +       const char *prefix;         struct walker *walker;         int commits_on_stdin = 0;         int commits; @@ -18,6 +19,8 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)         int get_verbosely = 0;         int get_recover = 0;   +       prefix = setup_git_directory(); +         git_config(git_default_config, NULL);           while (arg < argc && argv[arg][0] == '-') { 

Lets analyze this patch line by line.

  • The first line

    diff --git a/builtin-http-fetch.c b/http-fetch.c
    is a "git diff" header in the form diff --git a/file1 b/file2. The a/ and b/ filenames are the same unless rename/copy is involved (like in our case). The --git is to mean that diff is in the "git" diff format.
  • Next are one or more extended header lines. The first three

    similarity index 95% rename from builtin-http-fetch.c rename to http-fetch.c
    tell us that the file was renamed from builtin-http-fetch.c to http-fetch.c and that those two files are 95% identical (which was used to detect this rename).

    The last line in extended diff header, which is
    index f3e63d7..e8f44ba 100644
    tell us about mode of given file (100644 means that it is ordinary file and not e.g. symlink, and that it doesn't have executable permission bit), and about shortened hash of preimage (the version of file before given change) and postimage (the version of file after change). This line is used by git am --3way to try to do a 3-way merge if patch cannot be applied itself.

  • Next is two-line unified diff header

    --- a/builtin-http-fetch.c +++ b/http-fetch.c
    Compared to diff -U result it doesn't have from-file-modification-time nor to-file-modification-time after source (preimage) and destination (postimage) file names. If file was created the source is /dev/null; if file was deleted, the target is /dev/null.
    If you set diff.mnemonicPrefix configuration variable to true, in place of a/ and b/ prefixes in this two-line header you can have instead c/, i/, w/ and o/ as prefixes, respectively to what you compare; see git-config(1)
  • Next come one or more hunks of differences; each hunk shows one area where the files differ. Unified format hunks starts with line like

    @@ -1,8 +1,9 @@
    or
    @@ -18,6 +19,8 @@ int cmd_http_fetch(int argc, const char **argv, ...
    It is in the format @@ from-file-range to-file-range @@ [header]. The from-file-range is in the form -<start line>,<number of lines>, and to-file-range is +<start line>,<number of lines>. Both start-line and number-of-lines refer to position and length of hunk in preimage and postimage, respectively. If number-of-lines not shown it means that it is 1.

The optional header shows the C function where each change occurs, if it is a C file (like -p option in GNU diff), or the equivalent, if any, for other types of files.

  • Next comes the description of where files differ. The lines common to both files begin with a space character. The lines that actually differ between the two files have one of the following indicator characters in the left print column:

  • '+' -- A line was added here to the first file.

  • '-' -- A line was removed here from the first file.


So, for example, first chunk

     #include "cache.h"      #include "walker.h"           -int cmd_http_fetch(int argc, const char **argv, const char *prefix)     +int main(int argc, const char **argv)      {     +       const char *prefix;             struct walker *walker;             int commits_on_stdin = 0;             int commits; 

means that cmd_http_fetch was replaced by main, and that const char *prefix; line was added.

In other words, before the change, the appropriate fragment of then 'builtin-http-fetch.c' file looked like this:

    #include "cache.h"     #include "walker.h"          int cmd_http_fetch(int argc, const char **argv, const char *prefix)     {            struct walker *walker;            int commits_on_stdin = 0;            int commits; 

After the change this fragment of now 'http-fetch.c' file looks like this instead:

    #include "cache.h"     #include "walker.h"           int main(int argc, const char **argv)     {            const char *prefix;            struct walker *walker;            int commits_on_stdin = 0;            int commits; 
  • There might be
    \ No newline at end of file
    line present (it is not in example diff).

As Donal Fellows said it is best to practice reading diffs on real-life examples, where you know what you have changed.

References:

  • git-diff(1) manpage, section "Generating patches with -p"
  • (diff.info)Detailed Unified node, "Detailed Description of Unified Format".
like image 101
Jakub Narębski Avatar answered Oct 10 '22 23:10

Jakub Narębski


@@ -1,2 +3,4 @@ part of the diff

This part took me a while to understand, so I've created a minimal example.

The format is basically the same the diff -u unified diff.

For instance:

diff -u <(seq 16) <(seq 16 | grep -Ev '^(2|3|14|15)$') 

Here we removed lines 2, 3, 14 and 15. Output:

@@ -1,6 +1,4 @@  1 -2 -3  4  5  6 @@ -11,6 +9,4 @@  11  12  13 -14 -15  16 

@@ -1,6 +1,4 @@ means:

  • -1,6 means that this piece of the first file starts at line 1 and shows a total of 6 lines. Therefore it shows lines 1 to 6.

    1 2 3 4 5 6 

    - means "old", as we usually invoke it as diff -u old new.

  • +1,4 means that this piece of the second file starts at line 1 and shows a total of 4 lines. Therefore it shows lines 1 to 4.

    + means "new".

    We only have 4 lines instead of 6 because 2 lines were removed! The new hunk is just:

    1 4 5 6 

@@ -11,6 +9,4 @@ for the second hunk is analogous:

  • on the old file, we have 6 lines, starting at line 11 of the old file:

    11 12 13 14 15 16 
  • on the new file, we have 4 lines, starting at line 9 of the new file:

    11 12 13 16 

    Note that line 11 is the 9th line of the new file because we have already removed 2 lines on the previous hunk: 2 and 3.

Hunk header

Depending on your git version and configuration, you can also get a code line next to the @@ line, e.g. the func1() { in:

@@ -4,7 +4,6 @@ func1() { 

This can also be obtained with the -p flag of plain diff.

Example: old file:

func1() {     1;     2;     3;     4;     5;     6;     7;     8;     9; } 

If we remove line 6, the diff shows:

@@ -4,7 +4,6 @@ func1() {      3;      4;      5; -    6;      7;      8;      9; 

Note that this is not the correct line for func1: it skipped lines 1 and 2.

This awesome feature often tells exactly to which function or class each hunk belongs, which is very useful to interpret the diff.

How the algorithm to choose the header works exactly is discussed at: Where does the excerpt in the git diff hunk header come from?