123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- // Copyright 2014 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 models
- import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "os"
- "os/exec"
- "strings"
- "time"
- "golang.org/x/net/html/charset"
- "golang.org/x/text/transform"
- "github.com/Unknwon/com"
- "github.com/gogits/gogs/modules/base"
- "github.com/gogits/gogs/modules/git"
- "github.com/gogits/gogs/modules/log"
- "github.com/gogits/gogs/modules/process"
- )
- // Diff line types.
- const (
- DIFF_LINE_PLAIN = iota + 1
- DIFF_LINE_ADD
- DIFF_LINE_DEL
- DIFF_LINE_SECTION
- )
- const (
- DIFF_FILE_ADD = iota + 1
- DIFF_FILE_CHANGE
- DIFF_FILE_DEL
- DIFF_FILE_RENAME
- )
- type DiffLine struct {
- LeftIdx int
- RightIdx int
- Type int
- Content string
- }
- func (d DiffLine) GetType() int {
- return d.Type
- }
- type DiffSection struct {
- Name string
- Lines []*DiffLine
- }
- type DiffFile struct {
- Name string
- OldName string
- Index int
- Addition, Deletion int
- Type int
- IsCreated bool
- IsDeleted bool
- IsBin bool
- IsRenamed bool
- Sections []*DiffSection
- }
- type Diff struct {
- TotalAddition, TotalDeletion int
- Files []*DiffFile
- }
- func (diff *Diff) NumFiles() int {
- return len(diff.Files)
- }
- const DIFF_HEAD = "diff --git "
- func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
- scanner := bufio.NewScanner(reader)
- var (
- curFile *DiffFile
- curSection = &DiffSection{
- Lines: make([]*DiffLine, 0, 10),
- }
- leftLine, rightLine int
- // FIXME: Should use cache in the future.
- buf bytes.Buffer
- )
- diff := &Diff{Files: make([]*DiffFile, 0)}
- var i int
- for scanner.Scan() {
- line := scanner.Text()
- if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
- continue
- }
- if line == "" {
- continue
- }
- i = i + 1
- // Diff data too large, we only show the first about maxlines lines
- if i >= maxlines {
- log.Warn("Diff data too large")
- diff.Files = nil
- return diff, nil
- }
- 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, _ = com.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
- if len(ranges) > 1 {
- rightLine, _ = com.StrTo(strings.Split(ranges[1], ",")[0]).Int()
- } else {
- log.Warn("Parse line number failed: %v", line)
- 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).
- hasQuote := strings.Index(line, `\"`) > -1
- if hasQuote {
- line = strings.Replace(line, `\"`, `"`, -1)
- 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 = a[1 : len(a)-1]
- b = b[1 : len(b)-1]
- }
- curFile = &DiffFile{
- Name: a,
- Index: len(diff.Files) + 1,
- Type: DIFF_FILE_CHANGE,
- Sections: make([]*DiffSection, 0, 10),
- }
- diff.Files = append(diff.Files, curFile)
- // Check file diff type.
- for scanner.Scan() {
- switch {
- case strings.HasPrefix(scanner.Text(), "new file"):
- curFile.Type = DIFF_FILE_ADD
- curFile.IsCreated = true
- case strings.HasPrefix(scanner.Text(), "deleted"):
- curFile.Type = DIFF_FILE_DEL
- curFile.IsDeleted = true
- case strings.HasPrefix(scanner.Text(), "index"):
- curFile.Type = DIFF_FILE_CHANGE
- case strings.HasPrefix(scanner.Text(), "similarity index 100%"):
- curFile.Type = DIFF_FILE_RENAME
- curFile.IsRenamed = true
- curFile.OldName = curFile.Name
- curFile.Name = b
- }
- if curFile.Type > 0 {
- break
- }
- }
- }
- }
- for _, f := range diff.Files {
- buf.Reset()
- for _, sec := range f.Sections {
- for _, l := range sec.Lines {
- buf.WriteString(l.Content)
- buf.WriteString("\n")
- }
- }
- charsetLabel, err := base.DetectEncoding(buf.Bytes())
- if charsetLabel != "UTF-8" && err == nil {
- encoding, _ := charset.Lookup(charsetLabel)
- if encoding != nil {
- d := encoding.NewDecoder()
- for _, sec := range f.Sections {
- for _, l := range sec.Lines {
- if c, _, err := transform.String(d, l.Content); err == nil {
- l.Content = c
- }
- }
- }
- }
- }
- }
- return diff, nil
- }
- func GetDiffRange(repoPath, beforeCommitId string, afterCommitId string, maxlines int) (*Diff, error) {
- repo, err := git.OpenRepository(repoPath)
- if err != nil {
- return nil, err
- }
- commit, err := repo.GetCommit(afterCommitId)
- if err != nil {
- return nil, err
- }
- rd, wr := io.Pipe()
- var cmd *exec.Cmd
- // if "after" commit given
- if beforeCommitId == "" {
- // First commit of repository.
- if commit.ParentCount() == 0 {
- cmd = exec.Command("git", "show", afterCommitId)
- } else {
- c, _ := commit.Parent(0)
- cmd = exec.Command("git", "diff", "-M", c.ID.String(), afterCommitId)
- }
- } else {
- cmd = exec.Command("git", "diff", "-M", beforeCommitId, afterCommitId)
- }
- cmd.Dir = repoPath
- cmd.Stdout = wr
- cmd.Stdin = os.Stdin
- cmd.Stderr = os.Stderr
- done := make(chan error)
- go func() {
- cmd.Start()
- done <- cmd.Wait()
- wr.Close()
- }()
- defer rd.Close()
- desc := fmt.Sprintf("GetDiffRange(%s)", repoPath)
- pid := process.Add(desc, cmd)
- go func() {
- // In case process became zombie.
- select {
- case <-time.After(5 * time.Minute):
- if errKill := process.Kill(pid); errKill != nil {
- log.Error(4, "git_diff.ParsePatch(Kill): %v", err)
- }
- <-done
- // return "", ErrExecTimeout.Error(), ErrExecTimeout
- case err = <-done:
- process.Remove(pid)
- }
- }()
- return ParsePatch(pid, maxlines, cmd, rd)
- }
- func GetDiffCommit(repoPath, commitId string, maxlines int) (*Diff, error) {
- return GetDiffRange(repoPath, "", commitId, maxlines)
- }
|