fork of go-gitdiff with jj support
at v0.4.0 4.9 kB view raw
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)