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.

370 lines
9.7 KiB

  1. // Copyright 2015 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. "fmt"
  9. "strconv"
  10. "strings"
  11. "github.com/mcuadros/go-version"
  12. )
  13. // getRefCommitID returns the last commit ID string of given reference (branch or tag).
  14. func (repo *Repository) getRefCommitID(name string) (string, error) {
  15. stdout, err := NewCommand("show-ref", "--verify", name).RunInDir(repo.Path)
  16. if err != nil {
  17. if strings.Contains(err.Error(), "not a valid ref") {
  18. return "", ErrNotExist{name, ""}
  19. }
  20. return "", err
  21. }
  22. return strings.Split(stdout, " ")[0], nil
  23. }
  24. // GetBranchCommitID returns last commit ID string of given branch.
  25. func (repo *Repository) GetBranchCommitID(name string) (string, error) {
  26. return repo.getRefCommitID(BranchPrefix + name)
  27. }
  28. // GetTagCommitID returns last commit ID string of given tag.
  29. func (repo *Repository) GetTagCommitID(name string) (string, error) {
  30. return repo.getRefCommitID(TagPrefix + name)
  31. }
  32. // parseCommitData parses commit information from the (uncompressed) raw
  33. // data from the commit object.
  34. // \n\n separate headers from message
  35. func parseCommitData(data []byte) (*Commit, error) {
  36. commit := new(Commit)
  37. commit.parents = make([]SHA1, 0, 1)
  38. // we now have the contents of the commit object. Let's investigate...
  39. nextline := 0
  40. l:
  41. for {
  42. eol := bytes.IndexByte(data[nextline:], '\n')
  43. switch {
  44. case eol > 0:
  45. line := data[nextline : nextline+eol]
  46. spacepos := bytes.IndexByte(line, ' ')
  47. reftype := line[:spacepos]
  48. switch string(reftype) {
  49. case "tree", "object":
  50. id, err := NewIDFromString(string(line[spacepos+1:]))
  51. if err != nil {
  52. return nil, err
  53. }
  54. commit.Tree.ID = id
  55. case "parent":
  56. // A commit can have one or more parents
  57. oid, err := NewIDFromString(string(line[spacepos+1:]))
  58. if err != nil {
  59. return nil, err
  60. }
  61. commit.parents = append(commit.parents, oid)
  62. case "author", "tagger":
  63. sig, err := newSignatureFromCommitline(line[spacepos+1:])
  64. if err != nil {
  65. return nil, err
  66. }
  67. commit.Author = sig
  68. case "committer":
  69. sig, err := newSignatureFromCommitline(line[spacepos+1:])
  70. if err != nil {
  71. return nil, err
  72. }
  73. commit.Committer = sig
  74. case "gpgsig":
  75. sig, err := newGPGSignatureFromCommitline(data, nextline+spacepos+1)
  76. if err != nil {
  77. return nil, err
  78. }
  79. commit.Signature = sig
  80. }
  81. nextline += eol + 1
  82. case eol == 0:
  83. commit.CommitMessage = string(data[nextline+1:])
  84. break l
  85. default:
  86. break l
  87. }
  88. }
  89. return commit, nil
  90. }
  91. func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
  92. c, ok := repo.commitCache.Get(id.String())
  93. if ok {
  94. log("Hit cache: %s", id)
  95. return c.(*Commit), nil
  96. }
  97. data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path)
  98. if err != nil {
  99. if strings.Contains(err.Error(), "fatal: Not a valid object name") {
  100. return nil, ErrNotExist{id.String(), ""}
  101. }
  102. return nil, err
  103. }
  104. commit, err := parseCommitData(data)
  105. if err != nil {
  106. return nil, err
  107. }
  108. commit.repo = repo
  109. commit.ID = id
  110. repo.commitCache.Set(id.String(), commit)
  111. return commit, nil
  112. }
  113. // GetCommit returns commit object of by ID string.
  114. func (repo *Repository) GetCommit(commitID string) (*Commit, error) {
  115. if len(commitID) != 40 {
  116. var err error
  117. commitID, err = NewCommand("rev-parse", commitID).RunInDir(repo.Path)
  118. if err != nil {
  119. return nil, err
  120. }
  121. }
  122. id, err := NewIDFromString(commitID)
  123. if err != nil {
  124. return nil, err
  125. }
  126. return repo.getCommit(id)
  127. }
  128. // GetBranchCommit returns the last commit of given branch.
  129. func (repo *Repository) GetBranchCommit(name string) (*Commit, error) {
  130. commitID, err := repo.GetBranchCommitID(name)
  131. if err != nil {
  132. return nil, err
  133. }
  134. return repo.GetCommit(commitID)
  135. }
  136. // GetTagCommit get the commit of the specific tag via name
  137. func (repo *Repository) GetTagCommit(name string) (*Commit, error) {
  138. commitID, err := repo.GetTagCommitID(name)
  139. if err != nil {
  140. return nil, err
  141. }
  142. return repo.GetCommit(commitID)
  143. }
  144. func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit, error) {
  145. // File name starts with ':' must be escaped.
  146. if relpath[0] == ':' {
  147. relpath = `\` + relpath
  148. }
  149. stdout, err := NewCommand("log", "-1", prettyLogFormat, id.String(), "--", relpath).RunInDir(repo.Path)
  150. if err != nil {
  151. return nil, err
  152. }
  153. id, err = NewIDFromString(stdout)
  154. if err != nil {
  155. return nil, err
  156. }
  157. return repo.getCommit(id)
  158. }
  159. // GetCommitByPath returns the last commit of relative path.
  160. func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
  161. stdout, err := NewCommand("log", "-1", prettyLogFormat, "--", relpath).RunInDirBytes(repo.Path)
  162. if err != nil {
  163. return nil, err
  164. }
  165. commits, err := repo.parsePrettyFormatLogToList(stdout)
  166. if err != nil {
  167. return nil, err
  168. }
  169. return commits.Front().Value.(*Commit), nil
  170. }
  171. // CommitsRangeSize the default commits range size
  172. var CommitsRangeSize = 50
  173. func (repo *Repository) commitsByRange(id SHA1, page int) (*list.List, error) {
  174. stdout, err := NewCommand("log", id.String(), "--skip="+strconv.Itoa((page-1)*CommitsRangeSize),
  175. "--max-count="+strconv.Itoa(CommitsRangeSize), prettyLogFormat).RunInDirBytes(repo.Path)
  176. if err != nil {
  177. return nil, err
  178. }
  179. return repo.parsePrettyFormatLogToList(stdout)
  180. }
  181. func (repo *Repository) searchCommits(id SHA1, keyword string, all bool) (*list.List, error) {
  182. cmd := NewCommand("log", id.String(), "-100", "-i", "--grep="+keyword, prettyLogFormat)
  183. if all {
  184. cmd.AddArguments("--all")
  185. }
  186. stdout, err := cmd.RunInDirBytes(repo.Path)
  187. if err != nil {
  188. return nil, err
  189. }
  190. return repo.parsePrettyFormatLogToList(stdout)
  191. }
  192. func (repo *Repository) getFilesChanged(id1 string, id2 string) ([]string, error) {
  193. stdout, err := NewCommand("diff", "--name-only", id1, id2).RunInDirBytes(repo.Path)
  194. if err != nil {
  195. return nil, err
  196. }
  197. return strings.Split(string(stdout), "\n"), nil
  198. }
  199. // FileCommitsCount return the number of files at a revison
  200. func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) {
  201. return commitsCount(repo.Path, revision, file)
  202. }
  203. // CommitsByFileAndRange return the commits accroding revison file and the page
  204. func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (*list.List, error) {
  205. stdout, err := NewCommand("log", revision, "--follow", "--skip="+strconv.Itoa((page-1)*50),
  206. "--max-count="+strconv.Itoa(CommitsRangeSize), prettyLogFormat, "--", file).RunInDirBytes(repo.Path)
  207. if err != nil {
  208. return nil, err
  209. }
  210. return repo.parsePrettyFormatLogToList(stdout)
  211. }
  212. // FilesCountBetween return the number of files changed between two commits
  213. func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
  214. stdout, err := NewCommand("diff", "--name-only", startCommitID+"..."+endCommitID).RunInDir(repo.Path)
  215. if err != nil {
  216. return 0, err
  217. }
  218. return len(strings.Split(stdout, "\n")) - 1, nil
  219. }
  220. // CommitsBetween returns a list that contains commits between [last, before).
  221. func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) {
  222. if version.Compare(gitVersion, "1.8.0", ">=") {
  223. stdout, err := NewCommand("rev-list", before.ID.String()+"..."+last.ID.String()).RunInDirBytes(repo.Path)
  224. if err != nil {
  225. return nil, err
  226. }
  227. return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout))
  228. }
  229. // Fallback to stupid solution, which iterates all commits of the repository
  230. // if before is not an ancestor of last.
  231. l := list.New()
  232. if last == nil || last.ParentCount() == 0 {
  233. return l, nil
  234. }
  235. var err error
  236. cur := last
  237. for {
  238. if cur.ID.Equal(before.ID) {
  239. break
  240. }
  241. l.PushBack(cur)
  242. if cur.ParentCount() == 0 {
  243. break
  244. }
  245. cur, err = cur.Parent(0)
  246. if err != nil {
  247. return nil, err
  248. }
  249. }
  250. return l, nil
  251. }
  252. // CommitsBetweenIDs return commits between twoe commits
  253. func (repo *Repository) CommitsBetweenIDs(last, before string) (*list.List, error) {
  254. lastCommit, err := repo.GetCommit(last)
  255. if err != nil {
  256. return nil, err
  257. }
  258. beforeCommit, err := repo.GetCommit(before)
  259. if err != nil {
  260. return nil, err
  261. }
  262. return repo.CommitsBetween(lastCommit, beforeCommit)
  263. }
  264. // CommitsCountBetween return numbers of commits between two commits
  265. func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
  266. return commitsCount(repo.Path, start+"..."+end, "")
  267. }
  268. // commitsBefore the limit is depth, not total number of returned commits.
  269. func (repo *Repository) commitsBefore(l *list.List, parent *list.Element, id SHA1, current, limit int) error {
  270. // Reach the limit
  271. if limit > 0 && current > limit {
  272. return nil
  273. }
  274. commit, err := repo.getCommit(id)
  275. if err != nil {
  276. return fmt.Errorf("getCommit: %v", err)
  277. }
  278. var e *list.Element
  279. if parent == nil {
  280. e = l.PushBack(commit)
  281. } else {
  282. var in = parent
  283. for {
  284. if in == nil {
  285. break
  286. } else if in.Value.(*Commit).ID.Equal(commit.ID) {
  287. return nil
  288. } else if in.Next() == nil {
  289. break
  290. }
  291. if in.Value.(*Commit).Committer.When.Equal(commit.Committer.When) {
  292. break
  293. }
  294. if in.Value.(*Commit).Committer.When.After(commit.Committer.When) &&
  295. in.Next().Value.(*Commit).Committer.When.Before(commit.Committer.When) {
  296. break
  297. }
  298. in = in.Next()
  299. }
  300. e = l.InsertAfter(commit, in)
  301. }
  302. pr := parent
  303. if commit.ParentCount() > 1 {
  304. pr = e
  305. }
  306. for i := 0; i < commit.ParentCount(); i++ {
  307. id, err := commit.ParentID(i)
  308. if err != nil {
  309. return err
  310. }
  311. err = repo.commitsBefore(l, pr, id, current+1, limit)
  312. if err != nil {
  313. return err
  314. }
  315. }
  316. return nil
  317. }
  318. func (repo *Repository) getCommitsBefore(id SHA1) (*list.List, error) {
  319. l := list.New()
  320. return l, repo.commitsBefore(l, nil, id, 1, 0)
  321. }
  322. func (repo *Repository) getCommitsBeforeLimit(id SHA1, num int) (*list.List, error) {
  323. l := list.New()
  324. return l, repo.commitsBefore(l, nil, id, 1, num)
  325. }