all repos

mugit @ 57f9c82

🐮 git server that your cow will love

mugit/internal/git/diff.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
feat: compare refs (#9)..., 1 month ago
1
package git
2
3
import (
4
	"fmt"
5
	"strings"
6
7
	"github.com/bluekeyes/go-gitdiff/gitdiff"
8
	"github.com/go-git/go-git/v5/plumbing"
9
	"github.com/go-git/go-git/v5/plumbing/object"
10
)
11
12
type TextFragment struct {
13
	Header      string
14
	Lines       []gitdiff.Line
15
	OldPosition int64
16
	NewPosition int64
17
}
18
19
type Diff struct {
20
	Name struct {
21
		Old string
22
		New string
23
	}
24
	TextFragments []TextFragment
25
	IsBinary      bool
26
	IsNew         bool
27
	IsDelete      bool
28
	IsRename      bool
29
}
30
31
type NiceDiff struct {
32
	Diff    []Diff
33
	Commit  *Commit
34
	Parents []string // list of short hashes
35
	Stat    struct {
36
		FilesChanged int
37
		Insertions   int
38
		Deletions    int
39
	}
40
}
41
42
func (g *Repo) Diff() (*NiceDiff, error) {
43
	c, err := g.r.CommitObject(g.h)
44
	if err != nil {
45
		return nil, fmt.Errorf("commit object: %w", err)
46
	}
47
48
	patch, parents, err := g.getPatch(c)
49
	if err != nil {
50
		return nil, err
51
	}
52
53
	nd, err := parseNiceDiff(patch.String())
54
	if err != nil {
55
		return nil, err
56
	}
57
	nd.Commit = newCommit(c)
58
	nd.Parents = parents
59
	return nd, nil
60
}
61
62
func parseNiceDiff(patch string) (*NiceDiff, error) {
63
	nd := &NiceDiff{}
64
	if strings.TrimSpace(patch) == "" {
65
		return nd, nil
66
	}
67
68
	diffs, _, err := gitdiff.Parse(strings.NewReader(patch))
69
	if err != nil {
70
		return nil, fmt.Errorf("parsing diff: %w", err)
71
	}
72
73
	nd.Stat.FilesChanged = len(diffs)
74
	nd.Diff = make([]Diff, len(diffs))
75
	for i, d := range diffs {
76
		diff := &nd.Diff[i]
77
		diff.Name.New = d.NewName
78
		if d.OldName != d.NewName {
79
			diff.Name.Old = d.OldName
80
		}
81
		diff.IsBinary = d.IsBinary
82
		diff.IsNew = d.IsNew
83
		diff.IsDelete = d.IsDelete
84
		diff.IsRename = d.IsRename
85
86
		for _, tf := range d.TextFragments {
87
			diff.TextFragments = append(diff.TextFragments, TextFragment{
88
				Header:      tf.Header(),
89
				Lines:       tf.Lines,
90
				OldPosition: tf.OldPosition,
91
				NewPosition: tf.NewPosition,
92
			})
93
			for _, l := range tf.Lines {
94
				switch l.Op {
95
				case gitdiff.OpAdd:
96
					nd.Stat.Insertions += 1
97
				case gitdiff.OpDelete:
98
					nd.Stat.Deletions += 1
99
				}
100
			}
101
		}
102
	}
103
	return nd, nil
104
}
105
106
func (g *Repo) getPatch(c *object.Commit) (*object.Patch, []string, error) {
107
	commitTree, err := c.Tree()
108
	if err != nil {
109
		return nil, nil, err
110
	}
111
112
	parentTree := &object.Tree{}
113
	if c.NumParents() > 0 {
114
		parent, perr := c.Parents().Next()
115
		if perr != nil {
116
			return nil, nil, perr
117
		}
118
		if parentTree, err = parent.Tree(); err != nil {
119
			return nil, nil, err
120
		}
121
	}
122
123
	patch, err := parentTree.Patch(commitTree)
124
	if err != nil {
125
		return nil, nil, fmt.Errorf("patch: %w", err)
126
	}
127
128
	parents := make([]string, len(c.ParentHashes))
129
	for i, h := range c.ParentHashes {
130
		parents[i] = newShortHash(h)
131
	}
132
133
	return patch, parents, nil
134
}
135
136
func (g *Repo) diffBetween(base, head plumbing.Hash) (*NiceDiff, error) {
137
	baseCommit, err := g.r.CommitObject(base)
138
	if err != nil {
139
		return nil, fmt.Errorf("base commit object %s: %w", base, err)
140
	}
141
	headCommit, err := g.r.CommitObject(head)
142
	if err != nil {
143
		return nil, fmt.Errorf("head commit object %s: %w", head, err)
144
	}
145
146
	baseTree, err := baseCommit.Tree()
147
	if err != nil {
148
		return nil, fmt.Errorf("base tree %s: %w", base, err)
149
	}
150
	headTree, err := headCommit.Tree()
151
	if err != nil {
152
		return nil, fmt.Errorf("head tree %s: %w", head, err)
153
	}
154
155
	patch, err := baseTree.Patch(headTree)
156
	if err != nil {
157
		return nil, fmt.Errorf("tree patch %s..%s: %w", base, head, err)
158
	}
159
160
	diff, err := parseNiceDiff(patch.String())
161
	if err != nil {
162
 		return nil, fmt.Errorf("parse tree diff %s..%s: %w", base, head, err)
163
	}
164
	return diff, nil
165
}