gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repository
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "net/http"
  9. "time"
  10. "code.gitea.io/gitea/models/db"
  11. "code.gitea.io/gitea/models/organization"
  12. repo_model "code.gitea.io/gitea/models/repo"
  13. unit_model "code.gitea.io/gitea/models/unit"
  14. user_model "code.gitea.io/gitea/models/user"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/git/gitcmd"
  17. "code.gitea.io/gitea/modules/gitrepo"
  18. "code.gitea.io/gitea/modules/lfs"
  19. "code.gitea.io/gitea/modules/log"
  20. "code.gitea.io/gitea/modules/migration"
  21. repo_module "code.gitea.io/gitea/modules/repository"
  22. "code.gitea.io/gitea/modules/setting"
  23. "code.gitea.io/gitea/modules/timeutil"
  24. "code.gitea.io/gitea/modules/util"
  25. )
  26. func cloneWiki(ctx context.Context, u *user_model.User, opts migration.MigrateOptions, migrateTimeout time.Duration) (string, error) {
  27. wikiPath := repo_model.WikiPath(u.Name, opts.RepoName)
  28. wikiRemotePath := repo_module.WikiRemoteURL(ctx, opts.CloneAddr)
  29. if wikiRemotePath == "" {
  30. return "", nil
  31. }
  32. if err := util.RemoveAll(wikiPath); err != nil {
  33. return "", fmt.Errorf("failed to remove existing wiki dir %q, err: %w", wikiPath, err)
  34. }
  35. cleanIncompleteWikiPath := func() {
  36. if err := util.RemoveAll(wikiPath); err != nil {
  37. log.Error("Failed to remove incomplete wiki dir %q, err: %v", wikiPath, err)
  38. }
  39. }
  40. if err := git.Clone(ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{
  41. Mirror: true,
  42. Quiet: true,
  43. Timeout: migrateTimeout,
  44. SkipTLSVerify: setting.Migrations.SkipTLSVerify,
  45. }); err != nil {
  46. log.Error("Clone wiki failed, err: %v", err)
  47. cleanIncompleteWikiPath()
  48. return "", err
  49. }
  50. if err := git.WriteCommitGraph(ctx, wikiPath); err != nil {
  51. cleanIncompleteWikiPath()
  52. return "", err
  53. }
  54. defaultBranch, err := git.GetDefaultBranch(ctx, wikiPath)
  55. if err != nil {
  56. cleanIncompleteWikiPath()
  57. return "", fmt.Errorf("failed to get wiki repo default branch for %q, err: %w", wikiPath, err)
  58. }
  59. return defaultBranch, nil
  60. }
  61. // MigrateRepositoryGitData starts migrating git related data after created migrating repository
  62. func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
  63. repo *repo_model.Repository, opts migration.MigrateOptions,
  64. httpTransport *http.Transport,
  65. ) (*repo_model.Repository, error) {
  66. repoPath := repo_model.RepoPath(u.Name, opts.RepoName)
  67. if u.IsOrganization() {
  68. t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx)
  69. if err != nil {
  70. return nil, err
  71. }
  72. repo.NumWatches = t.NumMembers
  73. } else {
  74. repo.NumWatches = 1
  75. }
  76. migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second
  77. if err := util.RemoveAll(repoPath); err != nil {
  78. return repo, fmt.Errorf("failed to remove existing repo dir %q, err: %w", repoPath, err)
  79. }
  80. if err := git.Clone(ctx, opts.CloneAddr, repoPath, git.CloneRepoOptions{
  81. Mirror: true,
  82. Quiet: true,
  83. Timeout: migrateTimeout,
  84. SkipTLSVerify: setting.Migrations.SkipTLSVerify,
  85. }); err != nil {
  86. if errors.Is(err, context.DeadlineExceeded) {
  87. return repo, fmt.Errorf("clone timed out, consider increasing [git.timeout] MIGRATE in app.ini, underlying err: %w", err)
  88. }
  89. return repo, fmt.Errorf("clone error: %w", err)
  90. }
  91. if err := git.WriteCommitGraph(ctx, repoPath); err != nil {
  92. return repo, err
  93. }
  94. if opts.Wiki {
  95. defaultWikiBranch, err := cloneWiki(ctx, u, opts, migrateTimeout)
  96. if err != nil {
  97. return repo, fmt.Errorf("clone wiki error: %w", err)
  98. }
  99. repo.DefaultWikiBranch = defaultWikiBranch
  100. }
  101. if repo.OwnerID == u.ID {
  102. repo.Owner = u
  103. }
  104. if err := updateGitRepoAfterCreate(ctx, repo); err != nil {
  105. return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err)
  106. }
  107. gitRepo, err := git.OpenRepository(ctx, repoPath)
  108. if err != nil {
  109. return repo, fmt.Errorf("OpenRepository: %w", err)
  110. }
  111. defer gitRepo.Close()
  112. repo.IsEmpty, err = gitRepo.IsEmpty()
  113. if err != nil {
  114. return repo, fmt.Errorf("git.IsEmpty: %w", err)
  115. }
  116. if !repo.IsEmpty {
  117. if len(repo.DefaultBranch) == 0 {
  118. // Try to get HEAD branch and set it as default branch.
  119. headBranchName, err := git.GetDefaultBranch(ctx, repoPath)
  120. if err != nil {
  121. return repo, fmt.Errorf("GetHEADBranch: %w", err)
  122. }
  123. if headBranchName != "" {
  124. repo.DefaultBranch = headBranchName
  125. }
  126. }
  127. if _, err := repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil {
  128. return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err)
  129. }
  130. // if releases migration are not requested, we will sync all tags here
  131. // otherwise, the releases sync will be done out of this function
  132. if !opts.Releases {
  133. repo.IsMirror = opts.Mirror
  134. if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
  135. log.Error("Failed to synchronize tags to releases for repository: %v", err)
  136. }
  137. }
  138. if opts.LFS {
  139. endpoint := lfs.DetermineEndpoint(opts.CloneAddr, opts.LFSEndpoint)
  140. lfsClient := lfs.NewClient(endpoint, httpTransport)
  141. if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, repo, gitRepo, lfsClient); err != nil {
  142. log.Error("Failed to store missing LFS objects for repository: %v", err)
  143. return repo, fmt.Errorf("StoreMissingLfsObjectsInRepository: %w", err)
  144. }
  145. }
  146. // Update repo license
  147. if err := AddRepoToLicenseUpdaterQueue(&LicenseUpdaterOptions{RepoID: repo.ID}); err != nil {
  148. log.Error("Failed to add repo to license updater queue: %v", err)
  149. }
  150. }
  151. return db.WithTx2(ctx, func(ctx context.Context) (*repo_model.Repository, error) {
  152. if opts.Mirror {
  153. remoteAddress, err := util.SanitizeURL(opts.CloneAddr)
  154. if err != nil {
  155. return repo, err
  156. }
  157. mirrorModel := repo_model.Mirror{
  158. RepoID: repo.ID,
  159. Interval: setting.Mirror.DefaultInterval,
  160. EnablePrune: true,
  161. NextUpdateUnix: timeutil.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval),
  162. LFS: opts.LFS,
  163. RemoteAddress: remoteAddress,
  164. }
  165. if opts.LFS {
  166. mirrorModel.LFSEndpoint = opts.LFSEndpoint
  167. }
  168. if opts.MirrorInterval != "" {
  169. parsedInterval, err := time.ParseDuration(opts.MirrorInterval)
  170. if err != nil {
  171. log.Error("Failed to set Interval: %v", err)
  172. return repo, err
  173. }
  174. if parsedInterval == 0 {
  175. mirrorModel.Interval = 0
  176. mirrorModel.NextUpdateUnix = 0
  177. } else if parsedInterval < setting.Mirror.MinInterval {
  178. err := fmt.Errorf("interval %s is set below Minimum Interval of %s", parsedInterval, setting.Mirror.MinInterval)
  179. log.Error("Interval: %s is too frequent", opts.MirrorInterval)
  180. return repo, err
  181. } else {
  182. mirrorModel.Interval = parsedInterval
  183. mirrorModel.NextUpdateUnix = timeutil.TimeStampNow().AddDuration(parsedInterval)
  184. }
  185. }
  186. if err = repo_model.InsertMirror(ctx, &mirrorModel); err != nil {
  187. return repo, fmt.Errorf("InsertOne: %w", err)
  188. }
  189. repo.IsMirror = true
  190. if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "num_watches", "is_empty", "default_branch", "default_wiki_branch", "is_mirror"); err != nil {
  191. return nil, err
  192. }
  193. if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
  194. log.Error("Failed to update size for repository: %v", err)
  195. }
  196. // this is necessary for sync local tags from remote
  197. configName := fmt.Sprintf("remote.%s.fetch", mirrorModel.GetRemoteName())
  198. if stdout, _, err := gitcmd.NewCommand("config").
  199. AddOptionValues("--add", configName, `+refs/tags/*:refs/tags/*`).
  200. RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}); err != nil {
  201. log.Error("MigrateRepositoryGitData(git config --add <remote> +refs/tags/*:refs/tags/*) in %v: Stdout: %s\nError: %v", repo, stdout, err)
  202. return repo, fmt.Errorf("error in MigrateRepositoryGitData(git config --add <remote> +refs/tags/*:refs/tags/*): %w", err)
  203. }
  204. } else {
  205. if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
  206. log.Error("Failed to update size for repository: %v", err)
  207. }
  208. if repo, err = CleanUpMigrateInfo(ctx, repo); err != nil {
  209. return nil, err
  210. }
  211. }
  212. var enableRepoUnits []repo_model.RepoUnit
  213. if opts.Releases && !unit_model.TypeReleases.UnitGlobalDisabled() {
  214. enableRepoUnits = append(enableRepoUnits, repo_model.RepoUnit{RepoID: repo.ID, Type: unit_model.TypeReleases})
  215. }
  216. if opts.Wiki && !unit_model.TypeWiki.UnitGlobalDisabled() {
  217. enableRepoUnits = append(enableRepoUnits, repo_model.RepoUnit{RepoID: repo.ID, Type: unit_model.TypeWiki})
  218. }
  219. if len(enableRepoUnits) > 0 {
  220. err = UpdateRepositoryUnits(ctx, repo, enableRepoUnits, nil)
  221. if err != nil {
  222. return nil, err
  223. }
  224. }
  225. return repo, nil
  226. })
  227. }
  228. // CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors.
  229. func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) {
  230. if err := gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
  231. return repo, fmt.Errorf("createDelegateHooks: %w", err)
  232. }
  233. hasWiki := HasWiki(ctx, repo)
  234. if hasWiki {
  235. if err := gitrepo.CreateDelegateHooks(ctx, repo.WikiStorageRepo()); err != nil {
  236. return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err)
  237. }
  238. }
  239. err := gitrepo.GitRemoteRemove(ctx, repo, "origin")
  240. if err != nil && !git.IsRemoteNotExistError(err) {
  241. return repo, fmt.Errorf("CleanUpMigrateInfo: %w", err)
  242. }
  243. if hasWiki {
  244. err = gitrepo.GitRemoteRemove(ctx, repo.WikiStorageRepo(), "origin")
  245. if err != nil && !git.IsRemoteNotExistError(err) {
  246. return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %w", err)
  247. }
  248. }
  249. return repo, UpdateRepository(ctx, repo, false)
  250. }