Skip to content

Commit 11d71e2

Browse files
committed
diffstat (Stat method on Hunk and FileDiff)
1 parent d353d86 commit 11d71e2

File tree

2 files changed

+123
-1
lines changed

2 files changed

+123
-1
lines changed

diff/diff.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package diff
22

3-
import "time"
3+
import (
4+
"bytes"
5+
"time"
6+
)
47

58
// A FileDiff represents a unified diff for a single file.
69
//
@@ -20,6 +23,16 @@ type FileDiff struct {
2023
Hunks []*Hunk // hunks that were changed from orig to new
2124
}
2225

26+
// Stat computes the number of lines added/changed/deleted in all
27+
// hunks in this file's diff.
28+
func (d *FileDiff) Stat() Stat {
29+
total := Stat{}
30+
for _, h := range d.Hunks {
31+
total.add(h.Stat())
32+
}
33+
return total
34+
}
35+
2336
// A Hunk represents a series of changes (additions or deletions) in a
2437
// file's unified diff.
2538
type Hunk struct {
@@ -34,6 +47,43 @@ type Hunk struct {
3447
Body []byte // hunk body (lines prefixed with '-', '+', or ' ')
3548
}
3649

50+
// Stat computes the number of lines added/changed/deleted in this
51+
// hunk.
52+
func (h *Hunk) Stat() Stat {
53+
lines := bytes.Split(h.Body, []byte{'\n'})
54+
var last byte
55+
st := Stat{}
56+
for _, line := range lines {
57+
if len(line) == 0 {
58+
last = 0
59+
continue
60+
}
61+
switch line[0] {
62+
case '-':
63+
if last == '+' {
64+
st.Added--
65+
st.Changed++
66+
last = 0 // next line can't change this one since this is already a change
67+
} else {
68+
st.Deleted++
69+
last = line[0]
70+
}
71+
case '+':
72+
if last == '-' {
73+
st.Deleted--
74+
st.Changed++
75+
last = 0 // next line can't change this one since this is already a change
76+
} else {
77+
st.Added++
78+
last = line[0]
79+
}
80+
default:
81+
last = 0
82+
}
83+
}
84+
return st
85+
}
86+
3787
var (
3888
hunkPrefix = []byte("@@ ")
3989
)
@@ -44,3 +94,15 @@ const hunkHeader = "@@ -%d,%d +%d,%d @@"
4494
// header timestamps. See
4595
// http://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html.
4696
const diffTimeFormat = "2006-01-02 15:04:05.000000000 -0700"
97+
98+
// A Stat is a diff stat that represents the number of lines
99+
// added/changed/deleted.
100+
type Stat struct {
101+
Added, Changed, Deleted int // numbers of lines
102+
}
103+
104+
func (s *Stat) add(o Stat) {
105+
s.Added += o.Added
106+
s.Changed += o.Changed
107+
s.Deleted += o.Deleted
108+
}

diff/diff_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,63 @@ func TestNoNewlineAtEnd(t *testing.T) {
189189
}
190190
}
191191
}
192+
193+
func TestFileDiff_Stat(t *testing.T) {
194+
tests := map[string]struct {
195+
hunks []*Hunk
196+
want Stat
197+
}{
198+
"no change": {
199+
hunks: []*Hunk{
200+
{Body: []byte(`@@ -0,0 +0,0
201+
a
202+
b
203+
`)},
204+
},
205+
want: Stat{},
206+
},
207+
"added/deleted": {
208+
hunks: []*Hunk{
209+
{Body: []byte(`@@ -0,0 +0,0
210+
+a
211+
b
212+
-c
213+
d
214+
`)},
215+
},
216+
want: Stat{Added: 1, Deleted: 1},
217+
},
218+
"changed": {
219+
hunks: []*Hunk{
220+
{Body: []byte(`@@ -0,0 +0,0
221+
+a
222+
+b
223+
-c
224+
-d
225+
e
226+
`)},
227+
},
228+
want: Stat{Added: 1, Changed: 1, Deleted: 1},
229+
},
230+
"many changes": {
231+
hunks: []*Hunk{
232+
{Body: []byte(`@@ -0,0 +0,0
233+
+a
234+
-b
235+
+c
236+
-d
237+
e
238+
`)},
239+
},
240+
want: Stat{Added: 0, Changed: 2, Deleted: 0},
241+
},
242+
}
243+
for label, test := range tests {
244+
fdiff := &FileDiff{Hunks: test.hunks}
245+
stat := fdiff.Stat()
246+
if !reflect.DeepEqual(stat, test.want) {
247+
t.Errorf("%s: got diff stat %+v, want %+v", label, stat, test.want)
248+
continue
249+
}
250+
}
251+
}

0 commit comments

Comments
 (0)