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.

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