You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

271 lines
6.0 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "bufio"
  7. "bytes"
  8. "fmt"
  9. "io"
  10. "os"
  11. "os/exec"
  12. "strings"
  13. "time"
  14. "github.com/Unknwon/com"
  15. "github.com/gogits/gogs/modules/base"
  16. "github.com/gogits/gogs/modules/git"
  17. "github.com/gogits/gogs/modules/log"
  18. "github.com/gogits/gogs/modules/mahonia"
  19. "github.com/gogits/gogs/modules/process"
  20. )
  21. // Diff line types.
  22. const (
  23. DIFF_LINE_PLAIN = iota + 1
  24. DIFF_LINE_ADD
  25. DIFF_LINE_DEL
  26. DIFF_LINE_SECTION
  27. )
  28. const (
  29. DIFF_FILE_ADD = iota + 1
  30. DIFF_FILE_CHANGE
  31. DIFF_FILE_DEL
  32. )
  33. type DiffLine struct {
  34. LeftIdx int
  35. RightIdx int
  36. Type int
  37. Content string
  38. }
  39. func (d DiffLine) GetType() int {
  40. return d.Type
  41. }
  42. type DiffSection struct {
  43. Name string
  44. Lines []*DiffLine
  45. }
  46. type DiffFile struct {
  47. Name string
  48. Index int
  49. Addition, Deletion int
  50. Type int
  51. IsBin bool
  52. Sections []*DiffSection
  53. }
  54. type Diff struct {
  55. TotalAddition, TotalDeletion int
  56. Files []*DiffFile
  57. }
  58. func (diff *Diff) NumFiles() int {
  59. return len(diff.Files)
  60. }
  61. const DIFF_HEAD = "diff --git "
  62. func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
  63. scanner := bufio.NewScanner(reader)
  64. var (
  65. curFile *DiffFile
  66. curSection = &DiffSection{
  67. Lines: make([]*DiffLine, 0, 10),
  68. }
  69. leftLine, rightLine int
  70. isTooLong bool
  71. // FIXME: use first 30 lines to detect file encoding. Should use cache in the future.
  72. buf bytes.Buffer
  73. )
  74. diff := &Diff{Files: make([]*DiffFile, 0)}
  75. var i int
  76. for scanner.Scan() {
  77. line := scanner.Text()
  78. // fmt.Println(i, line)
  79. if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
  80. continue
  81. }
  82. if line == "" {
  83. continue
  84. }
  85. i = i + 1
  86. // FIXME: use first 30 lines to detect file encoding.
  87. if i <= 30 {
  88. buf.WriteString(line)
  89. }
  90. // Diff data too large, we only show the first about maxlines lines
  91. if i == maxlines {
  92. isTooLong = true
  93. log.Warn("Diff data too large")
  94. //return &Diff{}, nil
  95. }
  96. switch {
  97. case line[0] == ' ':
  98. diffLine := &DiffLine{Type: DIFF_LINE_PLAIN, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
  99. leftLine++
  100. rightLine++
  101. curSection.Lines = append(curSection.Lines, diffLine)
  102. continue
  103. case line[0] == '@':
  104. if isTooLong {
  105. return diff, nil
  106. }
  107. curSection = &DiffSection{}
  108. curFile.Sections = append(curFile.Sections, curSection)
  109. ss := strings.Split(line, "@@")
  110. diffLine := &DiffLine{Type: DIFF_LINE_SECTION, Content: line}
  111. curSection.Lines = append(curSection.Lines, diffLine)
  112. // Parse line number.
  113. ranges := strings.Split(ss[len(ss)-2][1:], " ")
  114. leftLine, _ = com.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
  115. rightLine, _ = com.StrTo(strings.Split(ranges[1], ",")[0]).Int()
  116. continue
  117. case line[0] == '+':
  118. curFile.Addition++
  119. diff.TotalAddition++
  120. diffLine := &DiffLine{Type: DIFF_LINE_ADD, Content: line, RightIdx: rightLine}
  121. rightLine++
  122. curSection.Lines = append(curSection.Lines, diffLine)
  123. continue
  124. case line[0] == '-':
  125. curFile.Deletion++
  126. diff.TotalDeletion++
  127. diffLine := &DiffLine{Type: DIFF_LINE_DEL, Content: line, LeftIdx: leftLine}
  128. if leftLine > 0 {
  129. leftLine++
  130. }
  131. curSection.Lines = append(curSection.Lines, diffLine)
  132. case strings.HasPrefix(line, "Binary"):
  133. curFile.IsBin = true
  134. continue
  135. }
  136. // Get new file.
  137. if strings.HasPrefix(line, DIFF_HEAD) {
  138. if isTooLong {
  139. return diff, nil
  140. }
  141. fs := strings.Split(line[len(DIFF_HEAD):], " ")
  142. a := fs[0]
  143. curFile = &DiffFile{
  144. Name: a[strings.Index(a, "/")+1:],
  145. Index: len(diff.Files) + 1,
  146. Type: DIFF_FILE_CHANGE,
  147. Sections: make([]*DiffSection, 0, 10),
  148. }
  149. diff.Files = append(diff.Files, curFile)
  150. // Check file diff type.
  151. for scanner.Scan() {
  152. switch {
  153. case strings.HasPrefix(scanner.Text(), "new file"):
  154. curFile.Type = DIFF_FILE_ADD
  155. case strings.HasPrefix(scanner.Text(), "deleted"):
  156. curFile.Type = DIFF_FILE_DEL
  157. case strings.HasPrefix(scanner.Text(), "index"):
  158. curFile.Type = DIFF_FILE_CHANGE
  159. }
  160. if curFile.Type > 0 {
  161. break
  162. }
  163. }
  164. }
  165. }
  166. // FIXME: use first 30 lines to detect file encoding.
  167. charset, err := base.DetectEncoding(buf.Bytes())
  168. if charset != "utf8" && err == nil {
  169. decoder := mahonia.NewDecoder(charset)
  170. if decoder != nil {
  171. for _, f := range diff.Files {
  172. for _, sec := range f.Sections {
  173. for _, l := range sec.Lines {
  174. l.Content = decoder.ConvertString(l.Content)
  175. }
  176. }
  177. }
  178. }
  179. }
  180. return diff, nil
  181. }
  182. func GetDiffRange(repoPath, beforeCommitId string, afterCommitId string, maxlines int) (*Diff, error) {
  183. repo, err := git.OpenRepository(repoPath)
  184. if err != nil {
  185. return nil, err
  186. }
  187. commit, err := repo.GetCommit(afterCommitId)
  188. if err != nil {
  189. return nil, err
  190. }
  191. rd, wr := io.Pipe()
  192. var cmd *exec.Cmd
  193. // if "after" commit given
  194. if beforeCommitId == "" {
  195. // First commit of repository.
  196. if commit.ParentCount() == 0 {
  197. cmd = exec.Command("git", "show", afterCommitId)
  198. } else {
  199. c, _ := commit.Parent(0)
  200. cmd = exec.Command("git", "diff", c.Id.String(), afterCommitId)
  201. }
  202. } else {
  203. cmd = exec.Command("git", "diff", beforeCommitId, afterCommitId)
  204. }
  205. cmd.Dir = repoPath
  206. cmd.Stdout = wr
  207. cmd.Stdin = os.Stdin
  208. cmd.Stderr = os.Stderr
  209. done := make(chan error)
  210. go func() {
  211. cmd.Start()
  212. done <- cmd.Wait()
  213. wr.Close()
  214. }()
  215. defer rd.Close()
  216. desc := fmt.Sprintf("GetDiffRange(%s)", repoPath)
  217. pid := process.Add(desc, cmd)
  218. go func() {
  219. // In case process became zombie.
  220. select {
  221. case <-time.After(5 * time.Minute):
  222. if errKill := process.Kill(pid); errKill != nil {
  223. log.Error(4, "git_diff.ParsePatch(Kill): %v", err)
  224. }
  225. <-done
  226. // return "", ErrExecTimeout.Error(), ErrExecTimeout
  227. case err = <-done:
  228. process.Remove(pid)
  229. }
  230. }()
  231. return ParsePatch(pid, maxlines, cmd, rd)
  232. }
  233. func GetDiffCommit(repoPath, commitId string, maxlines int) (*Diff, error) {
  234. return GetDiffRange(repoPath, "", commitId, maxlines)
  235. }