History

Move git diff codes from models to services/gitdiff (#7889)

* move git diff codes from models to services/gitdiff * fix template * fix test * fix template

Lunny Xiao committed 7 years ago

Commit c03d75f1 parent b660a7315 files · +164 −111Browse files

Showing the changes to services/gitdiff/gitdiff.go (1 of 15 files). Show all

Unified · Split

services/gitdiff/gitdiff.go+828−0
@@ -0,0 +1,828 @@
1// Copyright 2014 The Gogs Authors. All rights reserved.
2// Copyright 2019 The Gitea Authors. All rights reserved.
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file.
5
6package gitdiff
7
8import (
9 "bufio"
10 "bytes"
11 "fmt"
12 "html"
13 "html/template"
14 "io"
15 "io/ioutil"
16 "os"
17 "os/exec"
18 "regexp"
19 "sort"
20 "strconv"
21 "strings"
22
23 "code.gitea.io/gitea/models"
24 "code.gitea.io/gitea/modules/charset"
25 "code.gitea.io/gitea/modules/git"
26 "code.gitea.io/gitea/modules/highlight"
27 "code.gitea.io/gitea/modules/log"
28 "code.gitea.io/gitea/modules/process"
29 "code.gitea.io/gitea/modules/setting"
30
31 "github.com/sergi/go-diff/diffmatchpatch"
32 "github.com/unknwon/com"
33 stdcharset "golang.org/x/net/html/charset"
34 "golang.org/x/text/transform"
35)
36
37// DiffLineType represents the type of a DiffLine.
38type DiffLineType uint8
39
40// DiffLineType possible values.
41const (
42 DiffLinePlain DiffLineType = iota + 1
43 DiffLineAdd
44 DiffLineDel
45 DiffLineSection
46)
47
48// DiffFileType represents the type of a DiffFile.
49type DiffFileType uint8
50
51// DiffFileType possible values.
52const (
53 DiffFileAdd DiffFileType = iota + 1
54 DiffFileChange
55 DiffFileDel
56 DiffFileRename
57)
58
59// DiffLine represents a line difference in a DiffSection.
60type DiffLine struct {
61 LeftIdx int
62 RightIdx int
63 Type DiffLineType
64 Content string
65 Comments []*models.Comment
66}
67
68// GetType returns the type of a DiffLine.
69func (d *DiffLine) GetType() int {
70 return int(d.Type)
71}
72
73// CanComment returns whether or not a line can get commented
74func (d *DiffLine) CanComment() bool {
75 return len(d.Comments) == 0 && d.Type != DiffLineSection
76}
77
78// GetCommentSide returns the comment side of the first comment, if not set returns empty string
79func (d *DiffLine) GetCommentSide() string {
80 if len(d.Comments) == 0 {
81 return ""
82 }
83 return d.Comments[0].DiffSide()
84}
85
86// GetLineTypeMarker returns the line type marker
87func (d *DiffLine) GetLineTypeMarker() string {
88 if strings.IndexByte(" +-", d.Content[0]) > -1 {
89 return d.Content[0:1]
90 }
91 return ""
92}
93
94// escape a line's content or return <br> needed for copy/paste purposes
95func getLineContent(content string) string {
96 if len(content) > 0 {
97 return html.EscapeString(content)
98 }
99 return "<br>"
100}
101
102// DiffSection represents a section of a DiffFile.
103type DiffSection struct {
104 Name string
105 Lines []*DiffLine
106}
107
108var (
109 addedCodePrefix = []byte(`<span class="added-code">`)
110 removedCodePrefix = []byte(`<span class="removed-code">`)
111 codeTagSuffix = []byte(`</span>`)
112)
113
114func diffToHTML(diffs []diffmatchpatch.Diff, lineType DiffLineType) template.HTML {
115 buf := bytes.NewBuffer(nil)
116
117 for i := range diffs {
118 switch {
119 case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == DiffLineAdd:
120 buf.Write(addedCodePrefix)
121 buf.WriteString(getLineContent(diffs[i].Text))
122 buf.Write(codeTagSuffix)
123 case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == DiffLineDel:
124 buf.Write(removedCodePrefix)
125 buf.WriteString(getLineContent(diffs[i].Text))
126 buf.Write(codeTagSuffix)
127 case diffs[i].Type == diffmatchpatch.DiffEqual:
128 buf.WriteString(getLineContent(diffs[i].Text))
129 }
130 }
131
132 return template.HTML(buf.Bytes())
133}
134
135// GetLine gets a specific line by type (add or del) and file line number
136func (diffSection *DiffSection) GetLine(lineType DiffLineType, idx int) *DiffLine {
137 var (
138 difference = 0
139 addCount = 0
140 delCount = 0
141 matchDiffLine *DiffLine
142 )
143
144LOOP:
145 for _, diffLine := range diffSection.Lines {
146 switch diffLine.Type {
147 case DiffLineAdd:
148 addCount++
149 case DiffLineDel:
150 delCount++
151 default:
152 if matchDiffLine != nil {
153 break LOOP
154 }
155 difference = diffLine.RightIdx - diffLine.LeftIdx
156 addCount = 0
157 delCount = 0
158 }
159
160 switch lineType {
161 case DiffLineDel:
162 if diffLine.RightIdx == 0 && diffLine.LeftIdx == idx-difference {
163 matchDiffLine = diffLine
164 }
165 case DiffLineAdd:
166 if diffLine.LeftIdx == 0 && diffLine.RightIdx == idx+difference {
167 matchDiffLine = diffLine
168 }
169 }
170 }
171
172 if addCount == delCount {
173 return matchDiffLine
174 }
175 return nil
176}
177
178var diffMatchPatch = diffmatchpatch.New()
179
180func init() {
181 diffMatchPatch.DiffEditCost = 100
182}
183
184// GetComputedInlineDiffFor computes inline diff for the given line.
185func (diffSection *DiffSection) GetComputedInlineDiffFor(diffLine *DiffLine) template.HTML {
186 if setting.Git.DisableDiffHighlight {
187 return template.HTML(getLineContent(diffLine.Content[1:]))
188 }
189 var (
190 compareDiffLine *DiffLine
191 diff1 string
192 diff2 string
193 )
194
195 // try to find equivalent diff line. ignore, otherwise
196 switch diffLine.Type {
197 case DiffLineAdd:
198 compareDiffLine = diffSection.GetLine(DiffLineDel, diffLine.RightIdx)
199 if compareDiffLine == nil {
200 return template.HTML(getLineContent(diffLine.Content[1:]))
201 }
202 diff1 = compareDiffLine.Content
203 diff2 = diffLine.Content
204 case DiffLineDel:
205 compareDiffLine = diffSection.GetLine(DiffLineAdd, diffLine.LeftIdx)
206 if compareDiffLine == nil {
207 return template.HTML(getLineContent(diffLine.Content[1:]))
208 }
209 diff1 = diffLine.Content
210 diff2 = compareDiffLine.Content
211 default:
212 if strings.IndexByte(" +-", diffLine.Content[0]) > -1 {
213 return template.HTML(getLineContent(diffLine.Content[1:]))
214 }
215 return template.HTML(getLineContent(diffLine.Content))
216 }
217
218 diffRecord := diffMatchPatch.DiffMain(diff1[1:], diff2[1:], true)
219 diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord)
220
221 return diffToHTML(diffRecord, diffLine.Type)
222}
223
224// DiffFile represents a file diff.
225type DiffFile struct {
226 Name string
227 OldName string
228 Index int
229 Addition, Deletion int
230 Type DiffFileType
231 IsCreated bool
232 IsDeleted bool
233 IsBin bool
234 IsLFSFile bool
235 IsRenamed bool
236 IsSubmodule bool
237 Sections []*DiffSection
238 IsIncomplete bool
239}
240
241// GetType returns type of diff file.
242func (diffFile *DiffFile) GetType() int {
243 return int(diffFile.Type)
244}
245
246// GetHighlightClass returns highlight class for a filename.
247func (diffFile *DiffFile) GetHighlightClass() string {
248 return highlight.FileNameToHighlightClass(diffFile.Name)
249}
250
251// Diff represents a difference between two git trees.
252type Diff struct {
253 TotalAddition, TotalDeletion int
254 Files []*DiffFile
255 IsIncomplete bool
256}
257
258// LoadComments loads comments into each line
259func (diff *Diff) LoadComments(issue *models.Issue, currentUser *models.User) error {
260 allComments, err := models.FetchCodeComments(issue, currentUser)
261 if err != nil {
262 return err
263 }
264 for _, file := range diff.Files {
265 if lineCommits, ok := allComments[file.Name]; ok {
266 for _, section := range file.Sections {
267 for _, line := range section.Lines {
268 if comments, ok := lineCommits[int64(line.LeftIdx*-1)]; ok {
269 line.Comments = append(line.Comments, comments...)
270 }
271 if comments, ok := lineCommits[int64(line.RightIdx)]; ok {
272 line.Comments = append(line.Comments, comments...)
273 }
274 sort.SliceStable(line.Comments, func(i, j int) bool {
275 return line.Comments[i].CreatedUnix < line.Comments[j].CreatedUnix
276 })
277 }
278 }
279 }
280 }
281 return nil
282}
283
284// NumFiles returns number of files changes in a diff.
285func (diff *Diff) NumFiles() int {
286 return len(diff.Files)
287}
288
289// Example: @@ -1,8 +1,9 @@ => [..., 1, 8, 1, 9]
290var hunkRegex = regexp.MustCompile(`^@@ -(?P<beginOld>[0-9]+)(,(?P<endOld>[0-9]+))? \+(?P<beginNew>[0-9]+)(,(?P<endNew>[0-9]+))? @@`)
291
292func isHeader(lof string) bool {
293 return strings.HasPrefix(lof, cmdDiffHead) || strings.HasPrefix(lof, "---") || strings.HasPrefix(lof, "+++")
294}
295
296// CutDiffAroundLine cuts a diff of a file in way that only the given line + numberOfLine above it will be shown
297// it also recalculates hunks and adds the appropriate headers to the new diff.
298// Warning: Only one-file diffs are allowed.
299func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLine int) string {
300 if line == 0 || numbersOfLine == 0 {
301 // no line or num of lines => no diff
302 return ""
303 }
304 scanner := bufio.NewScanner(originalDiff)
305 hunk := make([]string, 0)
306 // begin is the start of the hunk containing searched line
307 // end is the end of the hunk ...
308 // currentLine is the line number on the side of the searched line (differentiated by old)
309 // otherLine is the line number on the opposite side of the searched line (differentiated by old)
310 var begin, end, currentLine, otherLine int64
311 var headerLines int
312 for scanner.Scan() {
313 lof := scanner.Text()
314 // Add header to enable parsing
315 if isHeader(lof) {
316 hunk = append(hunk, lof)
317 headerLines++
318 }
319 if currentLine > line {
320 break
321 }
322 // Detect "hunk" with contains commented lof
323 if strings.HasPrefix(lof, "@@") {
324 // Already got our hunk. End of hunk detected!
325 if len(hunk) > headerLines {
326 break
327 }
328 // A map with named groups of our regex to recognize them later more easily
329 submatches := hunkRegex.FindStringSubmatch(lof)
330 groups := make(map[string]string)
331 for i, name := range hunkRegex.SubexpNames() {
332 if i != 0 && name != "" {
333 groups[name] = submatches[i]
334 }
335 }
336 if old {
337 begin = com.StrTo(groups["beginOld"]).MustInt64()
338 end = com.StrTo(groups["endOld"]).MustInt64()
339 // init otherLine with begin of opposite side
340 otherLine = com.StrTo(groups["beginNew"]).MustInt64()
341 } else {
342 begin = com.StrTo(groups["beginNew"]).MustInt64()
343 if groups["endNew"] != "" {
344 end = com.StrTo(groups["endNew"]).MustInt64()
345 } else {
346 end = 0
347 }
348 // init otherLine with begin of opposite side
349 otherLine = com.StrTo(groups["beginOld"]).MustInt64()
350 }
351 end += begin // end is for real only the number of lines in hunk
352 // lof is between begin and end
353 if begin <= line && end >= line {
354 hunk = append(hunk, lof)
355 currentLine = begin
356 continue
357 }
358 } else if len(hunk) > headerLines {
359 hunk = append(hunk, lof)
360 // Count lines in context
361 switch lof[0] {
362 case '+':
363 if !old {
364 currentLine++
365 } else {
366 otherLine++
367 }
368 case '-':
369 if old {
370 currentLine++
371 } else {
372 otherLine++
373 }
374 default:
375 currentLine++
376 otherLine++
377 }
378 }
379 }
380
381 // No hunk found
382 if currentLine == 0 {
383 return ""
384 }
385 // headerLines + hunkLine (1) = totalNonCodeLines
386 if len(hunk)-headerLines-1 <= numbersOfLine {
387 // No need to cut the hunk => return existing hunk
388 return strings.Join(hunk, "\n")
389 }
390 var oldBegin, oldNumOfLines, newBegin, newNumOfLines int64
391 if old {
392 oldBegin = currentLine
393 newBegin = otherLine
394 } else {
395 oldBegin = otherLine
396 newBegin = currentLine
397 }
398 // headers + hunk header
399 newHunk := make([]string, headerLines)
400 // transfer existing headers
401 copy(newHunk, hunk[:headerLines])
402 // transfer last n lines
403 newHunk = append(newHunk, hunk[len(hunk)-numbersOfLine-1:]...)
404 // calculate newBegin, ... by counting lines
405 for i := len(hunk) - 1; i >= len(hunk)-numbersOfLine; i-- {
406 switch hunk[i][0] {
407 case '+':
408 newBegin--
409 newNumOfLines++
410 case '-':
411 oldBegin--
412 oldNumOfLines++
413 default:
414 oldBegin--
415 newBegin--
416 newNumOfLines++
417 oldNumOfLines++
418 }
419 }
420 // construct the new hunk header
421 newHunk[headerLines] = fmt.Sprintf("@@ -%d,%d +%d,%d @@",
422 oldBegin, oldNumOfLines, newBegin, newNumOfLines)
423 return strings.Join(newHunk, "\n")
424}
425
426const cmdDiffHead = "diff --git "
427
428// ParsePatch builds a Diff object from a io.Reader and some
429// parameters.
430// TODO: move this function to gogits/git-module
431func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*Diff, error) {
432 var (
433 diff = &Diff{Files: make([]*DiffFile, 0)}
434
435 curFile = &DiffFile{}
436 curSection = &DiffSection{
437 Lines: make([]*DiffLine, 0, 10),
438 }
439
440 leftLine, rightLine int
441 lineCount int
442 curFileLinesCount int
443 curFileLFSPrefix bool
444 )
445
446 input := bufio.NewReader(reader)
447 isEOF := false
448 for !isEOF {
449 var linebuf bytes.Buffer
450 for {
451 b, err := input.ReadByte()
452 if err != nil {
453 if err == io.EOF {
454 isEOF = true
455 break
456 } else {
457 return nil, fmt.Errorf("ReadByte: %v", err)
458 }
459 }
460 if b == '\n' {
461 break
462 }
463 if linebuf.Len() < maxLineCharacters {
464 linebuf.WriteByte(b)
465 } else if linebuf.Len() == maxLineCharacters {
466 curFile.IsIncomplete = true
467 }
468 }
469 line := linebuf.String()
470
471 if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") || len(line) == 0 {
472 continue
473 }
474
475 trimLine := strings.Trim(line, "+- ")
476
477 if trimLine == models.LFSMetaFileIdentifier {
478 curFileLFSPrefix = true
479 }
480
481 if curFileLFSPrefix && strings.HasPrefix(trimLine, models.LFSMetaFileOidPrefix) {
482 oid := strings.TrimPrefix(trimLine, models.LFSMetaFileOidPrefix)
483
484 if len(oid) == 64 {
485 m := &models.LFSMetaObject{Oid: oid}
486 count, err := models.Count(m)
487
488 if err == nil && count > 0 {
489 curFile.IsBin = true
490 curFile.IsLFSFile = true
491 curSection.Lines = nil
492 }
493 }
494 }
495
496 curFileLinesCount++
497 lineCount++
498
499 // Diff data too large, we only show the first about maxLines lines
500 if curFileLinesCount >= maxLines {
501 curFile.IsIncomplete = true
502 }
503 switch {
504 case line[0] == ' ':
505 diffLine := &DiffLine{Type: DiffLinePlain, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
506 leftLine++
507 rightLine++
508 curSection.Lines = append(curSection.Lines, diffLine)
509 continue
510 case line[0] == '@':
511 curSection = &DiffSection{}
512 curFile.Sections = append(curFile.Sections, curSection)
513 ss := strings.Split(line, "@@")
514 diffLine := &DiffLine{Type: DiffLineSection, Content: line}
515 curSection.Lines = append(curSection.Lines, diffLine)
516
517 // Parse line number.
518 ranges := strings.Split(ss[1][1:], " ")
519 leftLine, _ = com.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
520 if len(ranges) > 1 {
521 rightLine, _ = com.StrTo(strings.Split(ranges[1], ",")[0]).Int()
522 } else {
523 log.Warn("Parse line number failed: %v", line)
524 rightLine = leftLine
525 }
526 continue
527 case line[0] == '+':
528 curFile.Addition++
529 diff.TotalAddition++
530 diffLine := &DiffLine{Type: DiffLineAdd, Content: line, RightIdx: rightLine}
531 rightLine++
532 curSection.Lines = append(curSection.Lines, diffLine)
533 continue
534 case line[0] == '-':
535 curFile.Deletion++
536 diff.TotalDeletion++
537 diffLine := &DiffLine{Type: DiffLineDel, Content: line, LeftIdx: leftLine}
538 if leftLine > 0 {
539 leftLine++
540 }
541 curSection.Lines = append(curSection.Lines, diffLine)
542 case strings.HasPrefix(line, "Binary"):
543 curFile.IsBin = true
544 continue
545 }
546
547 // Get new file.
548 if strings.HasPrefix(line, cmdDiffHead) {
549 var middle int
550
551 // Note: In case file name is surrounded by double quotes (it happens only in git-shell).
552 // e.g. diff --git "a/xxx" "b/xxx"
553 hasQuote := line[len(cmdDiffHead)] == '"'
554 if hasQuote {
555 middle = strings.Index(line, ` "b/`)
556 } else {
557 middle = strings.Index(line, " b/")
558 }
559
560 beg := len(cmdDiffHead)
561 a := line[beg+2 : middle]
562 b := line[middle+3:]
563
564 if hasQuote {
565 // Keep the entire string in double quotes for now
566 a = line[beg:middle]
567 b = line[middle+1:]
568
569 var err error
570 a, err = strconv.Unquote(a)
571 if err != nil {
572 return nil, fmt.Errorf("Unquote: %v", err)
573 }
574 b, err = strconv.Unquote(b)
575 if err != nil {
576 return nil, fmt.Errorf("Unquote: %v", err)
577 }
578 // Now remove the /a /b
579 a = a[2:]
580 b = b[2:]
581
582 }
583
584 curFile = &DiffFile{
585 Name: b,
586 OldName: a,
587 Index: len(diff.Files) + 1,
588 Type: DiffFileChange,
589 Sections: make([]*DiffSection, 0, 10),
590 IsRenamed: a != b,
591 }
592 diff.Files = append(diff.Files, curFile)
593 if len(diff.Files) >= maxFiles {
594 diff.IsIncomplete = true
595 _, err := io.Copy(ioutil.Discard, reader)
596 if err != nil {
597 return nil, fmt.Errorf("Copy: %v", err)
598 }
599 break
600 }
601 curFileLinesCount = 0
602 curFileLFSPrefix = false
603
604 // Check file diff type and is submodule.
605 for {
606 line, err := input.ReadString('\n')
607 if err != nil {
608 if err == io.EOF {
609 isEOF = true
610 } else {
611 return nil, fmt.Errorf("ReadString: %v", err)
612 }
613 }
614
615 switch {
616 case strings.HasPrefix(line, "new file"):
617 curFile.Type = DiffFileAdd
618 curFile.IsCreated = true
619 case strings.HasPrefix(line, "deleted"):
620 curFile.Type = DiffFileDel
621 curFile.IsDeleted = true
622 case strings.HasPrefix(line, "index"):
623 curFile.Type = DiffFileChange
624 case strings.HasPrefix(line, "similarity index 100%"):
625 curFile.Type = DiffFileRename
626 }
627 if curFile.Type > 0 {
628 if strings.HasSuffix(line, " 160000\n") {
629 curFile.IsSubmodule = true
630 }
631 break
632 }
633 }
634 }
635 }
636
637 // FIXME: detect encoding while parsing.
638 var buf bytes.Buffer
639 for _, f := range diff.Files {
640 buf.Reset()
641 for _, sec := range f.Sections {
642 for _, l := range sec.Lines {
643 buf.WriteString(l.Content)
644 buf.WriteString("\n")
645 }
646 }
647 charsetLabel, err := charset.DetectEncoding(buf.Bytes())
648 if charsetLabel != "UTF-8" && err == nil {
649 encoding, _ := stdcharset.Lookup(charsetLabel)
650 if encoding != nil {
651 d := encoding.NewDecoder()
652 for _, sec := range f.Sections {
653 for _, l := range sec.Lines {
654 if c, _, err := transform.String(d, l.Content); err == nil {
655 l.Content = c
656 }
657 }
658 }
659 }
660 }
661 }
662
663 return diff, nil
664}
665
666// GetDiffRange builds a Diff between two commits of a repository.
667// passing the empty string as beforeCommitID returns a diff from the
668// parent commit.
669func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacters, maxFiles int) (*Diff, error) {
670 return GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID, maxLines, maxLineCharacters, maxFiles, "")
671}
672
673// GetDiffRangeWithWhitespaceBehavior builds a Diff between two commits of a repository.
674// Passing the empty string as beforeCommitID returns a diff from the parent commit.
675// The whitespaceBehavior is either an empty string or a git flag
676func GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacters, maxFiles int, whitespaceBehavior string) (*Diff, error) {
677 gitRepo, err := git.OpenRepository(repoPath)
678 if err != nil {
679 return nil, err
680 }
681
682 commit, err := gitRepo.GetCommit(afterCommitID)
683 if err != nil {
684 return nil, err
685 }
686
687 var cmd *exec.Cmd
688 if len(beforeCommitID) == 0 && commit.ParentCount() == 0 {
689 cmd = exec.Command(git.GitExecutable, "show", afterCommitID)
690 } else {
691 actualBeforeCommitID := beforeCommitID
692 if len(actualBeforeCommitID) == 0 {
693 parentCommit, _ := commit.Parent(0)
694 actualBeforeCommitID = parentCommit.ID.String()
695 }
696 diffArgs := []string{"diff", "-M"}
697 if len(whitespaceBehavior) != 0 {
698 diffArgs = append(diffArgs, whitespaceBehavior)
699 }
700 diffArgs = append(diffArgs, actualBeforeCommitID)
701 diffArgs = append(diffArgs, afterCommitID)
702 cmd = exec.Command(git.GitExecutable, diffArgs...)
703 }
704 cmd.Dir = repoPath
705 cmd.Stderr = os.Stderr
706
707 stdout, err := cmd.StdoutPipe()
708 if err != nil {
709 return nil, fmt.Errorf("StdoutPipe: %v", err)
710 }
711
712 if err = cmd.Start(); err != nil {
713 return nil, fmt.Errorf("Start: %v", err)
714 }
715
716 pid := process.GetManager().Add(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath), cmd)
717 defer process.GetManager().Remove(pid)
718
719 diff, err := ParsePatch(maxLines, maxLineCharacters, maxFiles, stdout)
720 if err != nil {
721 return nil, fmt.Errorf("ParsePatch: %v", err)
722 }
723
724 if err = cmd.Wait(); err != nil {
725 return nil, fmt.Errorf("Wait: %v", err)
726 }
727
728 return diff, nil
729}
730
731// RawDiffType type of a raw diff.
732type RawDiffType string
733
734// RawDiffType possible values.
735const (
736 RawDiffNormal RawDiffType = "diff"
737 RawDiffPatch RawDiffType = "patch"
738)
739
740// GetRawDiff dumps diff results of repository in given commit ID to io.Writer.
741// TODO: move this function to gogits/git-module
742func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Writer) error {
743 return GetRawDiffForFile(repoPath, "", commitID, diffType, "", writer)
744}
745
746// GetRawDiffForFile dumps diff results of file in given commit ID to io.Writer.
747// TODO: move this function to gogits/git-module
748func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error {
749 repo, err := git.OpenRepository(repoPath)
750 if err != nil {
751 return fmt.Errorf("OpenRepository: %v", err)
752 }
753
754 commit, err := repo.GetCommit(endCommit)
755 if err != nil {
756 return fmt.Errorf("GetCommit: %v", err)
757 }
758 fileArgs := make([]string, 0)
759 if len(file) > 0 {
760 fileArgs = append(fileArgs, "--", file)
761 }
762 var cmd *exec.Cmd
763 switch diffType {
764 case RawDiffNormal:
765 if len(startCommit) != 0 {
766 cmd = exec.Command(git.GitExecutable, append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...)
767 } else if commit.ParentCount() == 0 {
768 cmd = exec.Command(git.GitExecutable, append([]string{"show", endCommit}, fileArgs...)...)
769 } else {
770 c, _ := commit.Parent(0)
771 cmd = exec.Command(git.GitExecutable, append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...)
772 }
773 case RawDiffPatch:
774 if len(startCommit) != 0 {
775 query := fmt.Sprintf("%s...%s", endCommit, startCommit)
776 cmd = exec.Command(git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...)
777 } else if commit.ParentCount() == 0 {
778 cmd = exec.Command(git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...)
779 } else {
780 c, _ := commit.Parent(0)
781 query := fmt.Sprintf("%s...%s", endCommit, c.ID.String())
782 cmd = exec.Command(git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...)
783 }
784 default:
785 return fmt.Errorf("invalid diffType: %s", diffType)
786 }
787
788 stderr := new(bytes.Buffer)
789
790 cmd.Dir = repoPath
791 cmd.Stdout = writer
792 cmd.Stderr = stderr
793 if err = cmd.Run(); err != nil {
794 return fmt.Errorf("Run: %v - %s", err, stderr)
795 }
796 return nil
797}
798
799// GetDiffCommit builds a Diff representing the given commitID.
800func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacters, maxFiles int) (*Diff, error) {
801 return GetDiffRange(repoPath, "", commitID, maxLines, maxLineCharacters, maxFiles)
802}
803
804// CommentAsDiff returns c.Patch as *Diff
805func CommentAsDiff(c *models.Comment) (*Diff, error) {
806 diff, err := ParsePatch(setting.Git.MaxGitDiffLines,
807 setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(c.Patch))
808 if err != nil {
809 return nil, err
810 }
811 if len(diff.Files) == 0 {
812 return nil, fmt.Errorf("no file found for comment ID: %d", c.ID)
813 }
814 secs := diff.Files[0].Sections
815 if len(secs) == 0 {
816 return nil, fmt.Errorf("no sections found for comment ID: %d", c.ID)
817 }
818 return diff, nil
819}
820
821// CommentMustAsDiff executes AsDiff and logs the error instead of returning
822func CommentMustAsDiff(c *models.Comment) *Diff {
823 diff, err := CommentAsDiff(c)
824 if err != nil {
825 log.Warn("CommentMustAsDiff: %v", err)
826 }
827 return diff
828}