2

SO Question in Context : Find out changed line numbers from git diff

Based on the question above, I need to print the diff for modified files between two revisions in git in this format :

/path/to/file
startline1-startline2
startline3-startline4

/path/to/another/file
startline5-startline6
...

This is my attempt to filter git diff's output.

git diff --unified=0 --diff-filter=M HEAD~1 HEAD | \
grep -v -e '^[+-]' -e '^index' | \
sed 's/diff --git a.* b\//\//g; s/.*@@\(.*\)@@.*/\1/g; s/^ -//g; s/,[0-9]*//g; s/\(^[0-9]*\) +/\1-/g;'

I am not sure if the above command satisfies all cases which git diff might show in the output.

Can the existing solution be improved or is there any better way to do this using some other utility like awk or in git itself?


Based on the answer given by @muru, here's a comparison :

# Using git diff [expected output w.r.t line numbers and not formatting]

$ git diff --unified=0 --diff-filter=M HEAD~3 HEAD | grep -v -e '^[+-]' -e '^index'
diff --git a/lib/get-config.js b/lib/get-config.js
@@ -89,0 +90,5 @@ module.exports = {
diff --git a/lib/pull.js b/lib/pull.js
@@ -62 +62 @@ module.exports = class Pull {
@@ -83 +83 @@ module.exports = class Pull {
diff --git a/lib/router.js b/lib/router.js
@@ -3,0 +4 @@ const yaml = require('js-yaml')
@@ -125,0 +127 @@ module.exports = app => {
@@ -136,0 +139 @@ module.exports = app => {
# Using external diff as suggested by @muru
# [expected output with formatting but line numbers don't match with git diff's output]

$ git -c diff.external='./foo.sh' diff --diff-filter=M 'HEAD~3' HEAD
/tmp/AAAA_get-config.js
90-89

/tmp/BBBB_pull.js
62
83

/tmp/CCCC_router.js
4-3
126-125
137-136

The line numbers are different for the file router.js.

Repository used for comparison.


I would like to clarify what format is being expected in the output.

git diff generates output in unified diff format. The format is something like this :

# Assumes --unified=0 i.e no context lines
diff --git a/path/to/file b/path/to/file
index x..z mode
--- a/path/to/file
+++ b/path/to/file
@@ -l1,s +l2,s @@ optional section heading
- ...
- ...
+ ...
+ ...
diff --git a/path/to/file1 b/path/to/file1
index y..z mode
--- a/path/to/file1
+++ b/path/to/file2
@@ -l3,s +l4,s @@ optional section heading
- ...
- ...
+ ...
+ ...
.
.
.

The expected output should contain diff for only modified files (using --diff-filer=M) and should be in this format :

/path/to/file
l1-l2

/path/to/file1
l3-l4
l5-l6
.
.
.

Here l1,l2... are line numbers as mentioned in the unified diff format (@@ -l1,s +l2,s @@).


@muru's answer almost solves the problem but the line numbers obtained using this changed group format '%df%(f=l?:-%dl)\n' don't match the line numbers in unified diff format.

Is there a way to represent the line numbers(in unified diff format) using options such as changed-group-format ?


Here's a MVCE using bash git repository

Using git diff,

$ git diff --unified=0 --diff-filter=M 9e49d ad1b3 | grep -v -e '^[+-]' -e '^index'
diff --git a/execute_cmd.c b/execute_cmd.c
@@ -567 +567 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
@@ -753,2 +753 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
@@ -759,2 +758 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
@@ -1104 +1102 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
diff --git a/patchlevel.h b/patchlevel.h
@@ -28 +28 @@
diff --git a/subst.c b/subst.c
@@ -5339 +5339 @@ clear_fifo_list ()
@@ -5345 +5345 @@ copy_fifo_list (sizep)
@@ -5411,7 +5411,2 @@ unlink_fifo_list ()
@@ -5433 +5428 @@ close_new_fifos (list, lsize)
@@ -5437 +5431,0 @@ close_new_fifos (list, lsize)
@@ -5445,2 +5439,2 @@ close_new_fifos (list, lsize)
@@ -5568 +5562 @@ clear_fifo_list ()
@@ -5572 +5566 @@ copy_fifo_list (sizep)
@@ -5578 +5572 @@ copy_fifo_list (sizep)
@@ -5583 +5577 @@ copy_fifo_list (sizep)
@@ -5656 +5650 @@ close_new_fifos (list, lsize)
@@ -5660 +5653,0 @@ close_new_fifos (list, lsize)
@@ -5668,2 +5661,2 @@ close_new_fifos (list, lsize)
diff --git a/subst.h b/subst.h
@@ -276,2 +276,3 @@ extern void unlink_fifo __P((int));

Expected output

/execute_cmd.c
567-567 
753-753 
759-758 
1104-1102 

/patchlevel.h
28-28 

/subst.c
5339-5339 
5345-5345 
5411-5411 
5433-5428 
5437-5431 
5445-5439 
5568-5562 
5572-5566 
5578-5572 
5583-5577 
5656-5650 
5660-5653 
5668-5661 

/subst.h
276-276 

Output Based on @muru's answer

# Line numbers which don't match the expected output is indicated by "#"
$ git -c diff.external='../foo.sh' diff --diff-filter=M 9e49d ad1b3
/tmp/2wJaWf_execute_cmd.c
567
753-754 #
759-760 #
1104 #

/tmp/7j19Ob_patchlevel.h
28

/tmp/fQDY8b_subst.c
5339
5345
5411-5417 #
5433 #
5437 #
5445-5446 #
.
.
.
3
  • 2
    Can you put the relevant context in this question? Include example input and what output you is desired, and what you're having problems with.
    – muru
    Commented Apr 26, 2020 at 7:10
  • @muru I have updated the details Commented Apr 26, 2020 at 13:02
  • @EdMorton Any idea how we can represent the unified diff format using options such as changed-group-format so that this @@ -l1,s +l2,s @@ can be represented as this l1-l2? Commented May 8, 2020 at 2:31

1 Answer 1

4

You can get something similar if you use GNU diff, and make git diff call it:

foo.sh

#! /bin/bash
# See https://stackoverflow.com/a/255212/2072269 for details on parameters
echo "$2" # print filename
diff --changed-group-format $'%df%(f=l?:-%dl)\n' --line-format='' "$2" "$5"
echo
exit 0

And (example using the bash git repo):

$ export GIT_EXTERNAL_DIFF="$PWD/foo.sh"
$ git diff HEAD 'HEAD^^'
execute_cmd.c
567
753-754
759-760
1104

patchlevel.h
28

subst.c
5339
5345
5411-5417
5433
5437
5445-5446
5568
5572
5578
5583
5656
5660
5668-5669

subst.h
276-277

The GNU diff options:

--changed-group-format=format
These line groups are hunks containing lines from both files.

%df and %dl are the first and last line numbers of the change group in the old file (in decimal). %(f=l?:-%dl) is a ternary expression comparing f and l and omitting the last line if its the same as the first.

We don't need the actual lines, so --line-format is set to empty.


Based on the MCVE, it seems:

  1. You don't need the %(f=l?:-%dl) ternary check (which omits the second number if its the same as the first). We just need to print the second number.
  2. The second number should be %F for most cases - the first line of the change from the new file. Except where lines are just deleted from the old file, so the number of lines in the new file are 0. In that case, the number printed is %E - the line number just before the group in the new file. The ternary then becomes %(N=0?%dE:%dF).
  3. I should use $1 for the filename instead of $2.

So, with those changes:

#! /bin/bash
# See https://stackoverflow.com/a/255212/2072269 for details on parameters
echo "$1" # print filename
diff --changed-group-format $'%df-%(N=0?%dE:%dF)\n' --line-format='' "$2" "$5"
echo
exit 0

which gives the expected output:

$ PAGER=cat git diff --unified=0 --diff-filter=M 9e49d ad1b3
execute_cmd.c
567-567
753-753
759-758
1104-1102

patchlevel.h
28-28

subst.c
5339-5339
5345-5345
5411-5411
5433-5428
5437-5431
5445-5439
5568-5562
5572-5566
5578-5572
5583-5577
5656-5650
5660-5653
5668-5661

subst.h
276-276

13
  • Thanks for the answer, the line numbers sometimes differ from the git diff's output, I am guessing its got something to do with various options of 'Fn' here as some options under it mentions e+1 , l-f+1, what line number format git diff might be using ? (if its using the default, then what is the default format?) Commented Apr 27, 2020 at 5:35
  • @SaurabhPBhandari It's probably the same as GNU diff's unified format (gnu.org/software/diffutils/manual/html_node/…)
    – muru
    Commented Apr 27, 2020 at 6:21
  • The difference of line numbers between the git diff and git diff with external diff as foo.sh : i.sstatic.net/3jeQH.png , Is the unified format being used in foo.sh ? Commented Apr 27, 2020 at 6:41
  • Any idea why the line numbers are different ? Commented Apr 30, 2020 at 15:11
  • @SaurabhPBhandari maybe because your example is mostly of deleted lines. Please don't post screenshots of text. If you have new information to add, edit it into your question as text.
    – muru
    Commented Apr 30, 2020 at 17:53

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .