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.

433 lines
9.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
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
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
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
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. "container/list"
  8. "fmt"
  9. "io"
  10. "os"
  11. "os/exec"
  12. "path"
  13. "strings"
  14. "github.com/gogits/git"
  15. "github.com/gogits/gogs/modules/base"
  16. )
  17. // RepoFile represents a file object in git repository.
  18. type RepoFile struct {
  19. *git.TreeEntry
  20. Path string
  21. Size int64
  22. Repo *git.Repository
  23. Commit *git.Commit
  24. }
  25. // LookupBlob returns the content of an object.
  26. func (file *RepoFile) LookupBlob() (*git.Blob, error) {
  27. if file.Repo == nil {
  28. return nil, ErrRepoFileNotLoaded
  29. }
  30. return file.Repo.LookupBlob(file.Id)
  31. }
  32. // GetBranches returns all branches of given repository.
  33. func GetBranches(userName, repoName string) ([]string, error) {
  34. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  35. if err != nil {
  36. return nil, err
  37. }
  38. refs, err := repo.AllReferences()
  39. if err != nil {
  40. return nil, err
  41. }
  42. brs := make([]string, len(refs))
  43. for i, ref := range refs {
  44. brs[i] = ref.BranchName()
  45. }
  46. return brs, nil
  47. }
  48. func IsBranchExist(userName, repoName, branchName string) bool {
  49. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  50. if err != nil {
  51. return false
  52. }
  53. return repo.IsBranchExist(branchName)
  54. }
  55. func GetTargetFile(userName, repoName, branchName, commitId, rpath string) (*RepoFile, error) {
  56. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  57. if err != nil {
  58. return nil, err
  59. }
  60. commit, err := repo.GetCommit(branchName, commitId)
  61. if err != nil {
  62. return nil, err
  63. }
  64. parts := strings.Split(path.Clean(rpath), "/")
  65. var entry *git.TreeEntry
  66. tree := commit.Tree
  67. for i, part := range parts {
  68. if i == len(parts)-1 {
  69. entry = tree.EntryByName(part)
  70. if entry == nil {
  71. return nil, ErrRepoFileNotExist
  72. }
  73. } else {
  74. tree, err = repo.SubTree(tree, part)
  75. if err != nil {
  76. return nil, err
  77. }
  78. }
  79. }
  80. size, err := repo.ObjectSize(entry.Id)
  81. if err != nil {
  82. return nil, err
  83. }
  84. repoFile := &RepoFile{
  85. entry,
  86. rpath,
  87. size,
  88. repo,
  89. commit,
  90. }
  91. return repoFile, nil
  92. }
  93. // GetReposFiles returns a list of file object in given directory of repository.
  94. func GetReposFiles(userName, repoName, branchName, commitId, rpath string) ([]*RepoFile, error) {
  95. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  96. if err != nil {
  97. return nil, err
  98. }
  99. commit, err := repo.GetCommit(branchName, commitId)
  100. if err != nil {
  101. return nil, err
  102. }
  103. var repodirs []*RepoFile
  104. var repofiles []*RepoFile
  105. commit.Tree.Walk(func(dirname string, entry *git.TreeEntry) int {
  106. if dirname == rpath {
  107. // TODO: size get method shoule be improved
  108. size, err := repo.ObjectSize(entry.Id)
  109. if err != nil {
  110. return 0
  111. }
  112. var cm = commit
  113. var i int
  114. for {
  115. i = i + 1
  116. //fmt.Println(".....", i, cm.Id(), cm.ParentCount())
  117. if cm.ParentCount() == 0 {
  118. break
  119. } else if cm.ParentCount() == 1 {
  120. pt, _ := repo.SubTree(cm.Parent(0).Tree, dirname)
  121. if pt == nil {
  122. break
  123. }
  124. pEntry := pt.EntryByName(entry.Name)
  125. if pEntry == nil || !pEntry.Id.Equal(entry.Id) {
  126. break
  127. } else {
  128. cm = cm.Parent(0)
  129. }
  130. } else {
  131. var emptyCnt = 0
  132. var sameIdcnt = 0
  133. var lastSameCm *git.Commit
  134. //fmt.Println(".....", cm.ParentCount())
  135. for i := 0; i < cm.ParentCount(); i++ {
  136. //fmt.Println("parent", i, cm.Parent(i).Id())
  137. p := cm.Parent(i)
  138. pt, _ := repo.SubTree(p.Tree, dirname)
  139. var pEntry *git.TreeEntry
  140. if pt != nil {
  141. pEntry = pt.EntryByName(entry.Name)
  142. }
  143. //fmt.Println("pEntry", pEntry)
  144. if pEntry == nil {
  145. emptyCnt = emptyCnt + 1
  146. if emptyCnt+sameIdcnt == cm.ParentCount() {
  147. if lastSameCm == nil {
  148. goto loop
  149. } else {
  150. cm = lastSameCm
  151. break
  152. }
  153. }
  154. } else {
  155. //fmt.Println(i, "pEntry", pEntry.Id, "entry", entry.Id)
  156. if !pEntry.Id.Equal(entry.Id) {
  157. goto loop
  158. } else {
  159. lastSameCm = cm.Parent(i)
  160. sameIdcnt = sameIdcnt + 1
  161. if emptyCnt+sameIdcnt == cm.ParentCount() {
  162. // TODO: now follow the first parent commit?
  163. cm = lastSameCm
  164. //fmt.Println("sameId...")
  165. break
  166. }
  167. }
  168. }
  169. }
  170. }
  171. }
  172. loop:
  173. rp := &RepoFile{
  174. entry,
  175. path.Join(dirname, entry.Name),
  176. size,
  177. repo,
  178. cm,
  179. }
  180. if entry.IsFile() {
  181. repofiles = append(repofiles, rp)
  182. } else if entry.IsDir() {
  183. repodirs = append(repodirs, rp)
  184. }
  185. }
  186. return 0
  187. })
  188. return append(repodirs, repofiles...), nil
  189. }
  190. func GetCommit(userName, repoName, branchname, commitid string) (*git.Commit, error) {
  191. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  192. if err != nil {
  193. return nil, err
  194. }
  195. return repo.GetCommit(branchname, commitid)
  196. }
  197. // GetCommitsByBranch returns all commits of given branch of repository.
  198. func GetCommitsByBranch(userName, repoName, branchName string) (*list.List, error) {
  199. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  200. if err != nil {
  201. return nil, err
  202. }
  203. r, err := repo.LookupReference(fmt.Sprintf("refs/heads/%s", branchName))
  204. if err != nil {
  205. return nil, err
  206. }
  207. return r.AllCommits()
  208. }
  209. // GetCommitsByCommitId returns all commits of given commitId of repository.
  210. func GetCommitsByCommitId(userName, repoName, commitId string) (*list.List, error) {
  211. repo, err := git.OpenRepository(RepoPath(userName, repoName))
  212. if err != nil {
  213. return nil, err
  214. }
  215. oid, err := git.NewOidFromString(commitId)
  216. if err != nil {
  217. return nil, err
  218. }
  219. return repo.CommitsBefore(oid)
  220. }
  221. // Diff line types.
  222. const (
  223. DIFF_LINE_PLAIN = iota + 1
  224. DIFF_LINE_ADD
  225. DIFF_LINE_DEL
  226. DIFF_LINE_SECTION
  227. )
  228. const (
  229. DIFF_FILE_ADD = iota + 1
  230. DIFF_FILE_CHANGE
  231. DIFF_FILE_DEL
  232. )
  233. type DiffLine struct {
  234. LeftIdx int
  235. RightIdx int
  236. Type int
  237. Content string
  238. }
  239. func (d DiffLine) GetType() int {
  240. return d.Type
  241. }
  242. type DiffSection struct {
  243. Name string
  244. Lines []*DiffLine
  245. }
  246. type DiffFile struct {
  247. Name string
  248. Addition, Deletion int
  249. Type int
  250. Sections []*DiffSection
  251. }
  252. type Diff struct {
  253. TotalAddition, TotalDeletion int
  254. Files []*DiffFile
  255. }
  256. func (diff *Diff) NumFiles() int {
  257. return len(diff.Files)
  258. }
  259. const DIFF_HEAD = "diff --git "
  260. func ParsePatch(reader io.Reader) (*Diff, error) {
  261. scanner := bufio.NewScanner(reader)
  262. var (
  263. curFile *DiffFile
  264. curSection = &DiffSection{
  265. Lines: make([]*DiffLine, 0, 10),
  266. }
  267. leftLine, rightLine int
  268. )
  269. diff := &Diff{Files: make([]*DiffFile, 0)}
  270. var i int
  271. for scanner.Scan() {
  272. line := scanner.Text()
  273. // fmt.Println(i, line)
  274. if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
  275. continue
  276. }
  277. i = i + 1
  278. if line == "" {
  279. continue
  280. }
  281. if line[0] == ' ' {
  282. diffLine := &DiffLine{Type: DIFF_LINE_PLAIN, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
  283. leftLine++
  284. rightLine++
  285. curSection.Lines = append(curSection.Lines, diffLine)
  286. continue
  287. } else if line[0] == '@' {
  288. curSection = &DiffSection{}
  289. curFile.Sections = append(curFile.Sections, curSection)
  290. ss := strings.Split(line, "@@")
  291. diffLine := &DiffLine{Type: DIFF_LINE_SECTION, Content: line}
  292. curSection.Lines = append(curSection.Lines, diffLine)
  293. // Parse line number.
  294. ranges := strings.Split(ss[len(ss)-2][1:], " ")
  295. leftLine, _ = base.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
  296. rightLine, _ = base.StrTo(strings.Split(ranges[1], ",")[0]).Int()
  297. continue
  298. } else if line[0] == '+' {
  299. curFile.Addition++
  300. diff.TotalAddition++
  301. diffLine := &DiffLine{Type: DIFF_LINE_ADD, Content: line, RightIdx: rightLine}
  302. rightLine++
  303. curSection.Lines = append(curSection.Lines, diffLine)
  304. continue
  305. } else if line[0] == '-' {
  306. curFile.Deletion++
  307. diff.TotalDeletion++
  308. diffLine := &DiffLine{Type: DIFF_LINE_DEL, Content: line, LeftIdx: leftLine}
  309. if leftLine > 0 {
  310. leftLine++
  311. }
  312. curSection.Lines = append(curSection.Lines, diffLine)
  313. continue
  314. }
  315. // Get new file.
  316. if strings.HasPrefix(line, DIFF_HEAD) {
  317. fs := strings.Split(line[len(DIFF_HEAD):], " ")
  318. a := fs[0]
  319. curFile = &DiffFile{
  320. Name: a[strings.Index(a, "/")+1:],
  321. Type: DIFF_FILE_CHANGE,
  322. Sections: make([]*DiffSection, 0, 10),
  323. }
  324. diff.Files = append(diff.Files, curFile)
  325. // Check file diff type.
  326. for scanner.Scan() {
  327. switch {
  328. case strings.HasPrefix(scanner.Text(), "new file"):
  329. curFile.Type = DIFF_FILE_ADD
  330. case strings.HasPrefix(scanner.Text(), "deleted"):
  331. curFile.Type = DIFF_FILE_DEL
  332. case strings.HasPrefix(scanner.Text(), "index"):
  333. curFile.Type = DIFF_FILE_CHANGE
  334. }
  335. if curFile.Type > 0 {
  336. break
  337. }
  338. }
  339. }
  340. }
  341. return diff, nil
  342. }
  343. func GetDiff(repoPath, commitid string) (*Diff, error) {
  344. repo, err := git.OpenRepository(repoPath)
  345. if err != nil {
  346. return nil, err
  347. }
  348. commit, err := repo.GetCommit("", commitid)
  349. if err != nil {
  350. return nil, err
  351. }
  352. // First commit of repository.
  353. if commit.ParentCount() == 0 {
  354. rd, wr := io.Pipe()
  355. go func() {
  356. cmd := exec.Command("git", "show", commitid)
  357. cmd.Dir = repoPath
  358. cmd.Stdout = wr
  359. cmd.Stdin = os.Stdin
  360. cmd.Stderr = os.Stderr
  361. cmd.Run()
  362. wr.Close()
  363. }()
  364. defer rd.Close()
  365. return ParsePatch(rd)
  366. }
  367. rd, wr := io.Pipe()
  368. go func() {
  369. cmd := exec.Command("git", "diff", commit.Parent(0).Oid.String(), commitid)
  370. cmd.Dir = repoPath
  371. cmd.Stdout = wr
  372. cmd.Stdin = os.Stdin
  373. cmd.Stderr = os.Stderr
  374. cmd.Run()
  375. wr.Close()
  376. }()
  377. defer rd.Close()
  378. return ParsePatch(rd)
  379. }