all repos

mugit @ e2e58a616a6974072fd512b70ed85323bb5c4a98

🐮 git server that your cow will love

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

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