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.

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