code.gitea.io/gitea@v1.21.7/cmd/dump_repo.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package cmd 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "os" 11 "strings" 12 13 "code.gitea.io/gitea/modules/git" 14 "code.gitea.io/gitea/modules/log" 15 base "code.gitea.io/gitea/modules/migration" 16 "code.gitea.io/gitea/modules/setting" 17 "code.gitea.io/gitea/modules/structs" 18 "code.gitea.io/gitea/modules/util" 19 "code.gitea.io/gitea/services/convert" 20 "code.gitea.io/gitea/services/migrations" 21 22 "github.com/urfave/cli/v2" 23 ) 24 25 // CmdDumpRepository represents the available dump repository sub-command. 26 var CmdDumpRepository = &cli.Command{ 27 Name: "dump-repo", 28 Usage: "Dump the repository from git/github/gitea/gitlab", 29 Description: "This is a command for dumping the repository data.", 30 Action: runDumpRepository, 31 Flags: []cli.Flag{ 32 &cli.StringFlag{ 33 Name: "git_service", 34 Value: "", 35 Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.", 36 }, 37 &cli.StringFlag{ 38 Name: "repo_dir", 39 Aliases: []string{"r"}, 40 Value: "./data", 41 Usage: "Repository dir path to store the data", 42 }, 43 &cli.StringFlag{ 44 Name: "clone_addr", 45 Value: "", 46 Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL", 47 }, 48 &cli.StringFlag{ 49 Name: "auth_username", 50 Value: "", 51 Usage: "The username to visit the clone_addr", 52 }, 53 &cli.StringFlag{ 54 Name: "auth_password", 55 Value: "", 56 Usage: "The password to visit the clone_addr", 57 }, 58 &cli.StringFlag{ 59 Name: "auth_token", 60 Value: "", 61 Usage: "The personal token to visit the clone_addr", 62 }, 63 &cli.StringFlag{ 64 Name: "owner_name", 65 Value: "", 66 Usage: "The data will be stored on a directory with owner name if not empty", 67 }, 68 &cli.StringFlag{ 69 Name: "repo_name", 70 Value: "", 71 Usage: "The data will be stored on a directory with repository name if not empty", 72 }, 73 &cli.StringFlag{ 74 Name: "units", 75 Value: "", 76 Usage: `Which items will be migrated, one or more units should be separated as comma. 77 wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`, 78 }, 79 }, 80 } 81 82 func runDumpRepository(ctx *cli.Context) error { 83 stdCtx, cancel := installSignals() 84 defer cancel() 85 86 if err := initDB(stdCtx); err != nil { 87 return err 88 } 89 90 // migrations.GiteaLocalUploader depends on git module 91 if err := git.InitSimple(context.Background()); err != nil { 92 return err 93 } 94 95 log.Info("AppPath: %s", setting.AppPath) 96 log.Info("AppWorkPath: %s", setting.AppWorkPath) 97 log.Info("Custom path: %s", setting.CustomPath) 98 log.Info("Log path: %s", setting.Log.RootPath) 99 log.Info("Configuration file: %s", setting.CustomConf) 100 101 var ( 102 serviceType structs.GitServiceType 103 cloneAddr = ctx.String("clone_addr") 104 serviceStr = ctx.String("git_service") 105 ) 106 107 if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") { 108 serviceStr = "github" 109 } else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://gitlab.com/") { 110 serviceStr = "gitlab" 111 } else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://gitea.com/") { 112 serviceStr = "gitea" 113 } 114 if serviceStr == "" { 115 return errors.New("git_service missed or clone_addr cannot be recognized") 116 } 117 serviceType = convert.ToGitServiceType(serviceStr) 118 119 opts := base.MigrateOptions{ 120 GitServiceType: serviceType, 121 CloneAddr: cloneAddr, 122 AuthUsername: ctx.String("auth_username"), 123 AuthPassword: ctx.String("auth_password"), 124 AuthToken: ctx.String("auth_token"), 125 RepoName: ctx.String("repo_name"), 126 } 127 128 if len(ctx.String("units")) == 0 { 129 opts.Wiki = true 130 opts.Issues = true 131 opts.Milestones = true 132 opts.Labels = true 133 opts.Releases = true 134 opts.Comments = true 135 opts.PullRequests = true 136 opts.ReleaseAssets = true 137 } else { 138 units := strings.Split(ctx.String("units"), ",") 139 for _, unit := range units { 140 switch strings.ToLower(strings.TrimSpace(unit)) { 141 case "": 142 continue 143 case "wiki": 144 opts.Wiki = true 145 case "issues": 146 opts.Issues = true 147 case "milestones": 148 opts.Milestones = true 149 case "labels": 150 opts.Labels = true 151 case "releases": 152 opts.Releases = true 153 case "release_assets": 154 opts.ReleaseAssets = true 155 case "comments": 156 opts.Comments = true 157 case "pull_requests": 158 opts.PullRequests = true 159 default: 160 return errors.New("invalid unit: " + unit) 161 } 162 } 163 } 164 165 // the repo_dir will be removed if error occurs in DumpRepository 166 // make sure the directory doesn't exist or is empty, prevent from deleting user files 167 repoDir := ctx.String("repo_dir") 168 if exists, err := util.IsExist(repoDir); err != nil { 169 return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err) 170 } else if exists { 171 if isDir, _ := util.IsDir(repoDir); !isDir { 172 return fmt.Errorf("repo_dir %q already exists but it's not a directory", repoDir) 173 } 174 if dir, _ := os.ReadDir(repoDir); len(dir) > 0 { 175 return fmt.Errorf("repo_dir %q is not empty", repoDir) 176 } 177 } 178 179 if err := migrations.DumpRepository( 180 context.Background(), 181 repoDir, 182 ctx.String("owner_name"), 183 opts, 184 ); err != nil { 185 log.Fatal("Failed to dump repository: %v", err) 186 return err 187 } 188 189 log.Trace("Dump finished!!!") 190 191 return nil 192 }