File: gitdiff.go

package info (click to toggle)
golang-github-gitleaks-go-gitdiff 0.8.0-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 800 kB
  • sloc: makefile: 2; sh: 1
file content (212 lines) | stat: -rw-r--r-- 5,151 bytes parent folder | download
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
package gitdiff

import (
	"errors"
	"fmt"
	"os"
	"strings"
)

// File describes changes to a single file. It can be either a text file or a
// binary file.
type File struct {
	OldName string
	NewName string

	IsNew    bool
	IsDelete bool
	IsCopy   bool
	IsRename bool

	OldMode os.FileMode
	NewMode os.FileMode

	OldOIDPrefix string
	NewOIDPrefix string
	Score        int

	PatchHeader *PatchHeader

	// TextFragments contains the fragments describing changes to a text file. It
	// may be empty if the file is empty or if only the mode changes.
	TextFragments []*TextFragment

	// IsBinary is true if the file is a binary file. If the patch includes
	// binary data, BinaryFragment will be non-nil and describe the changes to
	// the data. If the patch is reversible, ReverseBinaryFragment will also be
	// non-nil and describe the changes needed to restore the original file
	// after applying the changes in BinaryFragment.
	IsBinary              bool
	BinaryFragment        *BinaryFragment
	ReverseBinaryFragment *BinaryFragment
}

// TextFragment describes changed lines starting at a specific line in a text file.
type TextFragment struct {
	Comment string

	OldPosition int64
	OldLines    int64

	NewPosition int64
	NewLines    int64

	LinesAdded   int64
	LinesDeleted int64

	LeadingContext  int64
	TrailingContext int64

	Lines []Line
}

func (f *TextFragment) Raw(op LineOp) string {
	sb := strings.Builder{}
	for _, l := range f.Lines {
		if l.Op == op {
			sb.WriteString(l.Line)
		}
	}
	return sb.String()
}

// Header returns the canonical header of this fragment.
func (f *TextFragment) Header() string {
	return fmt.Sprintf("@@ -%d,%d +%d,%d @@ %s", f.OldPosition, f.OldLines, f.NewPosition, f.NewLines, f.Comment)
}

// Validate checks that the fragment is self-consistent and appliable. Validate
// returns an error if and only if the fragment is invalid.
func (f *TextFragment) Validate() error {
	if f == nil {
		return errors.New("nil fragment")
	}

	var (
		oldLines, newLines                     int64
		leadingContext, trailingContext        int64
		contextLines, addedLines, deletedLines int64
	)

	// count the types of lines in the fragment content
	for i, line := range f.Lines {
		switch line.Op {
		case OpContext:
			oldLines++
			newLines++
			contextLines++
			if addedLines == 0 && deletedLines == 0 {
				leadingContext++
			} else {
				trailingContext++
			}
		case OpAdd:
			newLines++
			addedLines++
			trailingContext = 0
		case OpDelete:
			oldLines++
			deletedLines++
			trailingContext = 0
		default:
			return fmt.Errorf("unknown operator %q on line %d", line.Op, i+1)
		}
	}

	// check the actual counts against the reported counts
	if oldLines != f.OldLines {
		return lineCountErr("old", oldLines, f.OldLines)
	}
	if newLines != f.NewLines {
		return lineCountErr("new", newLines, f.NewLines)
	}
	if leadingContext != f.LeadingContext {
		return lineCountErr("leading context", leadingContext, f.LeadingContext)
	}
	if trailingContext != f.TrailingContext {
		return lineCountErr("trailing context", trailingContext, f.TrailingContext)
	}
	if addedLines != f.LinesAdded {
		return lineCountErr("added", addedLines, f.LinesAdded)
	}
	if deletedLines != f.LinesDeleted {
		return lineCountErr("deleted", deletedLines, f.LinesDeleted)
	}

	// if a file is being created, it can only contain additions
	if f.OldPosition == 0 && f.OldLines != 0 {
		return errors.New("file creation fragment contains context or deletion lines")
	}

	return nil
}

func lineCountErr(kind string, actual, reported int64) error {
	return fmt.Errorf("fragment contains %d %s lines but reports %d", actual, kind, reported)
}

// Line is a line in a text fragment.
type Line struct {
	Op   LineOp
	Line string
}

func (fl Line) String() string {
	return fl.Op.String() + fl.Line
}

// Old returns true if the line appears in the old content of the fragment.
func (fl Line) Old() bool {
	return fl.Op == OpContext || fl.Op == OpDelete
}

// New returns true if the line appears in the new content of the fragment.
func (fl Line) New() bool {
	return fl.Op == OpContext || fl.Op == OpAdd
}

// NoEOL returns true if the line is missing a trailing newline character.
func (fl Line) NoEOL() bool {
	return len(fl.Line) == 0 || fl.Line[len(fl.Line)-1] != '\n'
}

// LineOp describes the type of a text fragment line: context, added, or removed.
type LineOp int

const (
	// OpContext indicates a context line
	OpContext LineOp = iota
	// OpDelete indicates a deleted line
	OpDelete
	// OpAdd indicates an added line
	OpAdd
)

func (op LineOp) String() string {
	switch op {
	case OpContext:
		return " "
	case OpDelete:
		return "-"
	case OpAdd:
		return "+"
	}
	return "?"
}

// BinaryFragment describes changes to a binary file.
type BinaryFragment struct {
	Method BinaryPatchMethod
	Size   int64
	Data   []byte
}

// BinaryPatchMethod is the method used to create and apply the binary patch.
type BinaryPatchMethod int

const (
	// BinaryPatchDelta indicates the data uses Git's packfile encoding
	BinaryPatchDelta BinaryPatchMethod = iota
	// BinaryPatchLiteral indicates the data is the exact file content
	BinaryPatchLiteral
)