| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- // Copyright 2024 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package repository
-
- import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "time"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/models/organization"
- repo_model "code.gitea.io/gitea/models/repo"
- unit_model "code.gitea.io/gitea/models/unit"
- user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/git/gitcmd"
- "code.gitea.io/gitea/modules/gitrepo"
- "code.gitea.io/gitea/modules/lfs"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/migration"
- repo_module "code.gitea.io/gitea/modules/repository"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
- )
-
- func cloneWiki(ctx context.Context, u *user_model.User, opts migration.MigrateOptions, migrateTimeout time.Duration) (string, error) {
- wikiPath := repo_model.WikiPath(u.Name, opts.RepoName)
- wikiRemotePath := repo_module.WikiRemoteURL(ctx, opts.CloneAddr)
- if wikiRemotePath == "" {
- return "", nil
- }
-
- if err := util.RemoveAll(wikiPath); err != nil {
- return "", fmt.Errorf("failed to remove existing wiki dir %q, err: %w", wikiPath, err)
- }
-
- cleanIncompleteWikiPath := func() {
- if err := util.RemoveAll(wikiPath); err != nil {
- log.Error("Failed to remove incomplete wiki dir %q, err: %v", wikiPath, err)
- }
- }
- if err := git.Clone(ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{
- Mirror: true,
- Quiet: true,
- Timeout: migrateTimeout,
- SkipTLSVerify: setting.Migrations.SkipTLSVerify,
- }); err != nil {
- log.Error("Clone wiki failed, err: %v", err)
- cleanIncompleteWikiPath()
- return "", err
- }
-
- if err := git.WriteCommitGraph(ctx, wikiPath); err != nil {
- cleanIncompleteWikiPath()
- return "", err
- }
-
- defaultBranch, err := git.GetDefaultBranch(ctx, wikiPath)
- if err != nil {
- cleanIncompleteWikiPath()
- return "", fmt.Errorf("failed to get wiki repo default branch for %q, err: %w", wikiPath, err)
- }
-
- return defaultBranch, nil
- }
-
- // MigrateRepositoryGitData starts migrating git related data after created migrating repository
- func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
- repo *repo_model.Repository, opts migration.MigrateOptions,
- httpTransport *http.Transport,
- ) (*repo_model.Repository, error) {
- repoPath := repo_model.RepoPath(u.Name, opts.RepoName)
-
- if u.IsOrganization() {
- t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx)
- if err != nil {
- return nil, err
- }
- repo.NumWatches = t.NumMembers
- } else {
- repo.NumWatches = 1
- }
-
- migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second
-
- if err := util.RemoveAll(repoPath); err != nil {
- return repo, fmt.Errorf("failed to remove existing repo dir %q, err: %w", repoPath, err)
- }
-
- if err := git.Clone(ctx, opts.CloneAddr, repoPath, git.CloneRepoOptions{
- Mirror: true,
- Quiet: true,
- Timeout: migrateTimeout,
- SkipTLSVerify: setting.Migrations.SkipTLSVerify,
- }); err != nil {
- if errors.Is(err, context.DeadlineExceeded) {
- return repo, fmt.Errorf("clone timed out, consider increasing [git.timeout] MIGRATE in app.ini, underlying err: %w", err)
- }
- return repo, fmt.Errorf("clone error: %w", err)
- }
-
- if err := git.WriteCommitGraph(ctx, repoPath); err != nil {
- return repo, err
- }
-
- if opts.Wiki {
- defaultWikiBranch, err := cloneWiki(ctx, u, opts, migrateTimeout)
- if err != nil {
- return repo, fmt.Errorf("clone wiki error: %w", err)
- }
- repo.DefaultWikiBranch = defaultWikiBranch
- }
-
- if repo.OwnerID == u.ID {
- repo.Owner = u
- }
-
- if err := updateGitRepoAfterCreate(ctx, repo); err != nil {
- return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err)
- }
-
- gitRepo, err := git.OpenRepository(ctx, repoPath)
- if err != nil {
- return repo, fmt.Errorf("OpenRepository: %w", err)
- }
- defer gitRepo.Close()
-
- repo.IsEmpty, err = gitRepo.IsEmpty()
- if err != nil {
- return repo, fmt.Errorf("git.IsEmpty: %w", err)
- }
-
- if !repo.IsEmpty {
- if len(repo.DefaultBranch) == 0 {
- // Try to get HEAD branch and set it as default branch.
- headBranchName, err := git.GetDefaultBranch(ctx, repoPath)
- if err != nil {
- return repo, fmt.Errorf("GetHEADBranch: %w", err)
- }
- if headBranchName != "" {
- repo.DefaultBranch = headBranchName
- }
- }
-
- if _, err := repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil {
- return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err)
- }
-
- // if releases migration are not requested, we will sync all tags here
- // otherwise, the releases sync will be done out of this function
- if !opts.Releases {
- repo.IsMirror = opts.Mirror
- if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
- log.Error("Failed to synchronize tags to releases for repository: %v", err)
- }
- }
-
- if opts.LFS {
- endpoint := lfs.DetermineEndpoint(opts.CloneAddr, opts.LFSEndpoint)
- lfsClient := lfs.NewClient(endpoint, httpTransport)
- if err = repo_module.StoreMissingLfsObjectsInRepository(ctx, repo, gitRepo, lfsClient); err != nil {
- log.Error("Failed to store missing LFS objects for repository: %v", err)
- return repo, fmt.Errorf("StoreMissingLfsObjectsInRepository: %w", err)
- }
- }
-
- // Update repo license
- if err := AddRepoToLicenseUpdaterQueue(&LicenseUpdaterOptions{RepoID: repo.ID}); err != nil {
- log.Error("Failed to add repo to license updater queue: %v", err)
- }
- }
-
- return db.WithTx2(ctx, func(ctx context.Context) (*repo_model.Repository, error) {
- if opts.Mirror {
- remoteAddress, err := util.SanitizeURL(opts.CloneAddr)
- if err != nil {
- return repo, err
- }
- mirrorModel := repo_model.Mirror{
- RepoID: repo.ID,
- Interval: setting.Mirror.DefaultInterval,
- EnablePrune: true,
- NextUpdateUnix: timeutil.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval),
- LFS: opts.LFS,
- RemoteAddress: remoteAddress,
- }
- if opts.LFS {
- mirrorModel.LFSEndpoint = opts.LFSEndpoint
- }
-
- if opts.MirrorInterval != "" {
- parsedInterval, err := time.ParseDuration(opts.MirrorInterval)
- if err != nil {
- log.Error("Failed to set Interval: %v", err)
- return repo, err
- }
- if parsedInterval == 0 {
- mirrorModel.Interval = 0
- mirrorModel.NextUpdateUnix = 0
- } else if parsedInterval < setting.Mirror.MinInterval {
- err := fmt.Errorf("interval %s is set below Minimum Interval of %s", parsedInterval, setting.Mirror.MinInterval)
- log.Error("Interval: %s is too frequent", opts.MirrorInterval)
- return repo, err
- } else {
- mirrorModel.Interval = parsedInterval
- mirrorModel.NextUpdateUnix = timeutil.TimeStampNow().AddDuration(parsedInterval)
- }
- }
-
- if err = repo_model.InsertMirror(ctx, &mirrorModel); err != nil {
- return repo, fmt.Errorf("InsertOne: %w", err)
- }
-
- repo.IsMirror = true
- if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "num_watches", "is_empty", "default_branch", "default_wiki_branch", "is_mirror"); err != nil {
- return nil, err
- }
-
- if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
- log.Error("Failed to update size for repository: %v", err)
- }
-
- // this is necessary for sync local tags from remote
- configName := fmt.Sprintf("remote.%s.fetch", mirrorModel.GetRemoteName())
- if stdout, _, err := gitcmd.NewCommand("config").
- AddOptionValues("--add", configName, `+refs/tags/*:refs/tags/*`).
- RunStdString(ctx, &gitcmd.RunOpts{Dir: repoPath}); err != nil {
- log.Error("MigrateRepositoryGitData(git config --add <remote> +refs/tags/*:refs/tags/*) in %v: Stdout: %s\nError: %v", repo, stdout, err)
- return repo, fmt.Errorf("error in MigrateRepositoryGitData(git config --add <remote> +refs/tags/*:refs/tags/*): %w", err)
- }
- } else {
- if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
- log.Error("Failed to update size for repository: %v", err)
- }
- if repo, err = CleanUpMigrateInfo(ctx, repo); err != nil {
- return nil, err
- }
- }
-
- var enableRepoUnits []repo_model.RepoUnit
- if opts.Releases && !unit_model.TypeReleases.UnitGlobalDisabled() {
- enableRepoUnits = append(enableRepoUnits, repo_model.RepoUnit{RepoID: repo.ID, Type: unit_model.TypeReleases})
- }
- if opts.Wiki && !unit_model.TypeWiki.UnitGlobalDisabled() {
- enableRepoUnits = append(enableRepoUnits, repo_model.RepoUnit{RepoID: repo.ID, Type: unit_model.TypeWiki})
- }
- if len(enableRepoUnits) > 0 {
- err = UpdateRepositoryUnits(ctx, repo, enableRepoUnits, nil)
- if err != nil {
- return nil, err
- }
- }
- return repo, nil
- })
- }
-
- // CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors.
- func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) {
- if err := gitrepo.CreateDelegateHooks(ctx, repo); err != nil {
- return repo, fmt.Errorf("createDelegateHooks: %w", err)
- }
-
- hasWiki := HasWiki(ctx, repo)
- if hasWiki {
- if err := gitrepo.CreateDelegateHooks(ctx, repo.WikiStorageRepo()); err != nil {
- return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err)
- }
- }
-
- err := gitrepo.GitRemoteRemove(ctx, repo, "origin")
- if err != nil && !git.IsRemoteNotExistError(err) {
- return repo, fmt.Errorf("CleanUpMigrateInfo: %w", err)
- }
-
- if hasWiki {
- err = gitrepo.GitRemoteRemove(ctx, repo.WikiStorageRepo(), "origin")
- if err != nil && !git.IsRemoteNotExistError(err) {
- return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %w", err)
- }
- }
-
- return repo, UpdateRepository(ctx, repo, false)
- }
|