fork of go-gitdiff with jj support
1package gitdiff
2
3import (
4 "errors"
5 "fmt"
6 "os"
7)
8
9// File describes changes to a single file. It can be either a text file or a
10// binary file.
11type File struct {
12 OldName string
13 NewName string
14
15 IsNew bool
16 IsDelete bool
17 IsCopy bool
18 IsRename bool
19
20 OldMode os.FileMode
21 NewMode os.FileMode
22
23 OldOIDPrefix string
24 NewOIDPrefix string
25 Score int
26
27 // TextFragments contains the fragments describing changes to a text file. It
28 // may be empty if the file is empty or if only the mode changes.
29 TextFragments []*TextFragment
30
31 // IsBinary is true if the file is a binary file. If the patch includes
32 // binary data, BinaryFragment will be non-nil and describe the changes to
33 // the data. If the patch is reversible, ReverseBinaryFragment will also be
34 // non-nil and describe the changes needed to restore the original file
35 // after applying the changes in BinaryFragment.
36 IsBinary bool
37 BinaryFragment *BinaryFragment
38 ReverseBinaryFragment *BinaryFragment
39}
40
41// TextFragment describes changed lines starting at a specific line in a text file.
42type TextFragment struct {
43 Comment string
44
45 OldPosition int64
46 OldLines int64
47
48 NewPosition int64
49 NewLines int64
50
51 LinesAdded int64
52 LinesDeleted int64
53
54 LeadingContext int64
55 TrailingContext int64
56
57 Lines []Line
58}
59
60// Header returns the canonical header of this fragment.
61func (f *TextFragment) Header() string {
62 return fmt.Sprintf("@@ -%d,%d +%d,%d @@ %s", f.OldPosition, f.OldLines, f.NewPosition, f.NewLines, f.Comment)
63}
64
65// Validate checks that the fragment is self-consistent and appliable. Validate
66// returns an error if and only if the fragment is invalid.
67func (f *TextFragment) Validate() error {
68 if f == nil {
69 return errors.New("nil fragment")
70 }
71
72 var (
73 oldLines, newLines int64
74 leadingContext, trailingContext int64
75 contextLines, addedLines, deletedLines int64
76 )
77
78 // count the types of lines in the fragment content
79 for i, line := range f.Lines {
80 switch line.Op {
81 case OpContext:
82 oldLines++
83 newLines++
84 contextLines++
85 if addedLines == 0 && deletedLines == 0 {
86 leadingContext++
87 } else {
88 trailingContext++
89 }
90 case OpAdd:
91 newLines++
92 addedLines++
93 trailingContext = 0
94 case OpDelete:
95 oldLines++
96 deletedLines++
97 trailingContext = 0
98 default:
99 return fmt.Errorf("unknown operator %q on line %d", line.Op, i+1)
100 }
101 }
102
103 // check the actual counts against the reported counts
104 if oldLines != f.OldLines {
105 return lineCountErr("old", oldLines, f.OldLines)
106 }
107 if newLines != f.NewLines {
108 return lineCountErr("new", newLines, f.NewLines)
109 }
110 if leadingContext != f.LeadingContext {
111 return lineCountErr("leading context", leadingContext, f.LeadingContext)
112 }
113 if trailingContext != f.TrailingContext {
114 return lineCountErr("trailing context", trailingContext, f.TrailingContext)
115 }
116 if addedLines != f.LinesAdded {
117 return lineCountErr("added", addedLines, f.LinesAdded)
118 }
119 if deletedLines != f.LinesDeleted {
120 return lineCountErr("deleted", deletedLines, f.LinesDeleted)
121 }
122
123 // if a file is being created, it can only contain additions
124 if f.OldPosition == 0 && f.OldLines != 0 {
125 return errors.New("file creation fragment contains context or deletion lines")
126 }
127
128 return nil
129}
130
131func lineCountErr(kind string, actual, reported int64) error {
132 return fmt.Errorf("fragment contains %d %s lines but reports %d", actual, kind, reported)
133}
134
135// Line is a line in a text fragment.
136type Line struct {
137 Op LineOp
138 Line string
139}
140
141func (fl Line) String() string {
142 return fl.Op.String() + fl.Line
143}
144
145// Old returns true if the line appears in the old content of the fragment.
146func (fl Line) Old() bool {
147 return fl.Op == OpContext || fl.Op == OpDelete
148}
149
150// New returns true if the line appears in the new content of the fragment.
151func (fl Line) New() bool {
152 return fl.Op == OpContext || fl.Op == OpAdd
153}
154
155// NoEOL returns true if the line is missing a trailing newline character.
156func (fl Line) NoEOL() bool {
157 return len(fl.Line) == 0 || fl.Line[len(fl.Line)-1] != '\n'
158}
159
160// LineOp describes the type of a text fragment line: context, added, or removed.
161type LineOp int
162
163const (
164 // OpContext indicates a context line
165 OpContext LineOp = iota
166 // OpDelete indicates a deleted line
167 OpDelete
168 // OpAdd indicates an added line
169 OpAdd
170)
171
172func (op LineOp) String() string {
173 switch op {
174 case OpContext:
175 return " "
176 case OpDelete:
177 return "-"
178 case OpAdd:
179 return "+"
180 }
181 return "?"
182}
183
184// BinaryFragment describes changes to a binary file.
185type BinaryFragment struct {
186 Method BinaryPatchMethod
187 Size int64
188 Data []byte
189}
190
191// BinaryPatchMethod is the method used to create and apply the binary patch.
192type BinaryPatchMethod int
193
194const (
195 // BinaryPatchDelta indicates the data uses Git's packfile encoding
196 BinaryPatchDelta BinaryPatchMethod = iota
197 // BinaryPatchLiteral indicates the data is the exact file content
198 BinaryPatchLiteral
199)