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.

repo_commit.go 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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 git
  5. import (
  6. "bytes"
  7. "container/list"
  8. "errors"
  9. "fmt"
  10. "strings"
  11. "sync"
  12. "github.com/Unknwon/com"
  13. )
  14. func (repo *Repository) getCommitIdOfRef(refpath string) (string, error) {
  15. stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--verify", refpath)
  16. if err != nil {
  17. return "", errors.New(stderr)
  18. }
  19. return strings.Split(stdout, " ")[0], nil
  20. }
  21. func (repo *Repository) GetCommitIdOfBranch(branchName string) (string, error) {
  22. return repo.getCommitIdOfRef("refs/heads/" + branchName)
  23. }
  24. // get branch's last commit or a special commit by id string
  25. func (repo *Repository) GetCommitOfBranch(branchName string) (*Commit, error) {
  26. commitId, err := repo.GetCommitIdOfBranch(branchName)
  27. if err != nil {
  28. return nil, err
  29. }
  30. return repo.GetCommit(commitId)
  31. }
  32. func (repo *Repository) GetCommitIdOfTag(tagName string) (string, error) {
  33. return repo.getCommitIdOfRef("refs/tags/" + tagName)
  34. }
  35. func (repo *Repository) GetCommitOfTag(tagName string) (*Commit, error) {
  36. tag, err := repo.GetTag(tagName)
  37. if err != nil {
  38. return nil, err
  39. }
  40. return tag.Commit()
  41. }
  42. // Parse commit information from the (uncompressed) raw
  43. // data from the commit object.
  44. // \n\n separate headers from message
  45. func parseCommitData(data []byte) (*Commit, error) {
  46. commit := new(Commit)
  47. commit.parents = make([]sha1, 0, 1)
  48. // we now have the contents of the commit object. Let's investigate...
  49. nextline := 0
  50. l:
  51. for {
  52. eol := bytes.IndexByte(data[nextline:], '\n')
  53. switch {
  54. case eol > 0:
  55. line := data[nextline : nextline+eol]
  56. spacepos := bytes.IndexByte(line, ' ')
  57. reftype := line[:spacepos]
  58. switch string(reftype) {
  59. case "tree":
  60. id, err := NewIdFromString(string(line[spacepos+1:]))
  61. if err != nil {
  62. return nil, err
  63. }
  64. commit.Tree.Id = id
  65. case "parent":
  66. // A commit can have one or more parents
  67. oid, err := NewIdFromString(string(line[spacepos+1:]))
  68. if err != nil {
  69. return nil, err
  70. }
  71. commit.parents = append(commit.parents, oid)
  72. case "author":
  73. sig, err := newSignatureFromCommitline(line[spacepos+1:])
  74. if err != nil {
  75. return nil, err
  76. }
  77. commit.Author = sig
  78. case "committer":
  79. sig, err := newSignatureFromCommitline(line[spacepos+1:])
  80. if err != nil {
  81. return nil, err
  82. }
  83. commit.Committer = sig
  84. }
  85. nextline += eol + 1
  86. case eol == 0:
  87. commit.CommitMessage = string(data[nextline+1:])
  88. break l
  89. default:
  90. break l
  91. }
  92. }
  93. return commit, nil
  94. }
  95. func (repo *Repository) getCommit(id sha1) (*Commit, error) {
  96. if repo.commitCache != nil {
  97. if c, ok := repo.commitCache[id]; ok {
  98. return c, nil
  99. }
  100. } else {
  101. repo.commitCache = make(map[sha1]*Commit, 10)
  102. }
  103. data, bytErr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
  104. if err != nil {
  105. return nil, errors.New(err.Error() + ": " + string(bytErr))
  106. }
  107. commit, err := parseCommitData(data)
  108. if err != nil {
  109. return nil, err
  110. }
  111. commit.repo = repo
  112. commit.Id = id
  113. repo.commitCache[id] = commit
  114. return commit, nil
  115. }
  116. // Find the commit object in the repository.
  117. func (repo *Repository) GetCommit(commitId string) (*Commit, error) {
  118. id, err := NewIdFromString(commitId)
  119. if err != nil {
  120. return nil, err
  121. }
  122. return repo.getCommit(id)
  123. }
  124. func (repo *Repository) commitsCount(id sha1) (int, error) {
  125. if gitVer.LessThan(MustParseVersion("1.8.0")) {
  126. stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log",
  127. "--pretty=format:''", id.String())
  128. if err != nil {
  129. return 0, errors.New(string(stderr))
  130. }
  131. return len(bytes.Split(stdout, []byte("\n"))), nil
  132. }
  133. stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count", id.String())
  134. if err != nil {
  135. return 0, errors.New(stderr)
  136. }
  137. return com.StrTo(strings.TrimSpace(stdout)).Int()
  138. }
  139. func (repo *Repository) CommitsCount(commitId string) (int, error) {
  140. id, err := NewIdFromString(commitId)
  141. if err != nil {
  142. return 0, err
  143. }
  144. return repo.commitsCount(id)
  145. }
  146. func (repo *Repository) commitsCountBetween(start, end sha1) (int, error) {
  147. if gitVer.LessThan(MustParseVersion("1.8.0")) {
  148. stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log",
  149. "--pretty=format:''", start.String()+"..."+end.String())
  150. if err != nil {
  151. return 0, errors.New(string(stderr))
  152. }
  153. return len(bytes.Split(stdout, []byte("\n"))), nil
  154. }
  155. stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count",
  156. start.String()+"..."+end.String())
  157. if err != nil {
  158. return 0, errors.New(stderr)
  159. }
  160. return com.StrTo(strings.TrimSpace(stdout)).Int()
  161. }
  162. func (repo *Repository) CommitsCountBetween(startCommitID, endCommitID string) (int, error) {
  163. start, err := NewIdFromString(startCommitID)
  164. if err != nil {
  165. return 0, err
  166. }
  167. end, err := NewIdFromString(endCommitID)
  168. if err != nil {
  169. return 0, err
  170. }
  171. return repo.commitsCountBetween(start, end)
  172. }
  173. func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
  174. stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "diff", "--name-only",
  175. startCommitID+"..."+endCommitID)
  176. if err != nil {
  177. return 0, fmt.Errorf("list changed files: %v", concatenateError(err, stderr))
  178. }
  179. return len(strings.Split(stdout, "\n")) - 1, nil
  180. }
  181. // used only for single tree, (]
  182. func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) {
  183. l := list.New()
  184. if last == nil || last.ParentCount() == 0 {
  185. return l, nil
  186. }
  187. var err error
  188. cur := last
  189. for {
  190. if cur.Id.Equal(before.Id) {
  191. break
  192. }
  193. l.PushBack(cur)
  194. if cur.ParentCount() == 0 {
  195. break
  196. }
  197. cur, err = cur.Parent(0)
  198. if err != nil {
  199. return nil, err
  200. }
  201. }
  202. return l, nil
  203. }
  204. func (repo *Repository) commitsBefore(lock *sync.Mutex, l *list.List, parent *list.Element, id sha1, limit int) error {
  205. commit, err := repo.getCommit(id)
  206. if err != nil {
  207. return err
  208. }
  209. var e *list.Element
  210. if parent == nil {
  211. e = l.PushBack(commit)
  212. } else {
  213. var in = parent
  214. for {
  215. if in == nil {
  216. break
  217. } else if in.Value.(*Commit).Id.Equal(commit.Id) {
  218. return nil
  219. } else {
  220. if in.Next() == nil {
  221. break
  222. }
  223. if in.Value.(*Commit).Committer.When.Equal(commit.Committer.When) {
  224. break
  225. }
  226. if in.Value.(*Commit).Committer.When.After(commit.Committer.When) &&
  227. in.Next().Value.(*Commit).Committer.When.Before(commit.Committer.When) {
  228. break
  229. }
  230. }
  231. in = in.Next()
  232. }
  233. e = l.InsertAfter(commit, in)
  234. }
  235. var pr = parent
  236. if commit.ParentCount() > 1 {
  237. pr = e
  238. }
  239. for i := 0; i < commit.ParentCount(); i++ {
  240. id, err := commit.ParentId(i)
  241. if err != nil {
  242. return err
  243. }
  244. err = repo.commitsBefore(lock, l, pr, id, 0)
  245. if err != nil {
  246. return err
  247. }
  248. }
  249. return nil
  250. }
  251. func (repo *Repository) FileCommitsCount(branch, file string) (int, error) {
  252. stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count",
  253. branch, "--", file)
  254. if err != nil {
  255. return 0, errors.New(stderr)
  256. }
  257. return com.StrTo(strings.TrimSpace(stdout)).Int()
  258. }
  259. func (repo *Repository) CommitsByFileAndRange(branch, file string, page int) (*list.List, error) {
  260. stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", branch,
  261. "--skip="+com.ToStr((page-1)*50), "--max-count=50", prettyLogFormat, "--", file)
  262. if err != nil {
  263. return nil, errors.New(string(stderr))
  264. }
  265. return parsePrettyFormatLog(repo, stdout)
  266. }
  267. func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) {
  268. l := list.New()
  269. lock := new(sync.Mutex)
  270. err := repo.commitsBefore(lock, l, nil, id, 0)
  271. return l, err
  272. }
  273. func (repo *Repository) searchCommits(id sha1, keyword string) (*list.List, error) {
  274. stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", id.String(), "-100",
  275. "-i", "--grep="+keyword, prettyLogFormat)
  276. if err != nil {
  277. return nil, err
  278. } else if len(stderr) > 0 {
  279. return nil, errors.New(string(stderr))
  280. }
  281. return parsePrettyFormatLog(repo, stdout)
  282. }
  283. var CommitsRangeSize = 50
  284. func (repo *Repository) commitsByRange(id sha1, page int) (*list.List, error) {
  285. stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", id.String(),
  286. "--skip="+com.ToStr((page-1)*CommitsRangeSize), "--max-count="+com.ToStr(CommitsRangeSize), prettyLogFormat)
  287. if err != nil {
  288. return nil, errors.New(string(stderr))
  289. }
  290. return parsePrettyFormatLog(repo, stdout)
  291. }
  292. func (repo *Repository) getCommitOfRelPath(id sha1, relPath string) (*Commit, error) {
  293. stdout, _, err := com.ExecCmdDir(repo.Path, "git", "log", "-1", prettyLogFormat, id.String(), "--", relPath)
  294. if err != nil {
  295. return nil, err
  296. }
  297. id, err = NewIdFromString(string(stdout))
  298. if err != nil {
  299. return nil, err
  300. }
  301. return repo.getCommit(id)
  302. }