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.2KB

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