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 3.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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. "fmt"
  7. "strings"
  8. "time"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/modules/git"
  11. "code.gitea.io/gitea/modules/log"
  12. "code.gitea.io/gitea/modules/structs"
  13. )
  14. // ForkRepository forks a repository
  15. func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, desc string) (_ *models.Repository, err error) {
  16. forkedRepo, err := oldRepo.GetUserFork(owner.ID)
  17. if err != nil {
  18. return nil, err
  19. }
  20. if forkedRepo != nil {
  21. return nil, models.ErrForkAlreadyExist{
  22. Uname: owner.Name,
  23. RepoName: oldRepo.FullName(),
  24. ForkName: forkedRepo.FullName(),
  25. }
  26. }
  27. repo := &models.Repository{
  28. OwnerID: owner.ID,
  29. Owner: owner,
  30. OwnerName: owner.Name,
  31. Name: name,
  32. LowerName: strings.ToLower(name),
  33. Description: desc,
  34. DefaultBranch: oldRepo.DefaultBranch,
  35. IsPrivate: oldRepo.IsPrivate || oldRepo.Owner.Visibility == structs.VisibleTypePrivate,
  36. IsEmpty: oldRepo.IsEmpty,
  37. IsFork: true,
  38. ForkID: oldRepo.ID,
  39. }
  40. oldRepoPath := oldRepo.RepoPath()
  41. err = models.WithTx(func(ctx models.DBContext) error {
  42. if err = models.CreateRepository(ctx, doer, owner, repo, false); err != nil {
  43. return err
  44. }
  45. rollbackRemoveFn := func() {
  46. if repo.ID == 0 {
  47. return
  48. }
  49. if errDelete := models.DeleteRepository(doer, owner.ID, repo.ID); errDelete != nil {
  50. log.Error("Rollback deleteRepository: %v", errDelete)
  51. }
  52. }
  53. if err = models.IncrementRepoForkNum(ctx, oldRepo.ID); err != nil {
  54. rollbackRemoveFn()
  55. return err
  56. }
  57. // copy lfs files failure should not be ignored
  58. if err := models.CopyLFS(ctx, repo, oldRepo); err != nil {
  59. rollbackRemoveFn()
  60. return err
  61. }
  62. repoPath := models.RepoPath(owner.Name, repo.Name)
  63. if stdout, err := git.NewCommand(
  64. "clone", "--bare", oldRepoPath, repoPath).
  65. SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", oldRepo.FullName(), repo.FullName())).
  66. RunInDirTimeout(10*time.Minute, ""); err != nil {
  67. log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, oldRepo, stdout, err)
  68. rollbackRemoveFn()
  69. return fmt.Errorf("git clone: %v", err)
  70. }
  71. if stdout, err := git.NewCommand("update-server-info").
  72. SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())).
  73. RunInDir(repoPath); err != nil {
  74. log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err)
  75. rollbackRemoveFn()
  76. return fmt.Errorf("git update-server-info: %v", err)
  77. }
  78. if err = createDelegateHooks(repoPath); err != nil {
  79. rollbackRemoveFn()
  80. return fmt.Errorf("createDelegateHooks: %v", err)
  81. }
  82. return nil
  83. })
  84. if err != nil {
  85. return nil, err
  86. }
  87. // even if below operations failed, it could be ignored. And they will be retried
  88. ctx := models.DefaultDBContext()
  89. if err = repo.UpdateSize(ctx); err != nil {
  90. log.Error("Failed to update size for repository: %v", err)
  91. }
  92. if err := models.CopyLanguageStat(oldRepo, repo); err != nil {
  93. log.Error("Copy language stat from oldRepo failed")
  94. }
  95. return repo, nil
  96. }