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  }