git.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  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. // Diff data too large.
  256. if i == 2000 {
  257. return &Diff{}, nil
  258. }
  259. if line == "" {
  260. continue
  261. }
  262. if line[0] == ' ' {
  263. diffLine := &DiffLine{Type: DIFF_LINE_PLAIN, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
  264. leftLine++
  265. rightLine++
  266. curSection.Lines = append(curSection.Lines, diffLine)
  267. continue
  268. } else if line[0] == '@' {
  269. curSection = &DiffSection{}
  270. curFile.Sections = append(curFile.Sections, curSection)
  271. ss := strings.Split(line, "@@")
  272. diffLine := &DiffLine{Type: DIFF_LINE_SECTION, Content: line}
  273. curSection.Lines = append(curSection.Lines, diffLine)
  274. // Parse line number.
  275. ranges := strings.Split(ss[len(ss)-2][1:], " ")
  276. leftLine, _ = base.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
  277. rightLine, _ = base.StrTo(strings.Split(ranges[1], ",")[0]).Int()
  278. continue
  279. } else if line[0] == '+' {
  280. curFile.Addition++
  281. diff.TotalAddition++
  282. diffLine := &DiffLine{Type: DIFF_LINE_ADD, Content: line, RightIdx: rightLine}
  283. rightLine++
  284. curSection.Lines = append(curSection.Lines, diffLine)
  285. continue
  286. } else if line[0] == '-' {
  287. curFile.Deletion++
  288. diff.TotalDeletion++
  289. diffLine := &DiffLine{Type: DIFF_LINE_DEL, Content: line, LeftIdx: leftLine}
  290. if leftLine > 0 {
  291. leftLine++
  292. }
  293. curSection.Lines = append(curSection.Lines, diffLine)
  294. continue
  295. }
  296. // Get new file.
  297. if strings.HasPrefix(line, DIFF_HEAD) {
  298. fs := strings.Split(line[len(DIFF_HEAD):], " ")
  299. a := fs[0]
  300. curFile = &DiffFile{
  301. Name: a[strings.Index(a, "/")+1:],
  302. Type: DIFF_FILE_CHANGE,
  303. Sections: make([]*DiffSection, 0, 10),
  304. }
  305. diff.Files = append(diff.Files, curFile)
  306. // Check file diff type.
  307. for scanner.Scan() {
  308. switch {
  309. case strings.HasPrefix(scanner.Text(), "new file"):
  310. curFile.Type = DIFF_FILE_ADD
  311. case strings.HasPrefix(scanner.Text(), "deleted"):
  312. curFile.Type = DIFF_FILE_DEL
  313. case strings.HasPrefix(scanner.Text(), "index"):
  314. curFile.Type = DIFF_FILE_CHANGE
  315. }
  316. if curFile.Type > 0 {
  317. break
  318. }
  319. }
  320. }
  321. }
  322. return diff, nil
  323. }
  324. func GetDiff(repoPath, commitid string) (*Diff, error) {
  325. repo, err := git.OpenRepository(repoPath)
  326. if err != nil {
  327. return nil, err
  328. }
  329. commit, err := repo.GetCommit(commitid)
  330. if err != nil {
  331. return nil, err
  332. }
  333. // First commit of repository.
  334. if commit.ParentCount() == 0 {
  335. rd, wr := io.Pipe()
  336. go func() {
  337. cmd := exec.Command("git", "show", commitid)
  338. cmd.Dir = repoPath
  339. cmd.Stdout = wr
  340. cmd.Stdin = os.Stdin
  341. cmd.Stderr = os.Stderr
  342. cmd.Run()
  343. wr.Close()
  344. }()
  345. defer rd.Close()
  346. return ParsePatch(rd)
  347. }
  348. rd, wr := io.Pipe()
  349. go func() {
  350. cmd := exec.Command("git", "diff", commit.Parent(0).Oid.String(), commitid)
  351. cmd.Dir = repoPath
  352. cmd.Stdout = wr
  353. cmd.Stdin = os.Stdin
  354. cmd.Stderr = os.Stderr
  355. cmd.Run()
  356. wr.Close()
  357. }()
  358. defer rd.Close()
  359. return ParsePatch(rd)
  360. }
  361. const prettyLogFormat = `--pretty=format:%H%n%an <%ae> %at%n%s`
  362. func parsePrettyFormatLog(logByts []byte) (*list.List, error) {
  363. l := list.New()
  364. buf := bytes.NewBuffer(logByts)
  365. if buf.Len() == 0 {
  366. return l, nil
  367. }
  368. idx := 0
  369. var commit *git.Commit
  370. for {
  371. line, err := buf.ReadString('\n')
  372. if err != nil && err != io.EOF {
  373. return nil, err
  374. }
  375. line = strings.TrimSpace(line)
  376. // fmt.Println(line)
  377. var parseErr error
  378. switch idx {
  379. case 0: // SHA1.
  380. commit = &git.Commit{}
  381. commit.Oid, parseErr = git.NewOidFromString(line)
  382. case 1: // Signature.
  383. commit.Author, parseErr = git.NewSignatureFromCommitline([]byte(line + " "))
  384. case 2: // Commit message.
  385. commit.CommitMessage = line
  386. l.PushBack(commit)
  387. idx = -1
  388. }
  389. if parseErr != nil {
  390. return nil, parseErr
  391. }
  392. idx++
  393. if err == io.EOF {
  394. break
  395. }
  396. }
  397. return l, nil
  398. }
  399. // SearchCommits searches commits in given branch and keyword of repository.
  400. func SearchCommits(repoPath, branch, keyword string) (*list.List, error) {
  401. stdout, stderr, err := com.ExecCmdDirBytes(repoPath, "git", "log", branch, "-100",
  402. "-i", "--grep="+keyword, prettyLogFormat)
  403. if err != nil {
  404. return nil, err
  405. } else if len(stderr) > 0 {
  406. return nil, errors.New(string(stderr))
  407. }
  408. return parsePrettyFormatLog(stdout)
  409. }
  410. // GetCommitsByRange returns certain number of commits with given page of repository.
  411. func GetCommitsByRange(repoPath, branch string, page int) (*list.List, error) {
  412. stdout, stderr, err := com.ExecCmdDirBytes(repoPath, "git", "log", branch,
  413. "--skip="+base.ToStr((page-1)*50), "--max-count=50", prettyLogFormat)
  414. if err != nil {
  415. return nil, err
  416. } else if len(stderr) > 0 {
  417. return nil, errors.New(string(stderr))
  418. }
  419. return parsePrettyFormatLog(stdout)
  420. }
  421. // GetCommitsCount returns the commits count of given branch of repository.
  422. func GetCommitsCount(repoPath, branch string) (int, error) {
  423. stdout, stderr, err := com.ExecCmdDir(repoPath, "git", "rev-list", "--count", branch)
  424. if err != nil {
  425. return 0, err
  426. } else if len(stderr) > 0 {
  427. return 0, errors.New(stderr)
  428. }
  429. return base.StrTo(strings.TrimSpace(stdout)).Int()
  430. }