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.

288 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
  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: 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. // Diff data too large, we only show the first about maxlines lines
  90. if i == maxlines {
  91. isTooLong = true
  92. log.Warn("Diff data too large")
  93. }
  94. switch {
  95. case line[0] == ' ':
  96. diffLine := &DiffLine{Type: DIFF_LINE_PLAIN, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
  97. leftLine++
  98. rightLine++
  99. curSection.Lines = append(curSection.Lines, diffLine)
  100. continue
  101. case line[0] == '@':
  102. if isTooLong {
  103. break
  104. }
  105. curSection = &DiffSection{}
  106. curFile.Sections = append(curFile.Sections, curSection)
  107. ss := strings.Split(line, "@@")
  108. diffLine := &DiffLine{Type: DIFF_LINE_SECTION, Content: line}
  109. curSection.Lines = append(curSection.Lines, diffLine)
  110. // Parse line number.
  111. ranges := strings.Split(ss[1][1:], " ")
  112. leftLine, _ = com.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
  113. if len(ranges) > 1 {
  114. rightLine, _ = com.StrTo(strings.Split(ranges[1], ",")[0]).Int()
  115. } else {
  116. log.Warn("Parse line number failed: %v", line)
  117. rightLine = leftLine
  118. }
  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. break
  143. }
  144. beg := len(DIFF_HEAD)
  145. a := line[beg : (len(line)-beg)/2+beg]
  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. for _, f := range diff.Files {
  176. buf.Reset()
  177. for _, sec := range f.Sections {
  178. for _, l := range sec.Lines {
  179. buf.WriteString(l.Content)
  180. buf.WriteString("\n")
  181. }
  182. }
  183. charsetLabel, err := base.DetectEncoding(buf.Bytes())
  184. if charsetLabel != "UTF-8" && err == nil {
  185. encoding, _ := charset.Lookup(charsetLabel)
  186. if encoding != nil {
  187. d := encoding.NewDecoder()
  188. for _, sec := range f.Sections {
  189. for _, l := range sec.Lines {
  190. if c, _, err := transform.String(d, l.Content); err == nil {
  191. l.Content = c
  192. }
  193. }
  194. }
  195. }
  196. }
  197. }
  198. return diff, nil
  199. }
  200. func GetDiffRange(repoPath, beforeCommitId string, afterCommitId string, maxlines int) (*Diff, error) {
  201. repo, err := git.OpenRepository(repoPath)
  202. if err != nil {
  203. return nil, err
  204. }
  205. commit, err := repo.GetCommit(afterCommitId)
  206. if err != nil {
  207. return nil, err
  208. }
  209. rd, wr := io.Pipe()
  210. var cmd *exec.Cmd
  211. // if "after" commit given
  212. if beforeCommitId == "" {
  213. // First commit of repository.
  214. if commit.ParentCount() == 0 {
  215. cmd = exec.Command("git", "show", afterCommitId)
  216. } else {
  217. c, _ := commit.Parent(0)
  218. cmd = exec.Command("git", "diff", c.Id.String(), afterCommitId)
  219. }
  220. } else {
  221. cmd = exec.Command("git", "diff", beforeCommitId, afterCommitId)
  222. }
  223. cmd.Dir = repoPath
  224. cmd.Stdout = wr
  225. cmd.Stdin = os.Stdin
  226. cmd.Stderr = os.Stderr
  227. done := make(chan error)
  228. go func() {
  229. cmd.Start()
  230. done <- cmd.Wait()
  231. wr.Close()
  232. }()
  233. defer rd.Close()
  234. desc := fmt.Sprintf("GetDiffRange(%s)", repoPath)
  235. pid := process.Add(desc, cmd)
  236. go func() {
  237. // In case process became zombie.
  238. select {
  239. case <-time.After(5 * time.Minute):
  240. if errKill := process.Kill(pid); errKill != nil {
  241. log.Error(4, "git_diff.ParsePatch(Kill): %v", err)
  242. }
  243. <-done
  244. // return "", ErrExecTimeout.Error(), ErrExecTimeout
  245. case err = <-done:
  246. process.Remove(pid)
  247. }
  248. }()
  249. return ParsePatch(pid, maxlines, cmd, rd)
  250. }
  251. func GetDiffCommit(repoPath, commitId string, maxlines int) (*Diff, error) {
  252. return GetDiffRange(repoPath, "", commitId, maxlines)
  253. }