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.

fork.go 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. // Copyright 2019 The Gitea 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 repository
  5. import (
  6. "context"
  7. "fmt"
  8. "strings"
  9. "time"
  10. "code.gitea.io/gitea/models"
  11. "code.gitea.io/gitea/models/db"
  12. git_model "code.gitea.io/gitea/models/git"
  13. repo_model "code.gitea.io/gitea/models/repo"
  14. user_model "code.gitea.io/gitea/models/user"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/notification"
  18. repo_module "code.gitea.io/gitea/modules/repository"
  19. "code.gitea.io/gitea/modules/structs"
  20. "code.gitea.io/gitea/modules/util"
  21. )
  22. // ForkRepoOptions contains the fork repository options
  23. type ForkRepoOptions struct {
  24. BaseRepo *repo_model.Repository
  25. Name string
  26. Description string
  27. }
  28. // ForkRepository forks a repository
  29. func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts ForkRepoOptions) (*repo_model.Repository, error) {
  30. forkedRepo, err := repo_model.GetUserFork(ctx, opts.BaseRepo.ID, owner.ID)
  31. if err != nil {
  32. return nil, err
  33. }
  34. if forkedRepo != nil {
  35. return nil, models.ErrForkAlreadyExist{
  36. Uname: owner.Name,
  37. RepoName: opts.BaseRepo.FullName(),
  38. ForkName: forkedRepo.FullName(),
  39. }
  40. }
  41. repo := &repo_model.Repository{
  42. OwnerID: owner.ID,
  43. Owner: owner,
  44. OwnerName: owner.Name,
  45. Name: opts.Name,
  46. LowerName: strings.ToLower(opts.Name),
  47. Description: opts.Description,
  48. DefaultBranch: opts.BaseRepo.DefaultBranch,
  49. IsPrivate: opts.BaseRepo.IsPrivate || opts.BaseRepo.Owner.Visibility == structs.VisibleTypePrivate,
  50. IsEmpty: opts.BaseRepo.IsEmpty,
  51. IsFork: true,
  52. ForkID: opts.BaseRepo.ID,
  53. }
  54. oldRepoPath := opts.BaseRepo.RepoPath()
  55. needsRollback := false
  56. rollbackFn := func() {
  57. if !needsRollback {
  58. return
  59. }
  60. repoPath := repo_model.RepoPath(owner.Name, repo.Name)
  61. if exists, _ := util.IsExist(repoPath); !exists {
  62. return
  63. }
  64. // As the transaction will be failed and hence database changes will be destroyed we only need
  65. // to delete the related repository on the filesystem
  66. if errDelete := util.RemoveAll(repoPath); errDelete != nil {
  67. log.Error("Failed to remove fork repo")
  68. }
  69. }
  70. needsRollbackInPanic := true
  71. defer func() {
  72. panicErr := recover()
  73. if panicErr == nil {
  74. return
  75. }
  76. if needsRollbackInPanic {
  77. rollbackFn()
  78. }
  79. panic(panicErr)
  80. }()
  81. err = db.WithTx(func(txCtx context.Context) error {
  82. if err = models.CreateRepository(txCtx, doer, owner, repo, false); err != nil {
  83. return err
  84. }
  85. if err = repo_model.IncrementRepoForkNum(txCtx, opts.BaseRepo.ID); err != nil {
  86. return err
  87. }
  88. // copy lfs files failure should not be ignored
  89. if err = git_model.CopyLFS(txCtx, repo, opts.BaseRepo); err != nil {
  90. return err
  91. }
  92. needsRollback = true
  93. repoPath := repo_model.RepoPath(owner.Name, repo.Name)
  94. if stdout, _, err := git.NewCommand(txCtx,
  95. "clone", "--bare", oldRepoPath, repoPath).
  96. SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", opts.BaseRepo.FullName(), repo.FullName())).
  97. RunStdBytes(&git.RunOpts{Timeout: 10 * time.Minute}); err != nil {
  98. log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err)
  99. return fmt.Errorf("git clone: %v", err)
  100. }
  101. if err := repo_module.CheckDaemonExportOK(txCtx, repo); err != nil {
  102. return fmt.Errorf("checkDaemonExportOK: %v", err)
  103. }
  104. if stdout, _, err := git.NewCommand(txCtx, "update-server-info").
  105. SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())).
  106. RunStdString(&git.RunOpts{Dir: repoPath}); err != nil {
  107. log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err)
  108. return fmt.Errorf("git update-server-info: %v", err)
  109. }
  110. if err = repo_module.CreateDelegateHooks(repoPath); err != nil {
  111. return fmt.Errorf("createDelegateHooks: %v", err)
  112. }
  113. return nil
  114. })
  115. needsRollbackInPanic = false
  116. if err != nil {
  117. rollbackFn()
  118. return nil, err
  119. }
  120. // even if below operations failed, it could be ignored. And they will be retried
  121. if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
  122. log.Error("Failed to update size for repository: %v", err)
  123. }
  124. if err := repo_model.CopyLanguageStat(opts.BaseRepo, repo); err != nil {
  125. log.Error("Copy language stat from oldRepo failed: %v", err)
  126. }
  127. gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
  128. if err != nil {
  129. log.Error("Open created git repository failed: %v", err)
  130. } else {
  131. defer gitRepo.Close()
  132. if err := repo_module.SyncReleasesWithTags(repo, gitRepo); err != nil {
  133. log.Error("Sync releases from git tags failed: %v", err)
  134. }
  135. }
  136. notification.NotifyForkRepository(doer, opts.BaseRepo, repo)
  137. return repo, nil
  138. }
  139. // ConvertForkToNormalRepository convert the provided repo from a forked repo to normal repo
  140. func ConvertForkToNormalRepository(repo *repo_model.Repository) error {
  141. err := db.WithTx(func(ctx context.Context) error {
  142. repo, err := repo_model.GetRepositoryByIDCtx(ctx, repo.ID)
  143. if err != nil {
  144. return err
  145. }
  146. if !repo.IsFork {
  147. return nil
  148. }
  149. if err := repo_model.DecrementRepoForkNum(ctx, repo.ForkID); err != nil {
  150. 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)
  151. return err
  152. }
  153. repo.IsFork = false
  154. repo.ForkID = 0
  155. if err := repo_module.UpdateRepository(ctx, repo, false); err != nil {
  156. log.Error("Unable to update repository %-v whilst converting from fork. Error: %v", repo, err)
  157. return err
  158. }
  159. return nil
  160. })
  161. return err
  162. }