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.

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