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.

287 lines
6.7 KiB

  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2017 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package git
  6. import (
  7. "bytes"
  8. "container/list"
  9. "errors"
  10. "os"
  11. "path"
  12. "path/filepath"
  13. "strings"
  14. "time"
  15. "github.com/Unknwon/com"
  16. )
  17. // Repository represents a Git repository.
  18. type Repository struct {
  19. Path string
  20. commitCache *ObjectCache
  21. tagCache *ObjectCache
  22. }
  23. const prettyLogFormat = `--pretty=format:%H`
  24. func (repo *Repository) parsePrettyFormatLogToList(logs []byte) (*list.List, error) {
  25. l := list.New()
  26. if len(logs) == 0 {
  27. return l, nil
  28. }
  29. parts := bytes.Split(logs, []byte{'\n'})
  30. for _, commitID := range parts {
  31. commit, err := repo.GetCommit(string(commitID))
  32. if err != nil {
  33. return nil, err
  34. }
  35. l.PushBack(commit)
  36. }
  37. return l, nil
  38. }
  39. // IsRepoURLAccessible checks if given repository URL is accessible.
  40. func IsRepoURLAccessible(url string) bool {
  41. _, err := NewCommand("ls-remote", "-q", "-h", url, "HEAD").Run()
  42. if err != nil {
  43. return false
  44. }
  45. return true
  46. }
  47. // InitRepository initializes a new Git repository.
  48. func InitRepository(repoPath string, bare bool) error {
  49. os.MkdirAll(repoPath, os.ModePerm)
  50. cmd := NewCommand("init")
  51. if bare {
  52. cmd.AddArguments("--bare")
  53. }
  54. _, err := cmd.RunInDir(repoPath)
  55. return err
  56. }
  57. // OpenRepository opens the repository at the given path.
  58. func OpenRepository(repoPath string) (*Repository, error) {
  59. repoPath, err := filepath.Abs(repoPath)
  60. if err != nil {
  61. return nil, err
  62. } else if !isDir(repoPath) {
  63. return nil, errors.New("no such file or directory")
  64. }
  65. return &Repository{
  66. Path: repoPath,
  67. commitCache: newObjectCache(),
  68. tagCache: newObjectCache(),
  69. }, nil
  70. }
  71. // CloneRepoOptions options when clone a repository
  72. type CloneRepoOptions struct {
  73. Timeout time.Duration
  74. Mirror bool
  75. Bare bool
  76. Quiet bool
  77. Branch string
  78. }
  79. // Clone clones original repository to target path.
  80. func Clone(from, to string, opts CloneRepoOptions) (err error) {
  81. toDir := path.Dir(to)
  82. if err = os.MkdirAll(toDir, os.ModePerm); err != nil {
  83. return err
  84. }
  85. cmd := NewCommand("clone")
  86. if opts.Mirror {
  87. cmd.AddArguments("--mirror")
  88. }
  89. if opts.Bare {
  90. cmd.AddArguments("--bare")
  91. }
  92. if opts.Quiet {
  93. cmd.AddArguments("--quiet")
  94. }
  95. if len(opts.Branch) > 0 {
  96. cmd.AddArguments("-b", opts.Branch)
  97. }
  98. cmd.AddArguments(from, to)
  99. if opts.Timeout <= 0 {
  100. opts.Timeout = -1
  101. }
  102. _, err = cmd.RunTimeout(opts.Timeout)
  103. return err
  104. }
  105. // PullRemoteOptions options when pull from remote
  106. type PullRemoteOptions struct {
  107. Timeout time.Duration
  108. All bool
  109. Rebase bool
  110. Remote string
  111. Branch string
  112. }
  113. // Pull pulls changes from remotes.
  114. func Pull(repoPath string, opts PullRemoteOptions) error {
  115. cmd := NewCommand("pull")
  116. if opts.Rebase {
  117. cmd.AddArguments("--rebase")
  118. }
  119. if opts.All {
  120. cmd.AddArguments("--all")
  121. } else {
  122. cmd.AddArguments(opts.Remote)
  123. cmd.AddArguments(opts.Branch)
  124. }
  125. if opts.Timeout <= 0 {
  126. opts.Timeout = -1
  127. }
  128. _, err := cmd.RunInDirTimeout(opts.Timeout, repoPath)
  129. return err
  130. }
  131. // PushOptions options when push to remote
  132. type PushOptions struct {
  133. Remote string
  134. Branch string
  135. Force bool
  136. }
  137. // Push pushs local commits to given remote branch.
  138. func Push(repoPath string, opts PushOptions) error {
  139. cmd := NewCommand("push")
  140. if opts.Force {
  141. cmd.AddArguments("-f")
  142. }
  143. cmd.AddArguments(opts.Remote, opts.Branch)
  144. _, err := cmd.RunInDir(repoPath)
  145. return err
  146. }
  147. // CheckoutOptions options when heck out some branch
  148. type CheckoutOptions struct {
  149. Timeout time.Duration
  150. Branch string
  151. OldBranch string
  152. }
  153. // Checkout checkouts a branch
  154. func Checkout(repoPath string, opts CheckoutOptions) error {
  155. cmd := NewCommand("checkout")
  156. if len(opts.OldBranch) > 0 {
  157. cmd.AddArguments("-b")
  158. }
  159. if opts.Timeout <= 0 {
  160. opts.Timeout = -1
  161. }
  162. cmd.AddArguments(opts.Branch)
  163. if len(opts.OldBranch) > 0 {
  164. cmd.AddArguments(opts.OldBranch)
  165. }
  166. _, err := cmd.RunInDirTimeout(opts.Timeout, repoPath)
  167. return err
  168. }
  169. // ResetHEAD resets HEAD to given revision or head of branch.
  170. func ResetHEAD(repoPath string, hard bool, revision string) error {
  171. cmd := NewCommand("reset")
  172. if hard {
  173. cmd.AddArguments("--hard")
  174. }
  175. _, err := cmd.AddArguments(revision).RunInDir(repoPath)
  176. return err
  177. }
  178. // MoveFile moves a file to another file or directory.
  179. func MoveFile(repoPath, oldTreeName, newTreeName string) error {
  180. _, err := NewCommand("mv").AddArguments(oldTreeName, newTreeName).RunInDir(repoPath)
  181. return err
  182. }
  183. // CountObject represents repository count objects report
  184. type CountObject struct {
  185. Count int64
  186. Size int64
  187. InPack int64
  188. Packs int64
  189. SizePack int64
  190. PrunePack int64
  191. Garbage int64
  192. SizeGarbage int64
  193. }
  194. const (
  195. statCount = "count: "
  196. statSize = "size: "
  197. statInpack = "in-pack: "
  198. statPacks = "packs: "
  199. statSizePack = "size-pack: "
  200. statPrunePackage = "prune-package: "
  201. statGarbage = "garbage: "
  202. statSizeGarbage = "size-garbage: "
  203. )
  204. // GetRepoSize returns disk consumption for repo in path
  205. func GetRepoSize(repoPath string) (*CountObject, error) {
  206. cmd := NewCommand("count-objects", "-v")
  207. stdout, err := cmd.RunInDir(repoPath)
  208. if err != nil {
  209. return nil, err
  210. }
  211. return parseSize(stdout), nil
  212. }
  213. // parseSize parses the output from count-objects and return a CountObject
  214. func parseSize(objects string) *CountObject {
  215. repoSize := new(CountObject)
  216. for _, line := range strings.Split(objects, "\n") {
  217. switch {
  218. case strings.HasPrefix(line, statCount):
  219. repoSize.Count = com.StrTo(line[7:]).MustInt64()
  220. case strings.HasPrefix(line, statSize):
  221. repoSize.Size = com.StrTo(line[6:]).MustInt64() * 1024
  222. case strings.HasPrefix(line, statInpack):
  223. repoSize.InPack = com.StrTo(line[9:]).MustInt64()
  224. case strings.HasPrefix(line, statPacks):
  225. repoSize.Packs = com.StrTo(line[7:]).MustInt64()
  226. case strings.HasPrefix(line, statSizePack):
  227. repoSize.SizePack = com.StrTo(line[11:]).MustInt64() * 1024
  228. case strings.HasPrefix(line, statPrunePackage):
  229. repoSize.PrunePack = com.StrTo(line[16:]).MustInt64()
  230. case strings.HasPrefix(line, statGarbage):
  231. repoSize.Garbage = com.StrTo(line[9:]).MustInt64()
  232. case strings.HasPrefix(line, statSizeGarbage):
  233. repoSize.SizeGarbage = com.StrTo(line[14:]).MustInt64() * 1024
  234. }
  235. }
  236. return repoSize
  237. }
  238. // GetLatestCommitTime returns time for latest commit in repository (across all branches)
  239. func GetLatestCommitTime(repoPath string) (time.Time, error) {
  240. cmd := NewCommand("for-each-ref", "--sort=-committerdate", "refs/heads/", "--count", "1", "--format=%(committerdate)")
  241. stdout, err := cmd.RunInDir(repoPath)
  242. if err != nil {
  243. return time.Time{}, err
  244. }
  245. commitTime := strings.TrimSpace(stdout)
  246. return time.Parse(GitTimeLayout, commitTime)
  247. }