aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/gogs/git-module/repo_diff.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/gogs/git-module/repo_diff.go')
-rw-r--r--vendor/github.com/gogs/git-module/repo_diff.go400
1 files changed, 400 insertions, 0 deletions
diff --git a/vendor/github.com/gogs/git-module/repo_diff.go b/vendor/github.com/gogs/git-module/repo_diff.go
new file mode 100644
index 00000000..258a166f
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/repo_diff.go
@@ -0,0 +1,400 @@
+// Copyright 2017 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package git
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// DiffLineType represents the type of a line in diff.
+type DiffLineType uint8
+
+const (
+ DIFF_LINE_PLAIN DiffLineType = iota + 1
+ DIFF_LINE_ADD
+ DIFF_LINE_DEL
+ DIFF_LINE_SECTION
+)
+
+// DiffFileType represents the file status in diff.
+type DiffFileType uint8
+
+const (
+ DIFF_FILE_ADD DiffFileType = iota + 1
+ DIFF_FILE_CHANGE
+ DIFF_FILE_DEL
+ DIFF_FILE_RENAME
+)
+
+// DiffLine represents a line in diff.
+type DiffLine struct {
+ LeftIdx int
+ RightIdx int
+ Type DiffLineType
+ Content string
+}
+
+func (d *DiffLine) GetType() int {
+ return int(d.Type)
+}
+
+// DiffSection represents a section in diff.
+type DiffSection struct {
+ Name string
+ Lines []*DiffLine
+}
+
+// Line returns a specific line by type (add or del) and file line number from a section.
+func (diffSection *DiffSection) Line(lineType DiffLineType, idx int) *DiffLine {
+ var (
+ difference = 0
+ addCount = 0
+ delCount = 0
+ matchDiffLine *DiffLine
+ )
+
+LOOP:
+ for _, diffLine := range diffSection.Lines {
+ switch diffLine.Type {
+ case DIFF_LINE_ADD:
+ addCount++
+ case DIFF_LINE_DEL:
+ delCount++
+ default:
+ if matchDiffLine != nil {
+ break LOOP
+ }
+ difference = diffLine.RightIdx - diffLine.LeftIdx
+ addCount = 0
+ delCount = 0
+ }
+
+ switch lineType {
+ case DIFF_LINE_DEL:
+ if diffLine.RightIdx == 0 && diffLine.LeftIdx == idx-difference {
+ matchDiffLine = diffLine
+ }
+ case DIFF_LINE_ADD:
+ if diffLine.LeftIdx == 0 && diffLine.RightIdx == idx+difference {
+ matchDiffLine = diffLine
+ }
+ }
+ }
+
+ if addCount == delCount {
+ return matchDiffLine
+ }
+ return nil
+}
+
+// DiffFile represents a file in diff.
+type DiffFile struct {
+ Name string
+ OldName string
+ Index string // 40-byte SHA, Changed/New: new SHA; Deleted: old SHA
+ Addition, Deletion int
+ Type DiffFileType
+ IsCreated bool
+ IsDeleted bool
+ IsBin bool
+ IsRenamed bool
+ IsSubmodule bool
+ Sections []*DiffSection
+ IsIncomplete bool
+}
+
+func (diffFile *DiffFile) GetType() int {
+ return int(diffFile.Type)
+}
+
+func (diffFile *DiffFile) NumSections() int {
+ return len(diffFile.Sections)
+}
+
+// Diff contains all information of a specific diff output.
+type Diff struct {
+ TotalAddition, TotalDeletion int
+ Files []*DiffFile
+ IsIncomplete bool
+}
+
+func (diff *Diff) NumFiles() int {
+ return len(diff.Files)
+}
+
+const _DIFF_HEAD = "diff --git "
+
+// ParsePatch takes a reader and parses everything it receives in diff format.
+func ParsePatch(done chan<- error, maxLines, maxLineCharacteres, maxFiles int, reader io.Reader) *Diff {
+ var (
+ diff = &Diff{Files: make([]*DiffFile, 0)}
+
+ curFile *DiffFile
+ curSection = &DiffSection{
+ Lines: make([]*DiffLine, 0, 10),
+ }
+
+ leftLine, rightLine int
+ lineCount int
+ curFileLinesCount int
+ )
+ input := bufio.NewReader(reader)
+ isEOF := false
+ for !isEOF {
+ // TODO: would input.ReadBytes be more memory-efficient?
+ line, err := input.ReadString('\n')
+ if err != nil {
+ if err == io.EOF {
+ isEOF = true
+ } else {
+ done <- fmt.Errorf("ReadString: %v", err)
+ return nil
+ }
+ }
+
+ if len(line) > 0 && line[len(line)-1] == '\n' {
+ // Remove line break.
+ line = line[:len(line)-1]
+ }
+
+ if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") || len(line) == 0 {
+ continue
+ }
+
+ curFileLinesCount++
+ lineCount++
+
+ // Diff data too large, we only show the first about maxlines lines
+ if curFileLinesCount >= maxLines || len(line) >= maxLineCharacteres {
+ curFile.IsIncomplete = true
+ }
+
+ switch {
+ case line[0] == ' ':
+ diffLine := &DiffLine{Type: DIFF_LINE_PLAIN, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
+ leftLine++
+ rightLine++
+ curSection.Lines = append(curSection.Lines, diffLine)
+ continue
+ case line[0] == '@':
+ curSection = &DiffSection{}
+ curFile.Sections = append(curFile.Sections, curSection)
+ ss := strings.Split(line, "@@")
+ diffLine := &DiffLine{Type: DIFF_LINE_SECTION, Content: line}
+ curSection.Lines = append(curSection.Lines, diffLine)
+
+ // Parse line number.
+ ranges := strings.Split(ss[1][1:], " ")
+ leftLine, _ = strconv.Atoi(strings.Split(ranges[0], ",")[0][1:])
+ if len(ranges) > 1 {
+ rightLine, _ = strconv.Atoi(strings.Split(ranges[1], ",")[0])
+ } else {
+ rightLine = leftLine
+ }
+ continue
+ case line[0] == '+':
+ curFile.Addition++
+ diff.TotalAddition++
+ diffLine := &DiffLine{Type: DIFF_LINE_ADD, Content: line, RightIdx: rightLine}
+ rightLine++
+ curSection.Lines = append(curSection.Lines, diffLine)
+ continue
+ case line[0] == '-':
+ curFile.Deletion++
+ diff.TotalDeletion++
+ diffLine := &DiffLine{Type: DIFF_LINE_DEL, Content: line, LeftIdx: leftLine}
+ if leftLine > 0 {
+ leftLine++
+ }
+ curSection.Lines = append(curSection.Lines, diffLine)
+ case strings.HasPrefix(line, "Binary"):
+ curFile.IsBin = true
+ continue
+ }
+
+ // Get new file.
+ if strings.HasPrefix(line, _DIFF_HEAD) {
+ middle := -1
+
+ // Note: In case file name is surrounded by double quotes (it happens only in git-shell).
+ // e.g. diff --git "a/xxx" "b/xxx"
+ hasQuote := line[len(_DIFF_HEAD)] == '"'
+ if hasQuote {
+ middle = strings.Index(line, ` "b/`)
+ } else {
+ middle = strings.Index(line, " b/")
+ }
+
+ beg := len(_DIFF_HEAD)
+ a := line[beg+2 : middle]
+ b := line[middle+3:]
+ if hasQuote {
+ a = string(UnescapeChars([]byte(a[1 : len(a)-1])))
+ b = string(UnescapeChars([]byte(b[1 : len(b)-1])))
+ }
+
+ curFile = &DiffFile{
+ Name: a,
+ Type: DIFF_FILE_CHANGE,
+ Sections: make([]*DiffSection, 0, 10),
+ }
+ diff.Files = append(diff.Files, curFile)
+ if len(diff.Files) >= maxFiles {
+ diff.IsIncomplete = true
+ io.Copy(ioutil.Discard, reader)
+ break
+ }
+ curFileLinesCount = 0
+
+ // Check file diff type and submodule.
+ CHECK_TYPE:
+ for {
+ line, err := input.ReadString('\n')
+ if err != nil {
+ if err == io.EOF {
+ isEOF = true
+ } else {
+ done <- fmt.Errorf("ReadString: %v", err)
+ return nil
+ }
+ }
+
+ switch {
+ case strings.HasPrefix(line, "new file"):
+ curFile.Type = DIFF_FILE_ADD
+ curFile.IsCreated = true
+ curFile.IsSubmodule = strings.HasSuffix(line, " 160000\n")
+ case strings.HasPrefix(line, "deleted"):
+ curFile.Type = DIFF_FILE_DEL
+ curFile.IsDeleted = true
+ curFile.IsSubmodule = strings.HasSuffix(line, " 160000\n")
+ case strings.HasPrefix(line, "index"):
+ if curFile.IsDeleted {
+ curFile.Index = line[6:46]
+ } else if len(line) >= 88 {
+ curFile.Index = line[49:88]
+ } else {
+ curFile.Index = curFile.Name
+ }
+ break CHECK_TYPE
+ case strings.HasPrefix(line, "similarity index 100%"):
+ curFile.Type = DIFF_FILE_RENAME
+ curFile.IsRenamed = true
+ curFile.OldName = curFile.Name
+ curFile.Name = b
+ curFile.Index = b
+ break CHECK_TYPE
+ case strings.HasPrefix(line, "old mode"):
+ break CHECK_TYPE
+ }
+ }
+ }
+ }
+
+ done <- nil
+ return diff
+}
+
+// GetDiffRange returns a parsed diff object between given commits.
+func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
+ repo, err := OpenRepository(repoPath)
+ if err != nil {
+ return nil, err
+ }
+
+ commit, err := repo.GetCommit(afterCommitID)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := NewCommand()
+ if len(beforeCommitID) == 0 {
+ // First commit of repository
+ if commit.ParentCount() == 0 {
+ cmd.AddArguments("show", "--full-index", afterCommitID)
+ } else {
+ c, _ := commit.Parent(0)
+ cmd.AddArguments("diff", "--full-index", "-M", c.ID.String(), afterCommitID)
+ }
+ } else {
+ cmd.AddArguments("diff", "--full-index", "-M", beforeCommitID, afterCommitID)
+ }
+
+ stdout, w := io.Pipe()
+ done := make(chan error)
+ var diff *Diff
+ go func() {
+ diff = ParsePatch(done, maxLines, maxLineCharacteres, maxFiles, stdout)
+ }()
+
+ stderr := new(bytes.Buffer)
+ err = cmd.RunInDirTimeoutPipeline(2*time.Minute, repoPath, w, stderr)
+ w.Close() // Close writer to exit parsing goroutine
+ if err != nil {
+ return nil, concatenateError(err, stderr.String())
+ }
+
+ return diff, <-done
+}
+
+// RawDiffType represents the type of raw diff format.
+type RawDiffType string
+
+const (
+ RAW_DIFF_NORMAL RawDiffType = "diff"
+ RAW_DIFF_PATCH RawDiffType = "patch"
+)
+
+// GetRawDiff dumps diff results of repository in given commit ID to io.Writer.
+func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Writer) error {
+ repo, err := OpenRepository(repoPath)
+ if err != nil {
+ return fmt.Errorf("OpenRepository: %v", err)
+ }
+
+ commit, err := repo.GetCommit(commitID)
+ if err != nil {
+ return err
+ }
+
+ cmd := NewCommand()
+ switch diffType {
+ case RAW_DIFF_NORMAL:
+ if commit.ParentCount() == 0 {
+ cmd.AddArguments("show", commitID)
+ } else {
+ c, _ := commit.Parent(0)
+ cmd.AddArguments("diff", "-M", c.ID.String(), commitID)
+ }
+ case RAW_DIFF_PATCH:
+ if commit.ParentCount() == 0 {
+ cmd.AddArguments("format-patch", "--no-signature", "--stdout", "--root", commitID)
+ } else {
+ c, _ := commit.Parent(0)
+ query := fmt.Sprintf("%s...%s", commitID, c.ID.String())
+ cmd.AddArguments("format-patch", "--no-signature", "--stdout", query)
+ }
+ default:
+ return fmt.Errorf("invalid diffType: %s", diffType)
+ }
+
+ stderr := new(bytes.Buffer)
+ if err = cmd.RunInDirPipeline(repoPath, writer, stderr); err != nil {
+ return concatenateError(err, stderr.String())
+ }
+ return nil
+}
+
+// GetDiffCommit returns a parsed diff object of given commit.
+func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
+ return GetDiffRange(repoPath, "", commitID, maxLines, maxLineCharacteres, maxFiles)
+}