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.

559 lines
14 KiB

10 years ago
9 years ago
10 years ago
Git LFS support v2 (#122) * Import github.com/git-lfs/lfs-test-server as lfs module base Imported commit is 3968aac269a77b73924649b9412ae03f7ccd3198 Removed: Dockerfile CONTRIBUTING.md mgmt* script/ vendor/ kvlogger.go .dockerignore .gitignore README.md * Remove config, add JWT support from github.com/mgit-at/lfs-test-server Imported commit f0cdcc5a01599c5a955dc1bbf683bb4acecdba83 * Add LFS settings * Add LFS meta object model * Add LFS routes and initialization * Import github.com/dgrijalva/jwt-go into vendor/ * Adapt LFS module: handlers, routing, meta store * Move LFS routes to /user/repo/info/lfs/* * Add request header checks to LFS BatchHandler / PostHandler * Implement LFS basic authentication * Rework JWT secret generation / load * Implement LFS SSH token authentication with JWT Specification: https://github.com/github/git-lfs/tree/master/docs/api * Integrate LFS settings into install process * Remove LFS objects when repository is deleted Only removes objects from content store when deleted repo is the only referencing repository * Make LFS module stateless Fixes bug where LFS would not work after installation without restarting Gitea * Change 500 'Internal Server Error' to 400 'Bad Request' * Change sql query to xorm call * Remove unneeded type from LFS module * Change internal imports to code.gitea.io/gitea/ * Add Gitea authors copyright * Change basic auth realm to "gitea-lfs" * Add unique indexes to LFS model * Use xorm count function in LFS check on repository delete * Return io.ReadCloser from content store and close after usage * Add LFS info to runWeb() * Export LFS content store base path * LFS file download from UI * Work around git-lfs client issue with unauthenticated requests Returning a dummy Authorization header for unauthenticated requests lets git-lfs client skip asking for auth credentials See: https://github.com/github/git-lfs/issues/1088 * Fix unauthenticated UI downloads from public repositories * Authentication check order, Finish LFS file view logic * Ignore LFS hooks if installed for current OS user Fixes Gitea UI actions for repositories tracking LFS files. Checks for minimum needed git version by parsing the semantic version string. * Hide LFS metafile diff from commit view, marking as binary * Show LFS notice if file in commit view is tracked * Add notbefore/nbf JWT claim * Correct lint suggestions - comments for structs and functions - Add comments to LFS model - Function comment for GetRandomBytesAsBase64 - LFS server function comments and lint variable suggestion * Move secret generation code out of conditional Ensures no LFS code may run with an empty secret * Do not hand out JWT tokens if LFS server support is disabled
8 years ago
9 years ago
Git LFS support v2 (#122) * Import github.com/git-lfs/lfs-test-server as lfs module base Imported commit is 3968aac269a77b73924649b9412ae03f7ccd3198 Removed: Dockerfile CONTRIBUTING.md mgmt* script/ vendor/ kvlogger.go .dockerignore .gitignore README.md * Remove config, add JWT support from github.com/mgit-at/lfs-test-server Imported commit f0cdcc5a01599c5a955dc1bbf683bb4acecdba83 * Add LFS settings * Add LFS meta object model * Add LFS routes and initialization * Import github.com/dgrijalva/jwt-go into vendor/ * Adapt LFS module: handlers, routing, meta store * Move LFS routes to /user/repo/info/lfs/* * Add request header checks to LFS BatchHandler / PostHandler * Implement LFS basic authentication * Rework JWT secret generation / load * Implement LFS SSH token authentication with JWT Specification: https://github.com/github/git-lfs/tree/master/docs/api * Integrate LFS settings into install process * Remove LFS objects when repository is deleted Only removes objects from content store when deleted repo is the only referencing repository * Make LFS module stateless Fixes bug where LFS would not work after installation without restarting Gitea * Change 500 'Internal Server Error' to 400 'Bad Request' * Change sql query to xorm call * Remove unneeded type from LFS module * Change internal imports to code.gitea.io/gitea/ * Add Gitea authors copyright * Change basic auth realm to "gitea-lfs" * Add unique indexes to LFS model * Use xorm count function in LFS check on repository delete * Return io.ReadCloser from content store and close after usage * Add LFS info to runWeb() * Export LFS content store base path * LFS file download from UI * Work around git-lfs client issue with unauthenticated requests Returning a dummy Authorization header for unauthenticated requests lets git-lfs client skip asking for auth credentials See: https://github.com/github/git-lfs/issues/1088 * Fix unauthenticated UI downloads from public repositories * Authentication check order, Finish LFS file view logic * Ignore LFS hooks if installed for current OS user Fixes Gitea UI actions for repositories tracking LFS files. Checks for minimum needed git version by parsing the semantic version string. * Hide LFS metafile diff from commit view, marking as binary * Show LFS notice if file in commit view is tracked * Add notbefore/nbf JWT claim * Correct lint suggestions - comments for structs and functions - Add comments to LFS model - Function comment for GetRandomBytesAsBase64 - LFS server function comments and lint variable suggestion * Move secret generation code out of conditional Ensures no LFS code may run with an empty secret * Do not hand out JWT tokens if LFS server support is disabled
8 years ago
Git LFS support v2 (#122) * Import github.com/git-lfs/lfs-test-server as lfs module base Imported commit is 3968aac269a77b73924649b9412ae03f7ccd3198 Removed: Dockerfile CONTRIBUTING.md mgmt* script/ vendor/ kvlogger.go .dockerignore .gitignore README.md * Remove config, add JWT support from github.com/mgit-at/lfs-test-server Imported commit f0cdcc5a01599c5a955dc1bbf683bb4acecdba83 * Add LFS settings * Add LFS meta object model * Add LFS routes and initialization * Import github.com/dgrijalva/jwt-go into vendor/ * Adapt LFS module: handlers, routing, meta store * Move LFS routes to /user/repo/info/lfs/* * Add request header checks to LFS BatchHandler / PostHandler * Implement LFS basic authentication * Rework JWT secret generation / load * Implement LFS SSH token authentication with JWT Specification: https://github.com/github/git-lfs/tree/master/docs/api * Integrate LFS settings into install process * Remove LFS objects when repository is deleted Only removes objects from content store when deleted repo is the only referencing repository * Make LFS module stateless Fixes bug where LFS would not work after installation without restarting Gitea * Change 500 'Internal Server Error' to 400 'Bad Request' * Change sql query to xorm call * Remove unneeded type from LFS module * Change internal imports to code.gitea.io/gitea/ * Add Gitea authors copyright * Change basic auth realm to "gitea-lfs" * Add unique indexes to LFS model * Use xorm count function in LFS check on repository delete * Return io.ReadCloser from content store and close after usage * Add LFS info to runWeb() * Export LFS content store base path * LFS file download from UI * Work around git-lfs client issue with unauthenticated requests Returning a dummy Authorization header for unauthenticated requests lets git-lfs client skip asking for auth credentials See: https://github.com/github/git-lfs/issues/1088 * Fix unauthenticated UI downloads from public repositories * Authentication check order, Finish LFS file view logic * Ignore LFS hooks if installed for current OS user Fixes Gitea UI actions for repositories tracking LFS files. Checks for minimum needed git version by parsing the semantic version string. * Hide LFS metafile diff from commit view, marking as binary * Show LFS notice if file in commit view is tracked * Add notbefore/nbf JWT claim * Correct lint suggestions - comments for structs and functions - Add comments to LFS model - Function comment for GetRandomBytesAsBase64 - LFS server function comments and lint variable suggestion * Move secret generation code out of conditional Ensures no LFS code may run with an empty secret * Do not hand out JWT tokens if LFS server support is disabled
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
Git LFS support v2 (#122) * Import github.com/git-lfs/lfs-test-server as lfs module base Imported commit is 3968aac269a77b73924649b9412ae03f7ccd3198 Removed: Dockerfile CONTRIBUTING.md mgmt* script/ vendor/ kvlogger.go .dockerignore .gitignore README.md * Remove config, add JWT support from github.com/mgit-at/lfs-test-server Imported commit f0cdcc5a01599c5a955dc1bbf683bb4acecdba83 * Add LFS settings * Add LFS meta object model * Add LFS routes and initialization * Import github.com/dgrijalva/jwt-go into vendor/ * Adapt LFS module: handlers, routing, meta store * Move LFS routes to /user/repo/info/lfs/* * Add request header checks to LFS BatchHandler / PostHandler * Implement LFS basic authentication * Rework JWT secret generation / load * Implement LFS SSH token authentication with JWT Specification: https://github.com/github/git-lfs/tree/master/docs/api * Integrate LFS settings into install process * Remove LFS objects when repository is deleted Only removes objects from content store when deleted repo is the only referencing repository * Make LFS module stateless Fixes bug where LFS would not work after installation without restarting Gitea * Change 500 'Internal Server Error' to 400 'Bad Request' * Change sql query to xorm call * Remove unneeded type from LFS module * Change internal imports to code.gitea.io/gitea/ * Add Gitea authors copyright * Change basic auth realm to "gitea-lfs" * Add unique indexes to LFS model * Use xorm count function in LFS check on repository delete * Return io.ReadCloser from content store and close after usage * Add LFS info to runWeb() * Export LFS content store base path * LFS file download from UI * Work around git-lfs client issue with unauthenticated requests Returning a dummy Authorization header for unauthenticated requests lets git-lfs client skip asking for auth credentials See: https://github.com/github/git-lfs/issues/1088 * Fix unauthenticated UI downloads from public repositories * Authentication check order, Finish LFS file view logic * Ignore LFS hooks if installed for current OS user Fixes Gitea UI actions for repositories tracking LFS files. Checks for minimum needed git version by parsing the semantic version string. * Hide LFS metafile diff from commit view, marking as binary * Show LFS notice if file in commit view is tracked * Add notbefore/nbf JWT claim * Correct lint suggestions - comments for structs and functions - Add comments to LFS model - Function comment for GetRandomBytesAsBase64 - LFS server function comments and lint variable suggestion * Move secret generation code out of conditional Ensures no LFS code may run with an empty secret * Do not hand out JWT tokens if LFS server support is disabled
8 years ago
9 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. "html"
  10. "html/template"
  11. "io"
  12. "io/ioutil"
  13. "os"
  14. "os/exec"
  15. "strings"
  16. "code.gitea.io/git"
  17. "code.gitea.io/gitea/modules/base"
  18. "code.gitea.io/gitea/modules/highlight"
  19. "code.gitea.io/gitea/modules/log"
  20. "code.gitea.io/gitea/modules/process"
  21. "code.gitea.io/gitea/modules/setting"
  22. "github.com/Unknwon/com"
  23. "github.com/sergi/go-diff/diffmatchpatch"
  24. "golang.org/x/net/html/charset"
  25. "golang.org/x/text/transform"
  26. )
  27. // DiffLineType represents the type of a DiffLine.
  28. type DiffLineType uint8
  29. // DiffLineType possible values.
  30. const (
  31. DiffLinePlain DiffLineType = iota + 1
  32. DiffLineAdd
  33. DiffLineDel
  34. DiffLineSection
  35. )
  36. // DiffFileType represents the type of a DiffFile.
  37. type DiffFileType uint8
  38. // DiffFileType possible values.
  39. const (
  40. DiffFileAdd DiffFileType = iota + 1
  41. DiffFileChange
  42. DiffFileDel
  43. DiffFileRename
  44. )
  45. // DiffLine represents a line difference in a DiffSection.
  46. type DiffLine struct {
  47. LeftIdx int
  48. RightIdx int
  49. Type DiffLineType
  50. Content string
  51. }
  52. // GetType returns the type of a DiffLine.
  53. func (d *DiffLine) GetType() int {
  54. return int(d.Type)
  55. }
  56. // DiffSection represents a section of a DiffFile.
  57. type DiffSection struct {
  58. Name string
  59. Lines []*DiffLine
  60. }
  61. var (
  62. addedCodePrefix = []byte("<span class=\"added-code\">")
  63. removedCodePrefix = []byte("<span class=\"removed-code\">")
  64. codeTagSuffix = []byte("</span>")
  65. )
  66. func diffToHTML(diffs []diffmatchpatch.Diff, lineType DiffLineType) template.HTML {
  67. buf := bytes.NewBuffer(nil)
  68. // Reproduce signs which are cut for inline diff before.
  69. switch lineType {
  70. case DiffLineAdd:
  71. buf.WriteByte('+')
  72. case DiffLineDel:
  73. buf.WriteByte('-')
  74. }
  75. for i := range diffs {
  76. switch {
  77. case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == DiffLineAdd:
  78. buf.Write(addedCodePrefix)
  79. buf.WriteString(html.EscapeString(diffs[i].Text))
  80. buf.Write(codeTagSuffix)
  81. case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == DiffLineDel:
  82. buf.Write(removedCodePrefix)
  83. buf.WriteString(html.EscapeString(diffs[i].Text))
  84. buf.Write(codeTagSuffix)
  85. case diffs[i].Type == diffmatchpatch.DiffEqual:
  86. buf.WriteString(html.EscapeString(diffs[i].Text))
  87. }
  88. }
  89. return template.HTML(buf.Bytes())
  90. }
  91. // GetLine gets a specific line by type (add or del) and file line number
  92. func (diffSection *DiffSection) GetLine(lineType DiffLineType, idx int) *DiffLine {
  93. var (
  94. difference = 0
  95. addCount = 0
  96. delCount = 0
  97. matchDiffLine *DiffLine
  98. )
  99. LOOP:
  100. for _, diffLine := range diffSection.Lines {
  101. switch diffLine.Type {
  102. case DiffLineAdd:
  103. addCount++
  104. case DiffLineDel:
  105. delCount++
  106. default:
  107. if matchDiffLine != nil {
  108. break LOOP
  109. }
  110. difference = diffLine.RightIdx - diffLine.LeftIdx
  111. addCount = 0
  112. delCount = 0
  113. }
  114. switch lineType {
  115. case DiffLineDel:
  116. if diffLine.RightIdx == 0 && diffLine.LeftIdx == idx-difference {
  117. matchDiffLine = diffLine
  118. }
  119. case DiffLineAdd:
  120. if diffLine.LeftIdx == 0 && diffLine.RightIdx == idx+difference {
  121. matchDiffLine = diffLine
  122. }
  123. }
  124. }
  125. if addCount == delCount {
  126. return matchDiffLine
  127. }
  128. return nil
  129. }
  130. var diffMatchPatch = diffmatchpatch.New()
  131. func init() {
  132. diffMatchPatch.DiffEditCost = 100
  133. }
  134. // GetComputedInlineDiffFor computes inline diff for the given line.
  135. func (diffSection *DiffSection) GetComputedInlineDiffFor(diffLine *DiffLine) template.HTML {
  136. if setting.Git.DisableDiffHighlight {
  137. return template.HTML(html.EscapeString(diffLine.Content[1:]))
  138. }
  139. var (
  140. compareDiffLine *DiffLine
  141. diff1 string
  142. diff2 string
  143. )
  144. // try to find equivalent diff line. ignore, otherwise
  145. switch diffLine.Type {
  146. case DiffLineAdd:
  147. compareDiffLine = diffSection.GetLine(DiffLineDel, diffLine.RightIdx)
  148. if compareDiffLine == nil {
  149. return template.HTML(html.EscapeString(diffLine.Content))
  150. }
  151. diff1 = compareDiffLine.Content
  152. diff2 = diffLine.Content
  153. case DiffLineDel:
  154. compareDiffLine = diffSection.GetLine(DiffLineAdd, diffLine.LeftIdx)
  155. if compareDiffLine == nil {
  156. return template.HTML(html.EscapeString(diffLine.Content))
  157. }
  158. diff1 = diffLine.Content
  159. diff2 = compareDiffLine.Content
  160. default:
  161. return template.HTML(html.EscapeString(diffLine.Content))
  162. }
  163. diffRecord := diffMatchPatch.DiffMain(diff1[1:], diff2[1:], true)
  164. diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord)
  165. return diffToHTML(diffRecord, diffLine.Type)
  166. }
  167. // DiffFile represents a file diff.
  168. type DiffFile struct {
  169. Name string
  170. OldName string
  171. Index int
  172. Addition, Deletion int
  173. Type DiffFileType
  174. IsCreated bool
  175. IsDeleted bool
  176. IsBin bool
  177. IsLFSFile bool
  178. IsRenamed bool
  179. IsSubmodule bool
  180. Sections []*DiffSection
  181. IsIncomplete bool
  182. }
  183. // GetType returns type of diff file.
  184. func (diffFile *DiffFile) GetType() int {
  185. return int(diffFile.Type)
  186. }
  187. // GetHighlightClass returns highlight class for a filename.
  188. func (diffFile *DiffFile) GetHighlightClass() string {
  189. return highlight.FileNameToHighlightClass(diffFile.Name)
  190. }
  191. // Diff represents a difference between two git trees.
  192. type Diff struct {
  193. TotalAddition, TotalDeletion int
  194. Files []*DiffFile
  195. IsIncomplete bool
  196. }
  197. // NumFiles returns number of files changes in a diff.
  198. func (diff *Diff) NumFiles() int {
  199. return len(diff.Files)
  200. }
  201. const cmdDiffHead = "diff --git "
  202. // ParsePatch builds a Diff object from a io.Reader and some
  203. // parameters.
  204. // TODO: move this function to gogits/git-module
  205. func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*Diff, error) {
  206. var (
  207. diff = &Diff{Files: make([]*DiffFile, 0)}
  208. curFile *DiffFile
  209. curSection = &DiffSection{
  210. Lines: make([]*DiffLine, 0, 10),
  211. }
  212. leftLine, rightLine int
  213. lineCount int
  214. curFileLinesCount int
  215. curFileLFSPrefix bool
  216. )
  217. input := bufio.NewReader(reader)
  218. isEOF := false
  219. for !isEOF {
  220. line, err := input.ReadString('\n')
  221. if err != nil {
  222. if err == io.EOF {
  223. isEOF = true
  224. } else {
  225. return nil, fmt.Errorf("ReadString: %v", err)
  226. }
  227. }
  228. if len(line) > 0 && line[len(line)-1] == '\n' {
  229. // Remove line break.
  230. line = line[:len(line)-1]
  231. }
  232. if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") || len(line) == 0 {
  233. continue
  234. }
  235. trimLine := strings.Trim(line, "+- ")
  236. if trimLine == LFSMetaFileIdentifier {
  237. curFileLFSPrefix = true
  238. }
  239. if curFileLFSPrefix && strings.HasPrefix(trimLine, LFSMetaFileOidPrefix) {
  240. oid := strings.TrimPrefix(trimLine, LFSMetaFileOidPrefix)
  241. if len(oid) == 64 {
  242. m := &LFSMetaObject{Oid: oid}
  243. count, err := x.Count(m)
  244. if err == nil && count > 0 {
  245. curFile.IsBin = true
  246. curFile.IsLFSFile = true
  247. curSection.Lines = nil
  248. break
  249. }
  250. }
  251. }
  252. curFileLinesCount++
  253. lineCount++
  254. // Diff data too large, we only show the first about maxLines lines
  255. if curFileLinesCount >= maxLines || len(line) >= maxLineCharacters {
  256. curFile.IsIncomplete = true
  257. }
  258. switch {
  259. case line[0] == ' ':
  260. diffLine := &DiffLine{Type: DiffLinePlain, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
  261. leftLine++
  262. rightLine++
  263. curSection.Lines = append(curSection.Lines, diffLine)
  264. continue
  265. case line[0] == '@':
  266. curSection = &DiffSection{}
  267. curFile.Sections = append(curFile.Sections, curSection)
  268. ss := strings.Split(line, "@@")
  269. diffLine := &DiffLine{Type: DiffLineSection, Content: line}
  270. curSection.Lines = append(curSection.Lines, diffLine)
  271. // Parse line number.
  272. ranges := strings.Split(ss[1][1:], " ")
  273. leftLine, _ = com.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
  274. if len(ranges) > 1 {
  275. rightLine, _ = com.StrTo(strings.Split(ranges[1], ",")[0]).Int()
  276. } else {
  277. log.Warn("Parse line number failed: %v", line)
  278. rightLine = leftLine
  279. }
  280. continue
  281. case line[0] == '+':
  282. curFile.Addition++
  283. diff.TotalAddition++
  284. diffLine := &DiffLine{Type: DiffLineAdd, Content: line, RightIdx: rightLine}
  285. rightLine++
  286. curSection.Lines = append(curSection.Lines, diffLine)
  287. continue
  288. case line[0] == '-':
  289. curFile.Deletion++
  290. diff.TotalDeletion++
  291. diffLine := &DiffLine{Type: DiffLineDel, Content: line, LeftIdx: leftLine}
  292. if leftLine > 0 {
  293. leftLine++
  294. }
  295. curSection.Lines = append(curSection.Lines, diffLine)
  296. case strings.HasPrefix(line, "Binary"):
  297. curFile.IsBin = true
  298. continue
  299. }
  300. // Get new file.
  301. if strings.HasPrefix(line, cmdDiffHead) {
  302. middle := -1
  303. // Note: In case file name is surrounded by double quotes (it happens only in git-shell).
  304. // e.g. diff --git "a/xxx" "b/xxx"
  305. hasQuote := line[len(cmdDiffHead)] == '"'
  306. if hasQuote {
  307. middle = strings.Index(line, ` "b/`)
  308. } else {
  309. middle = strings.Index(line, " b/")
  310. }
  311. beg := len(cmdDiffHead)
  312. a := line[beg+2 : middle]
  313. b := line[middle+3:]
  314. if hasQuote {
  315. a = string(git.UnescapeChars([]byte(a[1 : len(a)-1])))
  316. b = string(git.UnescapeChars([]byte(b[1 : len(b)-1])))
  317. }
  318. curFile = &DiffFile{
  319. Name: a,
  320. Index: len(diff.Files) + 1,
  321. Type: DiffFileChange,
  322. Sections: make([]*DiffSection, 0, 10),
  323. }
  324. diff.Files = append(diff.Files, curFile)
  325. if len(diff.Files) >= maxFiles {
  326. diff.IsIncomplete = true
  327. io.Copy(ioutil.Discard, reader)
  328. break
  329. }
  330. curFileLinesCount = 0
  331. curFileLFSPrefix = false
  332. // Check file diff type and is submodule.
  333. for {
  334. line, err := input.ReadString('\n')
  335. if err != nil {
  336. if err == io.EOF {
  337. isEOF = true
  338. } else {
  339. return nil, fmt.Errorf("ReadString: %v", err)
  340. }
  341. }
  342. switch {
  343. case strings.HasPrefix(line, "new file"):
  344. curFile.Type = DiffFileAdd
  345. curFile.IsCreated = true
  346. case strings.HasPrefix(line, "deleted"):
  347. curFile.Type = DiffFileDel
  348. curFile.IsDeleted = true
  349. case strings.HasPrefix(line, "index"):
  350. curFile.Type = DiffFileChange
  351. case strings.HasPrefix(line, "similarity index 100%"):
  352. curFile.Type = DiffFileRename
  353. curFile.IsRenamed = true
  354. curFile.OldName = curFile.Name
  355. curFile.Name = b
  356. }
  357. if curFile.Type > 0 {
  358. if strings.HasSuffix(line, " 160000\n") {
  359. curFile.IsSubmodule = true
  360. }
  361. break
  362. }
  363. }
  364. }
  365. }
  366. // FIXME: detect encoding while parsing.
  367. var buf bytes.Buffer
  368. for _, f := range diff.Files {
  369. buf.Reset()
  370. for _, sec := range f.Sections {
  371. for _, l := range sec.Lines {
  372. buf.WriteString(l.Content)
  373. buf.WriteString("\n")
  374. }
  375. }
  376. charsetLabel, err := base.DetectEncoding(buf.Bytes())
  377. if charsetLabel != "UTF-8" && err == nil {
  378. encoding, _ := charset.Lookup(charsetLabel)
  379. if encoding != nil {
  380. d := encoding.NewDecoder()
  381. for _, sec := range f.Sections {
  382. for _, l := range sec.Lines {
  383. if c, _, err := transform.String(d, l.Content); err == nil {
  384. l.Content = c
  385. }
  386. }
  387. }
  388. }
  389. }
  390. }
  391. return diff, nil
  392. }
  393. // GetDiffRange builds a Diff between two commits of a repository.
  394. // passing the empty string as beforeCommitID returns a diff from the
  395. // parent commit.
  396. func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacters, maxFiles int) (*Diff, error) {
  397. gitRepo, err := git.OpenRepository(repoPath)
  398. if err != nil {
  399. return nil, err
  400. }
  401. commit, err := gitRepo.GetCommit(afterCommitID)
  402. if err != nil {
  403. return nil, err
  404. }
  405. var cmd *exec.Cmd
  406. // if "after" commit given
  407. if len(beforeCommitID) == 0 {
  408. // First commit of repository.
  409. if commit.ParentCount() == 0 {
  410. cmd = exec.Command("git", "show", afterCommitID)
  411. } else {
  412. c, _ := commit.Parent(0)
  413. cmd = exec.Command("git", "diff", "-M", c.ID.String(), afterCommitID)
  414. }
  415. } else {
  416. cmd = exec.Command("git", "diff", "-M", beforeCommitID, afterCommitID)
  417. }
  418. cmd.Dir = repoPath
  419. cmd.Stderr = os.Stderr
  420. stdout, err := cmd.StdoutPipe()
  421. if err != nil {
  422. return nil, fmt.Errorf("StdoutPipe: %v", err)
  423. }
  424. if err = cmd.Start(); err != nil {
  425. return nil, fmt.Errorf("Start: %v", err)
  426. }
  427. pid := process.GetManager().Add(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath), cmd)
  428. defer process.GetManager().Remove(pid)
  429. diff, err := ParsePatch(maxLines, maxLineCharacters, maxFiles, stdout)
  430. if err != nil {
  431. return nil, fmt.Errorf("ParsePatch: %v", err)
  432. }
  433. if err = cmd.Wait(); err != nil {
  434. return nil, fmt.Errorf("Wait: %v", err)
  435. }
  436. return diff, nil
  437. }
  438. // RawDiffType type of a raw diff.
  439. type RawDiffType string
  440. // RawDiffType possible values.
  441. const (
  442. RawDiffNormal RawDiffType = "diff"
  443. RawDiffPatch RawDiffType = "patch"
  444. )
  445. // GetRawDiff dumps diff results of repository in given commit ID to io.Writer.
  446. // TODO: move this function to gogits/git-module
  447. func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Writer) error {
  448. repo, err := git.OpenRepository(repoPath)
  449. if err != nil {
  450. return fmt.Errorf("OpenRepository: %v", err)
  451. }
  452. commit, err := repo.GetCommit(commitID)
  453. if err != nil {
  454. return fmt.Errorf("GetCommit: %v", err)
  455. }
  456. var cmd *exec.Cmd
  457. switch diffType {
  458. case RawDiffNormal:
  459. if commit.ParentCount() == 0 {
  460. cmd = exec.Command("git", "show", commitID)
  461. } else {
  462. c, _ := commit.Parent(0)
  463. cmd = exec.Command("git", "diff", "-M", c.ID.String(), commitID)
  464. }
  465. case RawDiffPatch:
  466. if commit.ParentCount() == 0 {
  467. cmd = exec.Command("git", "format-patch", "--no-signature", "--stdout", "--root", commitID)
  468. } else {
  469. c, _ := commit.Parent(0)
  470. query := fmt.Sprintf("%s...%s", commitID, c.ID.String())
  471. cmd = exec.Command("git", "format-patch", "--no-signature", "--stdout", query)
  472. }
  473. default:
  474. return fmt.Errorf("invalid diffType: %s", diffType)
  475. }
  476. stderr := new(bytes.Buffer)
  477. cmd.Dir = repoPath
  478. cmd.Stdout = writer
  479. cmd.Stderr = stderr
  480. if err = cmd.Run(); err != nil {
  481. return fmt.Errorf("Run: %v - %s", err, stderr)
  482. }
  483. return nil
  484. }
  485. // GetDiffCommit builds a Diff representing the given commitID.
  486. func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacters, maxFiles int) (*Diff, error) {
  487. return GetDiffRange(repoPath, "", commitID, maxLines, maxLineCharacters, maxFiles)
  488. }