all repos — elgit @ 59ba5ba7dd66f7bf537d1f17a9d588acce6b5e16

fork of legit: web frontend for git, written in go

git/diff.go (view raw)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package git

import (
	"fmt"
	"log"
	"strings"

	"github.com/bluekeyes/go-gitdiff/gitdiff"
	"github.com/go-git/go-git/v5/plumbing/object"
)

type TextFragment struct {
	Header string
	Lines  []gitdiff.Line
}

type Diff struct {
	Name struct {
		Old string
		New string
	}
	TextFragments []TextFragment
	IsBinary      bool
	IsNew         bool
	IsDelete      bool
}

type Commit struct {
	Message string
	Author  object.Signature
	This    string
	Parent  string
}

type Stat struct {
	FilesChanged int
	Insertions   int
	Deletions    int
}

// A nicer git diff representation.
type NiceDiff struct {
	Commit Commit
	Stat   Stat
	Diff   []Diff
}

// DiffFile returns a diff for a specific file in the current commit
func (g *GitRepo) DiffFile(filePath string) (*NiceDiff, error) {
	niceDiff, err := g.Diff()
	if err != nil {
		return nil, err
	}

	// Filter diffs to only include the specified file
	var filteredDiffs []Diff
	for _, d := range niceDiff.Diff {
		if d.Name.New == filePath || d.Name.Old == filePath {
			filteredDiffs = append(filteredDiffs, d)
		}
	}

	// If no matching files were found, return an empty diff
	if len(filteredDiffs) == 0 {
		return &NiceDiff{
			Commit: niceDiff.Commit,
			Stat: Stat{
				FilesChanged: 0,
				Insertions:   0,
				Deletions:    0,
			},
			Diff: []Diff{},
		}, nil
	}

	// Recalculate stats for the filtered diffs
	stat := Stat{
		FilesChanged: len(filteredDiffs),
		Insertions:   0,
		Deletions:    0,
	}

	for _, d := range filteredDiffs {
		for _, tf := range d.TextFragments {
			for _, l := range tf.Lines {
				switch l.Op {
				case gitdiff.OpAdd:
					stat.Insertions++
				case gitdiff.OpDelete:
					stat.Deletions++
				}
			}
		}
	}

	return &NiceDiff{
		Commit: niceDiff.Commit,
		Stat:   stat,
		Diff:   filteredDiffs,
	}, nil
}

func (g *GitRepo) Diff() (*NiceDiff, error) {
	c, err := g.r.CommitObject(g.h)
	if err != nil {
		return nil, fmt.Errorf("commit object: %w", err)
	}

	patch := &object.Patch{}
	commitTree, err := c.Tree()
	parent := &object.Commit{}
	if err == nil {
		parentTree := &object.Tree{}
		if c.NumParents() != 0 {
			parent, err = c.Parents().Next()
			if err == nil {
				parentTree, err = parent.Tree()
				if err == nil {
					patch, err = parentTree.Patch(commitTree)
					if err != nil {
						return nil, fmt.Errorf("patch: %w", err)
					}
				}
			}
		} else {
			patch, err = parentTree.Patch(commitTree)
			if err != nil {
				return nil, fmt.Errorf("patch: %w", err)
			}
		}
	}

	diffs, _, err := gitdiff.Parse(strings.NewReader(patch.String()))
	if err != nil {
		log.Println(err)
	}

	nd := NiceDiff{}
	nd.Commit.This = c.Hash.String()

	if parent.Hash.IsZero() {
		nd.Commit.Parent = ""
	} else {
		nd.Commit.Parent = parent.Hash.String()
	}
	nd.Commit.Author = c.Author
	nd.Commit.Message = c.Message

	for _, d := range diffs {
		ndiff := Diff{}
		ndiff.Name.New = d.NewName
		ndiff.Name.Old = d.OldName
		ndiff.IsBinary = d.IsBinary
		ndiff.IsNew = d.IsNew
		ndiff.IsDelete = d.IsDelete

		for _, tf := range d.TextFragments {
			ndiff.TextFragments = append(ndiff.TextFragments, TextFragment{
				Header: tf.Header(),
				Lines:  tf.Lines,
			})
			for _, l := range tf.Lines {
				switch l.Op {
				case gitdiff.OpAdd:
					nd.Stat.Insertions += 1
				case gitdiff.OpDelete:
					nd.Stat.Deletions += 1
				}
			}
		}

		nd.Diff = append(nd.Diff, ndiff)
	}

	nd.Stat.FilesChanged = len(diffs)

	return &nd, nil
}