all repos

mugit @ dc720d9

🐮 git server that your cow will love

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

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
ui: add line numbers to diffs, 2 months 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/object"
9
)
10
11
type TextFragment struct {
12
	Header      string
13
	Lines       []gitdiff.Line
14
	OldPosition int64
15
	NewPosition int64
16
}
17
18
type Diff struct {
19
	Name struct {
20
		Old string
21
		New string
22
	}
23
	TextFragments []TextFragment
24
	IsBinary      bool
25
	IsNew         bool
26
	IsDelete      bool
27
	IsRename      bool
28
}
29
30
type NiceDiff struct {
31
	Diff    []Diff
32
	Commit  *Commit
33
	Parents []string // list of short hashes
34
	Stat    struct {
35
		FilesChanged int
36
		Insertions   int
37
		Deletions    int
38
	}
39
}
40
41
func (g *Repo) Diff() (*NiceDiff, error) {
42
	c, err := g.r.CommitObject(g.h)
43
	if err != nil {
44
		return nil, fmt.Errorf("commit object: %w", err)
45
	}
46
47
	patch, parents, err := g.getPatch(c)
48
	if err != nil {
49
		return nil, err
50
	}
51
52
	diffs, _, err := gitdiff.Parse(strings.NewReader(patch.String()))
53
	if err != nil {
54
		return nil, fmt.Errorf("parsing diff: %w", err)
55
	}
56
57
	nd := NiceDiff{}
58
	nd.Commit = newCommit(c)
59
	nd.Parents = parents
60
	nd.Stat.FilesChanged = len(diffs)
61
	nd.Diff = make([]Diff, len(diffs))
62
	for i, d := range diffs {
63
		diff := &nd.Diff[i]
64
		diff.Name.New = d.NewName
65
		if d.OldName != d.NewName {
66
			diff.Name.Old = d.OldName
67
		}
68
		diff.IsBinary = d.IsBinary
69
		diff.IsNew = d.IsNew
70
		diff.IsDelete = d.IsDelete
71
		diff.IsRename = d.IsRename
72
73
		for _, tf := range d.TextFragments {
74
			diff.TextFragments = append(diff.TextFragments, TextFragment{
75
				Header:      tf.Header(),
76
				Lines:       tf.Lines,
77
				OldPosition: tf.OldPosition,
78
				NewPosition: tf.NewPosition,
79
			})
80
			for _, l := range tf.Lines {
81
				switch l.Op {
82
				case gitdiff.OpAdd:
83
					nd.Stat.Insertions += 1
84
				case gitdiff.OpDelete:
85
					nd.Stat.Deletions += 1
86
				}
87
			}
88
		}
89
	}
90
	return &nd, nil
91
}
92
93
func (g *Repo) getPatch(c *object.Commit) (*object.Patch, []string, error) {
94
	commitTree, err := c.Tree()
95
	if err != nil {
96
		return nil, nil, err
97
	}
98
99
	parentTree := &object.Tree{}
100
	if c.NumParents() > 0 {
101
		parent, perr := c.Parents().Next()
102
		if perr != nil {
103
			return nil, nil, perr
104
		}
105
		if parentTree, err = parent.Tree(); err != nil {
106
			return nil, nil, err
107
		}
108
	}
109
110
	patch, err := parentTree.Patch(commitTree)
111
	if err != nil {
112
		return nil, nil, fmt.Errorf("patch: %w", err)
113
	}
114
115
	parents := make([]string, len(c.ParentHashes))
116
	for i, h := range c.ParentHashes {
117
		parents[i] = newShortHash(h)
118
	}
119
120
	return patch, parents, nil
121
}