this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Move all header functions to file_header.go

Even though these are defined on *parser, it makes more sense to have
them in this file.

+312 -310
+73
gitdiff/file_header.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 + "io" 5 6 "os" 6 7 "strconv" 7 8 "strings" 8 9 "time" 9 10 ) 11 + 12 + func (p *parser) ParseGitFileHeader(f *File, header string) error { 13 + header = strings.TrimPrefix(header, fileHeaderPrefix) 14 + defaultName, err := parseGitHeaderName(header) 15 + if err != nil { 16 + return p.Errorf("git file header: %v", err) 17 + } 18 + 19 + for { 20 + line, err := p.PeekLine() 21 + if err == io.EOF { 22 + break 23 + } else if err != nil { 24 + return err 25 + } 26 + 27 + end, err := parseGitHeaderData(f, line, defaultName) 28 + if err != nil { 29 + return p.Errorf("git file header: %v", err) 30 + } 31 + if end { 32 + break 33 + } 34 + p.Line() 35 + } 36 + 37 + if f.OldName == "" && f.NewName == "" { 38 + if defaultName == "" { 39 + return p.Errorf("git file header: missing filename information") 40 + } 41 + f.OldName = defaultName 42 + f.NewName = defaultName 43 + } 44 + 45 + if (f.NewName == "" && !f.IsDelete) || (f.OldName == "" && !f.IsNew) { 46 + return p.Errorf("git file header: missing filename information") 47 + } 48 + 49 + return nil 50 + } 51 + 52 + func (p *parser) ParseTraditionalFileHeader(f *File, oldLine, newLine string) error { 53 + oldName, _, err := parseName(strings.TrimPrefix(oldLine, oldFilePrefix), '\t', 0) 54 + if err != nil { 55 + return p.Errorf("file header: %v", err) 56 + } 57 + 58 + newName, _, err := parseName(strings.TrimPrefix(newLine, newFilePrefix), '\t', 0) 59 + if err != nil { 60 + return p.Errorf("file header: %v", err) 61 + } 62 + 63 + switch { 64 + case oldName == devNull || hasEpochTimestamp(oldLine): 65 + f.IsNew = true 66 + f.NewName = newName 67 + case newName == devNull || hasEpochTimestamp(newLine): 68 + f.IsDelete = true 69 + f.OldName = oldName 70 + default: 71 + // if old name is a prefix of new name, use that instead 72 + // this avoids picking variants like "file.bak" or "file~" 73 + if strings.HasPrefix(newName, oldName) { 74 + f.OldName = oldName 75 + f.NewName = oldName 76 + } else { 77 + f.OldName = newName 78 + f.NewName = newName 79 + } 80 + } 81 + return nil 82 + } 10 83 11 84 // parseGitHeaderName extracts a default file name from the Git file header 12 85 // line. This is required for mode-only changes and creation/deletion of empty
+239
gitdiff/file_header_test.go
··· 1 1 package gitdiff 2 2 3 3 import ( 4 + "bufio" 4 5 "os" 5 6 "reflect" 7 + "strings" 6 8 "testing" 7 9 ) 10 + 11 + func TestParseGitFileHeader(t *testing.T) { 12 + tests := map[string]struct { 13 + Input string 14 + Output *File 15 + Err bool 16 + }{ 17 + "fileContentChange": { 18 + Input: `diff --git a/dir/file.txt b/dir/file.txt 19 + index 1c23fcc..40a1b33 100644 20 + --- a/dir/file.txt 21 + +++ b/dir/file.txt 22 + @@ -2,3 +4,5 @@ 23 + `, 24 + Output: &File{ 25 + OldName: "dir/file.txt", 26 + NewName: "dir/file.txt", 27 + OldMode: os.FileMode(0100644), 28 + OldOIDPrefix: "1c23fcc", 29 + NewOIDPrefix: "40a1b33", 30 + }, 31 + }, 32 + "newFile": { 33 + Input: `diff --git a/dir/file.txt b/dir/file.txt 34 + new file mode 100644 35 + index 0000000..f5711e4 36 + --- /dev/null 37 + +++ b/dir/file.txt 38 + `, 39 + Output: &File{ 40 + NewName: "dir/file.txt", 41 + NewMode: os.FileMode(0100644), 42 + OldOIDPrefix: "0000000", 43 + NewOIDPrefix: "f5711e4", 44 + IsNew: true, 45 + }, 46 + }, 47 + "newEmptyFile": { 48 + Input: `diff --git a/empty.txt b/empty.txt 49 + new file mode 100644 50 + index 0000000..e69de29 51 + `, 52 + Output: &File{ 53 + NewName: "empty.txt", 54 + NewMode: os.FileMode(0100644), 55 + OldOIDPrefix: "0000000", 56 + NewOIDPrefix: "e69de29", 57 + IsNew: true, 58 + }, 59 + }, 60 + "deleteFile": { 61 + Input: `diff --git a/dir/file.txt b/dir/file.txt 62 + deleted file mode 100644 63 + index 44cc321..0000000 64 + --- a/dir/file.txt 65 + +++ /dev/null 66 + `, 67 + Output: &File{ 68 + OldName: "dir/file.txt", 69 + OldMode: os.FileMode(0100644), 70 + OldOIDPrefix: "44cc321", 71 + NewOIDPrefix: "0000000", 72 + IsDelete: true, 73 + }, 74 + }, 75 + "changeMode": { 76 + Input: `diff --git a/file.sh b/file.sh 77 + old mode 100644 78 + new mode 100755 79 + `, 80 + Output: &File{ 81 + OldName: "file.sh", 82 + NewName: "file.sh", 83 + OldMode: os.FileMode(0100644), 84 + NewMode: os.FileMode(0100755), 85 + }, 86 + }, 87 + "rename": { 88 + Input: `diff --git a/foo.txt b/bar.txt 89 + similarity index 100% 90 + rename from foo.txt 91 + rename to bar.txt 92 + `, 93 + Output: &File{ 94 + OldName: "foo.txt", 95 + NewName: "bar.txt", 96 + Score: 100, 97 + IsRename: true, 98 + }, 99 + }, 100 + "copy": { 101 + Input: `diff --git a/file.txt b/copy.txt 102 + similarity index 100% 103 + copy from file.txt 104 + copy to copy.txt 105 + `, 106 + Output: &File{ 107 + OldName: "file.txt", 108 + NewName: "copy.txt", 109 + Score: 100, 110 + IsCopy: true, 111 + }, 112 + }, 113 + "missingDefaultFilename": { 114 + Input: `diff --git a/foo.sh b/bar.sh 115 + old mode 100644 116 + new mode 100755 117 + `, 118 + Err: true, 119 + }, 120 + "missingNewFilename": { 121 + Input: `diff --git a/file.txt b/file.txt 122 + index 1c23fcc..40a1b33 100644 123 + --- a/file.txt 124 + `, 125 + Err: true, 126 + }, 127 + "missingOldFilename": { 128 + Input: `diff --git a/file.txt b/file.txt 129 + index 1c23fcc..40a1b33 100644 130 + +++ b/file.txt 131 + `, 132 + Err: true, 133 + }, 134 + "invalidHeaderLine": { 135 + Input: `diff --git a/file.txt b/file.txt 136 + index deadbeef 137 + --- a/file.txt 138 + +++ b/file.txt 139 + `, 140 + Err: true, 141 + }, 142 + } 143 + 144 + for name, test := range tests { 145 + t.Run(name, func(t *testing.T) { 146 + p := &parser{r: bufio.NewReader(strings.NewReader(test.Input))} 147 + header, _ := p.Line() 148 + 149 + var f File 150 + err := p.ParseGitFileHeader(&f, header) 151 + if test.Err { 152 + if err == nil { 153 + t.Fatalf("expected error parsing git file header, got nil") 154 + } 155 + return 156 + } 157 + if err != nil { 158 + t.Fatalf("unexpected error parsing git file header: %v", err) 159 + } 160 + 161 + if test.Output != nil && !reflect.DeepEqual(f, *test.Output) { 162 + t.Errorf("incorrect file\nexpected: %+v\n actual: %+v", *test.Output, f) 163 + } 164 + }) 165 + } 166 + } 167 + 168 + func TestParseTraditionalFileHeader(t *testing.T) { 169 + tests := map[string]struct { 170 + OldLine string 171 + NewLine string 172 + Output *File 173 + Err bool 174 + }{ 175 + "fileContentChange": { 176 + OldLine: "--- dir/file_old.txt\t2019-03-21 23:00:00.0 -0700\n", 177 + NewLine: "+++ dir/file_new.txt\t2019-03-21 23:30:00.0 -0700\n", 178 + Output: &File{ 179 + OldName: "dir/file_new.txt", 180 + NewName: "dir/file_new.txt", 181 + }, 182 + }, 183 + "newFile": { 184 + OldLine: "--- /dev/null\t1969-12-31 17:00:00.0 -0700\n", 185 + NewLine: "+++ dir/file.txt\t2019-03-21 23:30:00.0 -0700\n", 186 + Output: &File{ 187 + NewName: "dir/file.txt", 188 + IsNew: true, 189 + }, 190 + }, 191 + "newFileTimestamp": { 192 + OldLine: "--- dir/file.txt\t1969-12-31 17:00:00.0 -0700\n", 193 + NewLine: "+++ dir/file.txt\t2019-03-21 23:30:00.0 -0700\n", 194 + Output: &File{ 195 + NewName: "dir/file.txt", 196 + IsNew: true, 197 + }, 198 + }, 199 + "deleteFile": { 200 + OldLine: "--- dir/file.txt\t2019-03-21 23:30:00.0 -0700\n", 201 + NewLine: "+++ /dev/null\t1969-12-31 17:00:00.0 -0700\n", 202 + Output: &File{ 203 + OldName: "dir/file.txt", 204 + IsDelete: true, 205 + }, 206 + }, 207 + "deleteFileTimestamp": { 208 + OldLine: "--- dir/file.txt\t2019-03-21 23:30:00.0 -0700\n", 209 + NewLine: "+++ dir/file.txt\t1969-12-31 17:00:00.0 -0700\n", 210 + Output: &File{ 211 + OldName: "dir/file.txt", 212 + IsDelete: true, 213 + }, 214 + }, 215 + "useShortestPrefixName": { 216 + OldLine: "--- dir/file.txt\t2019-03-21 23:00:00.0 -0700\n", 217 + NewLine: "+++ dir/file.txt~\t2019-03-21 23:30:00.0 -0700\n", 218 + Output: &File{ 219 + OldName: "dir/file.txt", 220 + NewName: "dir/file.txt", 221 + }, 222 + }, 223 + } 224 + 225 + for name, test := range tests { 226 + t.Run(name, func(t *testing.T) { 227 + p := &parser{r: bufio.NewReader(strings.NewReader(""))} 228 + 229 + var f File 230 + err := p.ParseTraditionalFileHeader(&f, test.OldLine, test.NewLine) 231 + if test.Err { 232 + if err == nil { 233 + t.Fatalf("expected error parsing traditional file header, got nil") 234 + } 235 + return 236 + } 237 + if err != nil { 238 + t.Fatalf("unexpected error parsing traditional file header: %v", err) 239 + } 240 + 241 + if test.Output != nil && !reflect.DeepEqual(f, *test.Output) { 242 + t.Errorf("incorrect file\nexpected: %+v\n actual: %+v", *test.Output, f) 243 + } 244 + }) 245 + } 246 + } 8 247 9 248 func TestCleanName(t *testing.T) { 10 249 tests := map[string]struct {
-72
gitdiff/parser.go
··· 132 132 panic("TODO(bkeyes): unimplemented") 133 133 } 134 134 135 - func (p *parser) ParseGitFileHeader(f *File, header string) error { 136 - header = strings.TrimPrefix(header, fileHeaderPrefix) 137 - defaultName, err := parseGitHeaderName(header) 138 - if err != nil { 139 - return p.Errorf("git file header: %v", err) 140 - } 141 - 142 - for { 143 - line, err := p.PeekLine() 144 - if err == io.EOF { 145 - break 146 - } else if err != nil { 147 - return err 148 - } 149 - 150 - end, err := parseGitHeaderData(f, line, defaultName) 151 - if err != nil { 152 - return p.Errorf("git file header: %v", err) 153 - } 154 - if end { 155 - break 156 - } 157 - p.Line() 158 - } 159 - 160 - if f.OldName == "" && f.NewName == "" { 161 - if defaultName == "" { 162 - return p.Errorf("git file header: missing filename information") 163 - } 164 - f.OldName = defaultName 165 - f.NewName = defaultName 166 - } 167 - 168 - if (f.NewName == "" && !f.IsDelete) || (f.OldName == "" && !f.IsNew) { 169 - return p.Errorf("git file header: missing filename information") 170 - } 171 - 172 - return nil 173 - } 174 - 175 - func (p *parser) ParseTraditionalFileHeader(f *File, oldLine, newLine string) error { 176 - oldName, _, err := parseName(strings.TrimPrefix(oldLine, oldFilePrefix), '\t', 0) 177 - if err != nil { 178 - return p.Errorf("file header: %v", err) 179 - } 180 - 181 - newName, _, err := parseName(strings.TrimPrefix(newLine, newFilePrefix), '\t', 0) 182 - if err != nil { 183 - return p.Errorf("file header: %v", err) 184 - } 185 - 186 - switch { 187 - case oldName == devNull || hasEpochTimestamp(oldLine): 188 - f.IsNew = true 189 - f.NewName = newName 190 - case newName == devNull || hasEpochTimestamp(newLine): 191 - f.IsDelete = true 192 - f.OldName = oldName 193 - default: 194 - // if old name is a prefix of new name, use that instead 195 - // this avoids picking variants like "file.bak" or "file~" 196 - if strings.HasPrefix(newName, oldName) { 197 - f.OldName = oldName 198 - f.NewName = oldName 199 - } else { 200 - f.OldName = newName 201 - f.NewName = newName 202 - } 203 - } 204 - return nil 205 - } 206 - 207 135 // Line reads and returns the next line. The first call to Line after a call to 208 136 // PeekLine will never retrun an error. 209 137 func (p *parser) Line() (line string, err error) {
-238
gitdiff/parser_test.go
··· 2 2 3 3 import ( 4 4 "bufio" 5 - "os" 6 5 "reflect" 7 6 "strings" 8 7 "testing" ··· 129 128 }) 130 129 } 131 130 } 132 - 133 - func TestParseGitFileHeader(t *testing.T) { 134 - tests := map[string]struct { 135 - Input string 136 - Output *File 137 - Err bool 138 - }{ 139 - "fileContentChange": { 140 - Input: `diff --git a/dir/file.txt b/dir/file.txt 141 - index 1c23fcc..40a1b33 100644 142 - --- a/dir/file.txt 143 - +++ b/dir/file.txt 144 - @@ -2,3 +4,5 @@ 145 - `, 146 - Output: &File{ 147 - OldName: "dir/file.txt", 148 - NewName: "dir/file.txt", 149 - OldMode: os.FileMode(0100644), 150 - OldOIDPrefix: "1c23fcc", 151 - NewOIDPrefix: "40a1b33", 152 - }, 153 - }, 154 - "newFile": { 155 - Input: `diff --git a/dir/file.txt b/dir/file.txt 156 - new file mode 100644 157 - index 0000000..f5711e4 158 - --- /dev/null 159 - +++ b/dir/file.txt 160 - `, 161 - Output: &File{ 162 - NewName: "dir/file.txt", 163 - NewMode: os.FileMode(0100644), 164 - OldOIDPrefix: "0000000", 165 - NewOIDPrefix: "f5711e4", 166 - IsNew: true, 167 - }, 168 - }, 169 - "newEmptyFile": { 170 - Input: `diff --git a/empty.txt b/empty.txt 171 - new file mode 100644 172 - index 0000000..e69de29 173 - `, 174 - Output: &File{ 175 - NewName: "empty.txt", 176 - NewMode: os.FileMode(0100644), 177 - OldOIDPrefix: "0000000", 178 - NewOIDPrefix: "e69de29", 179 - IsNew: true, 180 - }, 181 - }, 182 - "deleteFile": { 183 - Input: `diff --git a/dir/file.txt b/dir/file.txt 184 - deleted file mode 100644 185 - index 44cc321..0000000 186 - --- a/dir/file.txt 187 - +++ /dev/null 188 - `, 189 - Output: &File{ 190 - OldName: "dir/file.txt", 191 - OldMode: os.FileMode(0100644), 192 - OldOIDPrefix: "44cc321", 193 - NewOIDPrefix: "0000000", 194 - IsDelete: true, 195 - }, 196 - }, 197 - "changeMode": { 198 - Input: `diff --git a/file.sh b/file.sh 199 - old mode 100644 200 - new mode 100755 201 - `, 202 - Output: &File{ 203 - OldName: "file.sh", 204 - NewName: "file.sh", 205 - OldMode: os.FileMode(0100644), 206 - NewMode: os.FileMode(0100755), 207 - }, 208 - }, 209 - "rename": { 210 - Input: `diff --git a/foo.txt b/bar.txt 211 - similarity index 100% 212 - rename from foo.txt 213 - rename to bar.txt 214 - `, 215 - Output: &File{ 216 - OldName: "foo.txt", 217 - NewName: "bar.txt", 218 - Score: 100, 219 - IsRename: true, 220 - }, 221 - }, 222 - "copy": { 223 - Input: `diff --git a/file.txt b/copy.txt 224 - similarity index 100% 225 - copy from file.txt 226 - copy to copy.txt 227 - `, 228 - Output: &File{ 229 - OldName: "file.txt", 230 - NewName: "copy.txt", 231 - Score: 100, 232 - IsCopy: true, 233 - }, 234 - }, 235 - "missingDefaultFilename": { 236 - Input: `diff --git a/foo.sh b/bar.sh 237 - old mode 100644 238 - new mode 100755 239 - `, 240 - Err: true, 241 - }, 242 - "missingNewFilename": { 243 - Input: `diff --git a/file.txt b/file.txt 244 - index 1c23fcc..40a1b33 100644 245 - --- a/file.txt 246 - `, 247 - Err: true, 248 - }, 249 - "missingOldFilename": { 250 - Input: `diff --git a/file.txt b/file.txt 251 - index 1c23fcc..40a1b33 100644 252 - +++ b/file.txt 253 - `, 254 - Err: true, 255 - }, 256 - "invalidHeaderLine": { 257 - Input: `diff --git a/file.txt b/file.txt 258 - index deadbeef 259 - --- a/file.txt 260 - +++ b/file.txt 261 - `, 262 - Err: true, 263 - }, 264 - } 265 - 266 - for name, test := range tests { 267 - t.Run(name, func(t *testing.T) { 268 - p := &parser{r: bufio.NewReader(strings.NewReader(test.Input))} 269 - header, _ := p.Line() 270 - 271 - var f File 272 - err := p.ParseGitFileHeader(&f, header) 273 - if test.Err { 274 - if err == nil { 275 - t.Fatalf("expected error parsing git file header, got nil") 276 - } 277 - return 278 - } 279 - if err != nil { 280 - t.Fatalf("unexpected error parsing git file header: %v", err) 281 - } 282 - 283 - if test.Output != nil && !reflect.DeepEqual(f, *test.Output) { 284 - t.Errorf("incorrect file\nexpected: %+v\n actual: %+v", *test.Output, f) 285 - } 286 - }) 287 - } 288 - } 289 - 290 - func TestParseTraditionalFileHeader(t *testing.T) { 291 - tests := map[string]struct { 292 - OldLine string 293 - NewLine string 294 - Output *File 295 - Err bool 296 - }{ 297 - "fileContentChange": { 298 - OldLine: "--- dir/file_old.txt\t2019-03-21 23:00:00.0 -0700\n", 299 - NewLine: "+++ dir/file_new.txt\t2019-03-21 23:30:00.0 -0700\n", 300 - Output: &File{ 301 - OldName: "dir/file_new.txt", 302 - NewName: "dir/file_new.txt", 303 - }, 304 - }, 305 - "newFile": { 306 - OldLine: "--- /dev/null\t1969-12-31 17:00:00.0 -0700\n", 307 - NewLine: "+++ dir/file.txt\t2019-03-21 23:30:00.0 -0700\n", 308 - Output: &File{ 309 - NewName: "dir/file.txt", 310 - IsNew: true, 311 - }, 312 - }, 313 - "newFileTimestamp": { 314 - OldLine: "--- dir/file.txt\t1969-12-31 17:00:00.0 -0700\n", 315 - NewLine: "+++ dir/file.txt\t2019-03-21 23:30:00.0 -0700\n", 316 - Output: &File{ 317 - NewName: "dir/file.txt", 318 - IsNew: true, 319 - }, 320 - }, 321 - "deleteFile": { 322 - OldLine: "--- dir/file.txt\t2019-03-21 23:30:00.0 -0700\n", 323 - NewLine: "+++ /dev/null\t1969-12-31 17:00:00.0 -0700\n", 324 - Output: &File{ 325 - OldName: "dir/file.txt", 326 - IsDelete: true, 327 - }, 328 - }, 329 - "deleteFileTimestamp": { 330 - OldLine: "--- dir/file.txt\t2019-03-21 23:30:00.0 -0700\n", 331 - NewLine: "+++ dir/file.txt\t1969-12-31 17:00:00.0 -0700\n", 332 - Output: &File{ 333 - OldName: "dir/file.txt", 334 - IsDelete: true, 335 - }, 336 - }, 337 - "useShortestPrefixName": { 338 - OldLine: "--- dir/file.txt\t2019-03-21 23:00:00.0 -0700\n", 339 - NewLine: "+++ dir/file.txt~\t2019-03-21 23:30:00.0 -0700\n", 340 - Output: &File{ 341 - OldName: "dir/file.txt", 342 - NewName: "dir/file.txt", 343 - }, 344 - }, 345 - } 346 - 347 - for name, test := range tests { 348 - t.Run(name, func(t *testing.T) { 349 - p := &parser{r: bufio.NewReader(strings.NewReader(""))} 350 - 351 - var f File 352 - err := p.ParseTraditionalFileHeader(&f, test.OldLine, test.NewLine) 353 - if test.Err { 354 - if err == nil { 355 - t.Fatalf("expected error parsing traditional file header, got nil") 356 - } 357 - return 358 - } 359 - if err != nil { 360 - t.Fatalf("unexpected error parsing traditional file header: %v", err) 361 - } 362 - 363 - if test.Output != nil && !reflect.DeepEqual(f, *test.Output) { 364 - t.Errorf("incorrect file\nexpected: %+v\n actual: %+v", *test.Output, f) 365 - } 366 - }) 367 - } 368 - }