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.

286 lines
6.5 KiB

10 years ago
10 years ago
10 years ago
10 years ago
9 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. // FIXME: Should use cache in the future.
  74. buf bytes.Buffer
  75. )
  76. diff := &Diff{Files: make([]*DiffFile, 0)}
  77. var i int
  78. for scanner.Scan() {
  79. line := scanner.Text()
  80. // fmt.Println(i, line)
  81. if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
  82. continue
  83. }
  84. if line == "" {
  85. continue
  86. }
  87. i = i + 1
  88. // Diff data too large, we only show the first about maxlines lines
  89. if i >= maxlines {
  90. log.Warn("Diff data too large")
  91. diff.Files = nil
  92. return diff, nil
  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. curSection = &DiffSection{}
  103. curFile.Sections = append(curFile.Sections, curSection)
  104. ss := strings.Split(line, "@@")
  105. diffLine := &DiffLine{Type: DIFF_LINE_SECTION, Content: line}
  106. curSection.Lines = append(curSection.Lines, diffLine)
  107. // Parse line number.
  108. ranges := strings.Split(ss[1][1:], " ")
  109. leftLine, _ = com.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
  110. if len(ranges) > 1 {
  111. rightLine, _ = com.StrTo(strings.Split(ranges[1], ",")[0]).Int()
  112. } else {
  113. log.Warn("Parse line number failed: %v", line)
  114. rightLine = leftLine
  115. }
  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. beg := len(DIFF_HEAD)
  139. a := line[beg : (len(line)-beg)/2+beg]
  140. // In case file name is surrounded by double quotes(it happens only in git-shell).
  141. if a[0] == '"' {
  142. a = a[1 : len(a)-1]
  143. a = strings.Replace(a, `\"`, `"`, -1)
  144. }
  145. curFile = &DiffFile{
  146. Name: a[strings.Index(a, "/")+1:],
  147. Index: len(diff.Files) + 1,
  148. Type: DIFF_FILE_CHANGE,
  149. Sections: make([]*DiffSection, 0, 10),
  150. }
  151. diff.Files = append(diff.Files, curFile)
  152. // Check file diff type.
  153. for scanner.Scan() {
  154. switch {
  155. case strings.HasPrefix(scanner.Text(), "new file"):
  156. curFile.Type = DIFF_FILE_ADD
  157. curFile.IsDeleted = false
  158. curFile.IsCreated = true
  159. case strings.HasPrefix(scanner.Text(), "deleted"):
  160. curFile.Type = DIFF_FILE_DEL
  161. curFile.IsCreated = false
  162. curFile.IsDeleted = true
  163. case strings.HasPrefix(scanner.Text(), "index"):
  164. curFile.Type = DIFF_FILE_CHANGE
  165. curFile.IsCreated = false
  166. curFile.IsDeleted = false
  167. }
  168. if curFile.Type > 0 {
  169. break
  170. }
  171. }
  172. }
  173. }
  174. for _, f := range diff.Files {
  175. buf.Reset()
  176. for _, sec := range f.Sections {
  177. for _, l := range sec.Lines {
  178. buf.WriteString(l.Content)
  179. buf.WriteString("\n")
  180. }
  181. }
  182. charsetLabel, err := base.DetectEncoding(buf.Bytes())
  183. if charsetLabel != "UTF-8" && err == nil {
  184. encoding, _ := charset.Lookup(charsetLabel)
  185. if encoding != nil {
  186. d := encoding.NewDecoder()
  187. for _, sec := range f.Sections {
  188. for _, l := range sec.Lines {
  189. if c, _, err := transform.String(d, l.Content); err == nil {
  190. l.Content = c
  191. }
  192. }
  193. }
  194. }
  195. }
  196. }
  197. return diff, nil
  198. }
  199. func GetDiffRange(repoPath, beforeCommitId string, afterCommitId string, maxlines int) (*Diff, error) {
  200. repo, err := git.OpenRepository(repoPath)
  201. if err != nil {
  202. return nil, err
  203. }
  204. commit, err := repo.GetCommit(afterCommitId)
  205. if err != nil {
  206. return nil, err
  207. }
  208. rd, wr := io.Pipe()
  209. var cmd *exec.Cmd
  210. // if "after" commit given
  211. if beforeCommitId == "" {
  212. // First commit of repository.
  213. if commit.ParentCount() == 0 {
  214. cmd = exec.Command("git", "show", afterCommitId)
  215. } else {
  216. c, _ := commit.Parent(0)
  217. cmd = exec.Command("git", "diff", c.Id.String(), afterCommitId)
  218. }
  219. } else {
  220. cmd = exec.Command("git", "diff", beforeCommitId, afterCommitId)
  221. }
  222. cmd.Dir = repoPath
  223. cmd.Stdout = wr
  224. cmd.Stdin = os.Stdin
  225. cmd.Stderr = os.Stderr
  226. done := make(chan error)
  227. go func() {
  228. cmd.Start()
  229. done <- cmd.Wait()
  230. wr.Close()
  231. }()
  232. defer rd.Close()
  233. desc := fmt.Sprintf("GetDiffRange(%s)", repoPath)
  234. pid := process.Add(desc, cmd)
  235. go func() {
  236. // In case process became zombie.
  237. select {
  238. case <-time.After(5 * time.Minute):
  239. if errKill := process.Kill(pid); errKill != nil {
  240. log.Error(4, "git_diff.ParsePatch(Kill): %v", err)
  241. }
  242. <-done
  243. // return "", ErrExecTimeout.Error(), ErrExecTimeout
  244. case err = <-done:
  245. process.Remove(pid)
  246. }
  247. }()
  248. return ParsePatch(pid, maxlines, cmd, rd)
  249. }
  250. func GetDiffCommit(repoPath, commitId string, maxlines int) (*Diff, error) {
  251. return GetDiffRange(repoPath, "", commitId, maxlines)
  252. }