gitea源码

fork.go 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repository
  4. import (
  5. "context"
  6. "fmt"
  7. "strings"
  8. "time"
  9. "code.gitea.io/gitea/models/db"
  10. git_model "code.gitea.io/gitea/models/git"
  11. repo_model "code.gitea.io/gitea/models/repo"
  12. "code.gitea.io/gitea/models/unit"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/git"
  15. "code.gitea.io/gitea/modules/git/gitcmd"
  16. "code.gitea.io/gitea/modules/gitrepo"
  17. "code.gitea.io/gitea/modules/log"
  18. repo_module "code.gitea.io/gitea/modules/repository"
  19. "code.gitea.io/gitea/modules/structs"
  20. "code.gitea.io/gitea/modules/util"
  21. notify_service "code.gitea.io/gitea/services/notify"
  22. "xorm.io/builder"
  23. )
  24. // ErrForkAlreadyExist represents a "ForkAlreadyExist" kind of error.
  25. type ErrForkAlreadyExist struct {
  26. Uname string
  27. RepoName string
  28. ForkName string
  29. }
  30. // IsErrForkAlreadyExist checks if an error is an ErrForkAlreadyExist.
  31. func IsErrForkAlreadyExist(err error) bool {
  32. _, ok := err.(ErrForkAlreadyExist)
  33. return ok
  34. }
  35. func (err ErrForkAlreadyExist) Error() string {
  36. return fmt.Sprintf("repository is already forked by user [uname: %s, repo path: %s, fork path: %s]", err.Uname, err.RepoName, err.ForkName)
  37. }
  38. func (err ErrForkAlreadyExist) Unwrap() error {
  39. return util.ErrAlreadyExist
  40. }
  41. // ForkRepoOptions contains the fork repository options
  42. type ForkRepoOptions struct {
  43. BaseRepo *repo_model.Repository
  44. Name string
  45. Description string
  46. SingleBranch string
  47. }
  48. // ForkRepository forks a repository
  49. func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts ForkRepoOptions) (*repo_model.Repository, error) {
  50. if err := opts.BaseRepo.LoadOwner(ctx); err != nil {
  51. return nil, err
  52. }
  53. if user_model.IsUserBlockedBy(ctx, doer, opts.BaseRepo.Owner.ID) {
  54. return nil, user_model.ErrBlockedUser
  55. }
  56. // Fork is prohibited, if user has reached maximum limit of repositories
  57. if !doer.CanForkRepoIn(owner) {
  58. return nil, repo_model.ErrReachLimitOfRepo{
  59. Limit: owner.MaxRepoCreation,
  60. }
  61. }
  62. forkedRepo, err := repo_model.GetUserFork(ctx, opts.BaseRepo.ID, owner.ID)
  63. if err != nil {
  64. return nil, err
  65. }
  66. if forkedRepo != nil {
  67. return nil, ErrForkAlreadyExist{
  68. Uname: owner.Name,
  69. RepoName: opts.BaseRepo.FullName(),
  70. ForkName: forkedRepo.FullName(),
  71. }
  72. }
  73. defaultBranch := opts.BaseRepo.DefaultBranch
  74. if opts.SingleBranch != "" {
  75. defaultBranch = opts.SingleBranch
  76. }
  77. repo := &repo_model.Repository{
  78. OwnerID: owner.ID,
  79. Owner: owner,
  80. OwnerName: owner.Name,
  81. Name: opts.Name,
  82. LowerName: strings.ToLower(opts.Name),
  83. Description: opts.Description,
  84. DefaultBranch: defaultBranch,
  85. IsPrivate: opts.BaseRepo.IsPrivate || opts.BaseRepo.Owner.Visibility == structs.VisibleTypePrivate,
  86. IsEmpty: opts.BaseRepo.IsEmpty,
  87. IsFork: true,
  88. ForkID: opts.BaseRepo.ID,
  89. ObjectFormatName: opts.BaseRepo.ObjectFormatName,
  90. Status: repo_model.RepositoryBeingMigrated,
  91. }
  92. // 1 - Create the repository in the database
  93. err = db.WithTx(ctx, func(ctx context.Context) error {
  94. if err = createRepositoryInDB(ctx, doer, owner, repo, true); err != nil {
  95. return err
  96. }
  97. if err = repo_model.IncrementRepoForkNum(ctx, opts.BaseRepo.ID); err != nil {
  98. return err
  99. }
  100. // copy lfs files failure should not be ignored
  101. return git_model.CopyLFS(ctx, repo, opts.BaseRepo)
  102. })
  103. if err != nil {
  104. return nil, err
  105. }
  106. // last - clean up if something goes wrong
  107. // WARNING: Don't override all later err with local variables
  108. defer func() {
  109. if err != nil {
  110. // we can not use the ctx because it maybe canceled or timeout
  111. cleanupRepository(repo.ID)
  112. }
  113. }()
  114. // 2 - check whether the repository with the same storage exists
  115. var isExist bool
  116. isExist, err = gitrepo.IsRepositoryExist(ctx, repo)
  117. if err != nil {
  118. log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err)
  119. return nil, err
  120. }
  121. if isExist {
  122. log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName())
  123. // Don't return directly, we need err in defer to cleanupRepository
  124. err = repo_model.ErrRepoFilesAlreadyExist{
  125. Uname: repo.OwnerName,
  126. Name: repo.Name,
  127. }
  128. return nil, err
  129. }
  130. // 3 - Clone the repository
  131. cloneCmd := gitcmd.NewCommand("clone", "--bare")
  132. if opts.SingleBranch != "" {
  133. cloneCmd.AddArguments("--single-branch", "--branch").AddDynamicArguments(opts.SingleBranch)
  134. }
  135. var stdout []byte
  136. if stdout, _, err = cloneCmd.AddDynamicArguments(opts.BaseRepo.RepoPath(), repo.RepoPath()).
  137. RunStdBytes(ctx, &gitcmd.RunOpts{Timeout: 10 * time.Minute}); err != nil {
  138. log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err)
  139. return nil, fmt.Errorf("git clone: %w", err)
  140. }
  141. // 4 - Update the git repository
  142. if err = updateGitRepoAfterCreate(ctx, repo); err != nil {
  143. return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err)
  144. }
  145. // 5 - Create hooks
  146. if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
  147. return nil, fmt.Errorf("createDelegateHooks: %w", err)
  148. }
  149. // 6 - Sync the repository branches and tags
  150. var gitRepo *git.Repository
  151. gitRepo, err = gitrepo.OpenRepository(ctx, repo)
  152. if err != nil {
  153. return nil, fmt.Errorf("OpenRepository: %w", err)
  154. }
  155. defer gitRepo.Close()
  156. if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doer.ID); err != nil {
  157. return nil, fmt.Errorf("SyncRepoBranchesWithRepo: %w", err)
  158. }
  159. if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
  160. return nil, fmt.Errorf("Sync releases from git tags failed: %v", err)
  161. }
  162. // 7 - Update the repository
  163. // even if below operations failed, it could be ignored. And they will be retried
  164. if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
  165. log.Error("Failed to update size for repository: %v", err)
  166. err = nil
  167. }
  168. if err = repo_model.CopyLanguageStat(ctx, opts.BaseRepo, repo); err != nil {
  169. log.Error("Copy language stat from oldRepo failed: %v", err)
  170. err = nil
  171. }
  172. if err = repo_model.CopyLicense(ctx, opts.BaseRepo, repo); err != nil {
  173. return nil, err
  174. }
  175. // 8 - update repository status to be ready
  176. repo.Status = repo_model.RepositoryReady
  177. if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, repo, "status"); err != nil {
  178. return nil, fmt.Errorf("UpdateRepositoryCols: %w", err)
  179. }
  180. notify_service.ForkRepository(ctx, doer, opts.BaseRepo, repo)
  181. return repo, nil
  182. }
  183. // ConvertForkToNormalRepository convert the provided repo from a forked repo to normal repo
  184. func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Repository) error {
  185. return db.WithTx(ctx, func(ctx context.Context) error {
  186. repo, err := repo_model.GetRepositoryByID(ctx, repo.ID)
  187. if err != nil {
  188. return err
  189. }
  190. if !repo.IsFork {
  191. return nil
  192. }
  193. if err := repo_model.DecrementRepoForkNum(ctx, repo.ForkID); err != nil {
  194. log.Error("Unable to decrement repo fork num for old root repo %d of repository %-v whilst converting from fork. Error: %v", repo.ForkID, repo, err)
  195. return err
  196. }
  197. repo.IsFork = false
  198. repo.ForkID = 0
  199. return repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_fork", "fork_id")
  200. })
  201. }
  202. type findForksOptions struct {
  203. db.ListOptions
  204. RepoID int64
  205. Doer *user_model.User
  206. }
  207. func (opts findForksOptions) ToConds() builder.Cond {
  208. cond := builder.Eq{"fork_id": opts.RepoID}
  209. if opts.Doer != nil && opts.Doer.IsAdmin {
  210. return cond
  211. }
  212. return cond.And(repo_model.AccessibleRepositoryCondition(opts.Doer, unit.TypeInvalid))
  213. }
  214. // FindForks returns all the forks of the repository
  215. func FindForks(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, listOptions db.ListOptions) ([]*repo_model.Repository, int64, error) {
  216. return db.FindAndCount[repo_model.Repository](ctx, findForksOptions{
  217. ListOptions: listOptions,
  218. RepoID: repo.ID,
  219. Doer: doer,
  220. })
  221. }