git.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  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. }