aboutsummaryrefslogtreecommitdiffstats
path: root/cmd/dump_repo.go
blob: 0d3970466cd0c1b4ea51d061905b0529ef7fdeb2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package cmd

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strings"

	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/log"
	base "code.gitea.io/gitea/modules/migration"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/structs"
	"code.gitea.io/gitea/modules/util"
	"code.gitea.io/gitea/services/convert"
	"code.gitea.io/gitea/services/migrations"

	"github.com/urfave/cli"
)

// CmdDumpRepository represents the available dump repository sub-command.
var CmdDumpRepository = cli.Command{
	Name:        "dump-repo",
	Usage:       "Dump the repository from git/github/gitea/gitlab",
	Description: "This is a command for dumping the repository data.",
	Action:      runDumpRepository,
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "git_service",
			Value: "",
			Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
		},
		cli.StringFlag{
			Name:  "repo_dir, r",
			Value: "./data",
			Usage: "Repository dir path to store the data",
		},
		cli.StringFlag{
			Name:  "clone_addr",
			Value: "",
			Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL",
		},
		cli.StringFlag{
			Name:  "auth_username",
			Value: "",
			Usage: "The username to visit the clone_addr",
		},
		cli.StringFlag{
			Name:  "auth_password",
			Value: "",
			Usage: "The password to visit the clone_addr",
		},
		cli.StringFlag{
			Name:  "auth_token",
			Value: "",
			Usage: "The personal token to visit the clone_addr",
		},
		cli.StringFlag{
			Name:  "owner_name",
			Value: "",
			Usage: "The data will be stored on a directory with owner name if not empty",
		},
		cli.StringFlag{
			Name:  "repo_name",
			Value: "",
			Usage: "The data will be stored on a directory with repository name if not empty",
		},
		cli.StringFlag{
			Name:  "units",
			Value: "",
			Usage: `Which items will be migrated, one or more units should be separated as comma.
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
		},
	},
}

func runDumpRepository(ctx *cli.Context) error {
	stdCtx, cancel := installSignals()
	defer cancel()

	if err := initDB(stdCtx); err != nil {
		return err
	}

	// migrations.GiteaLocalUploader depends on git module
	if err := git.InitSimple(context.Background()); err != nil {
		return err
	}

	log.Info("AppPath: %s", setting.AppPath)
	log.Info("AppWorkPath: %s", setting.AppWorkPath)
	log.Info("Custom path: %s", setting.CustomPath)
	log.Info("Log path: %s", setting.Log.RootPath)
	log.Info("Configuration file: %s", setting.CustomConf)

	var (
		serviceType structs.GitServiceType
		cloneAddr   = ctx.String("clone_addr")
		serviceStr  = ctx.String("git_service")
	)

	if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") {
		serviceStr = "github"
	} else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://gitlab.com/") {
		serviceStr = "gitlab"
	} else if strings.HasPrefix(strings.ToLower(cloneAddr), "https://gitea.com/") {
		serviceStr = "gitea"
	}
	if serviceStr == "" {
		return errors.New("git_service missed or clone_addr cannot be recognized")
	}
	serviceType = convert.ToGitServiceType(serviceStr)

	opts := base.MigrateOptions{
		GitServiceType: serviceType,
		CloneAddr:      cloneAddr,
		AuthUsername:   ctx.String("auth_username"),
		AuthPassword:   ctx.String("auth_password"),
		AuthToken:      ctx.String("auth_token"),
		RepoName:       ctx.String("repo_name"),
	}

	if len(ctx.String("units")) == 0 {
		opts.Wiki = true
		opts.Issues = true
		opts.Milestones = true
		opts.Labels = true
		opts.Releases = true
		opts.Comments = true
		opts.PullRequests = true
		opts.ReleaseAssets = true
	} else {
		units := strings.Split(ctx.String("units"), ",")
		for _, unit := range units {
			switch strings.ToLower(strings.TrimSpace(unit)) {
			case "":
				continue
			case "wiki":
				opts.Wiki = true
			case "issues":
				opts.Issues = true
			case "milestones":
				opts.Milestones = true
			case "labels":
				opts.Labels = true
			case "releases":
				opts.Releases = true
			case "release_assets":
				opts.ReleaseAssets = true
			case "comments":
				opts.Comments = true
			case "pull_requests":
				opts.PullRequests = true
			default:
				return errors.New("invalid unit: " + unit)
			}
		}
	}

	// the repo_dir will be removed if error occurs in DumpRepository
	// make sure the directory doesn't exist or is empty, prevent from deleting user files
	repoDir := ctx.String("repo_dir")
	if exists, err := util.IsExist(repoDir); err != nil {
		return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err)
	} else if exists {
		if isDir, _ := util.IsDir(repoDir); !isDir {
			return fmt.Errorf("repo_dir %q already exists but it's not a directory", repoDir)
		}
		if dir, _ := os.ReadDir(repoDir); len(dir) > 0 {
			return fmt.Errorf("repo_dir %q is not empty", repoDir)
		}
	}

	if err := migrations.DumpRepository(
		context.Background(),
		repoDir,
		ctx.String("owner_name"),
		opts,
	); err != nil {
		log.Fatal("Failed to dump repository: %v", err)
		return err
	}

	log.Trace("Dump finished!!!")

	return nil
}