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.

dump_repo.go 5.1KB

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