git/diff.go
1package git
2
3import (
4 "fmt"
5 "log"
6 "strings"
7
8 "github.com/bluekeyes/go-gitdiff/gitdiff"
9 "github.com/go-git/go-git/v5/plumbing/object"
10)
11
12type TextFragment struct {
13 Header string
14 Lines []gitdiff.Line
15}
16
17type Diff struct {
18 Name struct {
19 Old string
20 New string
21 }
22 TextFragments []TextFragment
23 IsBinary bool
24 IsNew bool
25 IsDelete bool
26}
27
28// A nicer git diff representation.
29type NiceDiff struct {
30 Commit struct {
31 Message string
32 Author object.Signature
33 This string
34 Parent string
35 }
36 Stat struct {
37 FilesChanged int
38 Insertions int
39 Deletions int
40 }
41 Diff []Diff
42}
43
44func (g *GitRepo) Diff() (*NiceDiff, error) {
45 c, err := g.r.CommitObject(g.h)
46 if err != nil {
47 return nil, fmt.Errorf("commit object: %w", err)
48 }
49
50 commitTree, err := c.Tree()
51 if err != nil {
52 return nil, fmt.Errorf("commit tree: %w", err)
53 }
54
55 parentTree := &object.Tree{}
56 var parent *object.Commit
57 if c.NumParents() != 0 {
58 parent, err = c.Parents().Next()
59 if err != nil {
60 return nil, fmt.Errorf("parent commit (is the repository shallow?): %w", err)
61 }
62 parentTree, err = parent.Tree()
63 if err != nil {
64 return nil, fmt.Errorf("parent tree: %w", err)
65 }
66 }
67
68 patch, err := parentTree.Patch(commitTree)
69 if err != nil {
70 return nil, fmt.Errorf("patch: %w", err)
71 }
72
73 diffs, _, err := gitdiff.Parse(strings.NewReader(patch.String()))
74 if err != nil {
75 log.Println(err)
76 }
77
78 nd := NiceDiff{}
79 nd.Commit.This = c.Hash.String()
80
81 if parent == nil {
82 nd.Commit.Parent = ""
83 } else {
84 nd.Commit.Parent = parent.Hash.String()
85 }
86 nd.Commit.Author = c.Author
87 nd.Commit.Message = c.Message
88
89 for _, d := range diffs {
90 ndiff := Diff{}
91 ndiff.Name.New = d.NewName
92 ndiff.Name.Old = d.OldName
93 ndiff.IsBinary = d.IsBinary
94 ndiff.IsNew = d.IsNew
95 ndiff.IsDelete = d.IsDelete
96
97 for _, tf := range d.TextFragments {
98 ndiff.TextFragments = append(ndiff.TextFragments, TextFragment{
99 Header: tf.Header(),
100 Lines: tf.Lines,
101 })
102 for _, l := range tf.Lines {
103 switch l.Op {
104 case gitdiff.OpAdd:
105 nd.Stat.Insertions += 1
106 case gitdiff.OpDelete:
107 nd.Stat.Deletions += 1
108 }
109 }
110 }
111
112 nd.Diff = append(nd.Diff, ndiff)
113 }
114
115 nd.Stat.FilesChanged = len(diffs)
116
117 return &nd, nil
118}