summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--cmd/restore_repo.go5
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--integrations/dump_restore_test.go2
-rw-r--r--modules/migration/file_format.go112
-rw-r--r--modules/migration/file_format_test.go39
-rw-r--r--modules/migration/file_format_testdata/issue_a.json14
-rw-r--r--modules/migration/file_format_testdata/issue_a.yml10
-rw-r--r--modules/migration/file_format_testdata/issue_b.json5
-rw-r--r--modules/migration/file_format_testdata/milestones.json20
-rw-r--r--modules/migration/issue.go32
-rw-r--r--modules/migration/label.go6
-rw-r--r--modules/migration/milestone.go14
-rw-r--r--modules/migration/reaction.go6
-rw-r--r--modules/migration/schemas/issue.json114
-rw-r--r--modules/migration/schemas/label.json28
-rw-r--r--modules/migration/schemas/milestone.json67
-rw-r--r--modules/migration/schemas/reaction.json29
-rw-r--r--modules/migration/schemas_bindata.go10
-rw-r--r--modules/migration/schemas_dynamic.go40
-rw-r--r--modules/migration/schemas_static.go17
-rw-r--r--modules/private/restore_repo.go20
-rw-r--r--routers/private/restore_repo.go10
-rw-r--r--services/migrations/dump.go4
-rw-r--r--services/migrations/restore.go42
26 files changed, 577 insertions, 74 deletions
diff --git a/.gitignore b/.gitignore
index 98975fab49..eab92b49ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,8 @@ _testmain.go
coverage.all
cpu.out
+/modules/migration/bindata.go
+/modules/migration/bindata.go.hash
/modules/options/bindata.go
/modules/options/bindata.go.hash
/modules/public/bindata.go
diff --git a/cmd/restore_repo.go b/cmd/restore_repo.go
index 357bd92c77..f0b01e7984 100644
--- a/cmd/restore_repo.go
+++ b/cmd/restore_repo.go
@@ -43,6 +43,10 @@ var CmdRestoreRepository = cli.Command{
Usage: `Which items will be restored, 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.`,
},
+ cli.BoolFlag{
+ Name: "validation",
+ Usage: "Sanity check the content of the files before trying to load them",
+ },
},
}
@@ -58,6 +62,7 @@ func runRestoreRepository(c *cli.Context) error {
c.String("owner_name"),
c.String("repo_name"),
c.StringSlice("units"),
+ c.Bool("validation"),
)
if statusCode == http.StatusOK {
return nil
diff --git a/go.mod b/go.mod
index 7266acb751..9ca1429d11 100644
--- a/go.mod
+++ b/go.mod
@@ -97,6 +97,7 @@ require (
github.com/quasoft/websspi v1.0.0
github.com/rs/xid v1.3.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect
github.com/sergi/go-diff v1.2.0
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546
diff --git a/go.sum b/go.sum
index 898a73dd88..f632614fbc 100644
--- a/go.sum
+++ b/go.sum
@@ -1039,6 +1039,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
+github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
diff --git a/integrations/dump_restore_test.go b/integrations/dump_restore_test.go
index 053ca47855..c0e583293c 100644
--- a/integrations/dump_restore_test.go
+++ b/integrations/dump_restore_test.go
@@ -81,7 +81,7 @@ func TestDumpRestore(t *testing.T) {
//
newreponame := "restoredrepo"
- err = migrations.RestoreRepository(ctx, d, repo.OwnerName, newreponame, []string{"labels", "milestones", "issues", "comments"})
+ err = migrations.RestoreRepository(ctx, d, repo.OwnerName, newreponame, []string{"labels", "milestones", "issues", "comments"}, false)
assert.NoError(t, err)
newrepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: newreponame}).(*repo_model.Repository)
diff --git a/modules/migration/file_format.go b/modules/migration/file_format.go
new file mode 100644
index 0000000000..30e1d256cd
--- /dev/null
+++ b/modules/migration/file_format.go
@@ -0,0 +1,112 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migration
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/log"
+
+ "github.com/santhosh-tekuri/jsonschema/v5"
+ "gopkg.in/yaml.v2"
+)
+
+// Load project data from file, with optional validation
+func Load(filename string, data interface{}, validation bool) error {
+ isJSON := strings.HasSuffix(filename, ".json")
+
+ bs, err := os.ReadFile(filename)
+ if err != nil {
+ return err
+ }
+
+ if validation {
+ err := validate(bs, data, isJSON)
+ if err != nil {
+ return err
+ }
+ }
+ return unmarshal(bs, data, isJSON)
+}
+
+func unmarshal(bs []byte, data interface{}, isJSON bool) error {
+ if isJSON {
+ return json.Unmarshal(bs, data)
+ }
+ return yaml.Unmarshal(bs, data)
+}
+
+func getSchema(filename string) (*jsonschema.Schema, error) {
+ c := jsonschema.NewCompiler()
+ c.LoadURL = openSchema
+ return c.Compile(filename)
+}
+
+func validate(bs []byte, datatype interface{}, isJSON bool) error {
+ var v interface{}
+ err := unmarshal(bs, &v, isJSON)
+ if err != nil {
+ return err
+ }
+ if !isJSON {
+ v, err = toStringKeys(v)
+ if err != nil {
+ return err
+ }
+ }
+
+ var schemaFilename string
+ switch datatype := datatype.(type) {
+ case *[]*Issue:
+ schemaFilename = "issue.json"
+ case *[]*Milestone:
+ schemaFilename = "milestone.json"
+ default:
+ return fmt.Errorf("file_format:validate: %T has not a validation implemented", datatype)
+ }
+
+ sch, err := getSchema(schemaFilename)
+ if err != nil {
+ return err
+ }
+ err = sch.Validate(v)
+ if err != nil {
+ log.Error("migration validation with %s failed for\n%s", schemaFilename, string(bs))
+ }
+ return err
+}
+
+func toStringKeys(val interface{}) (interface{}, error) {
+ var err error
+ switch val := val.(type) {
+ case map[interface{}]interface{}:
+ m := make(map[string]interface{})
+ for k, v := range val {
+ k, ok := k.(string)
+ if !ok {
+ return nil, fmt.Errorf("found non-string key %T %s", k, k)
+ }
+ m[k], err = toStringKeys(v)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return m, nil
+ case []interface{}:
+ l := make([]interface{}, len(val))
+ for i, v := range val {
+ l[i], err = toStringKeys(v)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return l, nil
+ default:
+ return val, nil
+ }
+}
diff --git a/modules/migration/file_format_test.go b/modules/migration/file_format_test.go
new file mode 100644
index 0000000000..27104e209c
--- /dev/null
+++ b/modules/migration/file_format_test.go
@@ -0,0 +1,39 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migration
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/santhosh-tekuri/jsonschema/v5"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMigrationJSON_IssueOK(t *testing.T) {
+ issues := make([]*Issue, 0, 10)
+ err := Load("file_format_testdata/issue_a.json", &issues, true)
+ assert.NoError(t, err)
+ err = Load("file_format_testdata/issue_a.yml", &issues, true)
+ assert.NoError(t, err)
+}
+
+func TestMigrationJSON_IssueFail(t *testing.T) {
+ issues := make([]*Issue, 0, 10)
+ err := Load("file_format_testdata/issue_b.json", &issues, true)
+ if _, ok := err.(*jsonschema.ValidationError); ok {
+ errors := strings.Split(err.(*jsonschema.ValidationError).GoString(), "\n")
+ assert.Contains(t, errors[1], "missing properties")
+ assert.Contains(t, errors[1], "poster_id")
+ } else {
+ t.Fatalf("got: type %T with value %s, want: *jsonschema.ValidationError", err, err)
+ }
+}
+
+func TestMigrationJSON_MilestoneOK(t *testing.T) {
+ milestones := make([]*Milestone, 0, 10)
+ err := Load("file_format_testdata/milestones.json", &milestones, true)
+ assert.NoError(t, err)
+}
diff --git a/modules/migration/file_format_testdata/issue_a.json b/modules/migration/file_format_testdata/issue_a.json
new file mode 100644
index 0000000000..33d7759f63
--- /dev/null
+++ b/modules/migration/file_format_testdata/issue_a.json
@@ -0,0 +1,14 @@
+[
+ {
+ "number": 1,
+ "poster_id": 1,
+ "poster_name": "name_a",
+ "title": "title_a",
+ "content": "content_a",
+ "state": "closed",
+ "is_locked": false,
+ "created": "1985-04-12T23:20:50.52Z",
+ "updated": "1986-04-12T23:20:50.52Z",
+ "closed": "1987-04-12T23:20:50.52Z"
+ }
+]
diff --git a/modules/migration/file_format_testdata/issue_a.yml b/modules/migration/file_format_testdata/issue_a.yml
new file mode 100644
index 0000000000..d03bfb31f2
--- /dev/null
+++ b/modules/migration/file_format_testdata/issue_a.yml
@@ -0,0 +1,10 @@
+- number: 1
+ poster_id: 1
+ poster_name: name_a
+ title: title_a
+ content: content_a
+ state: closed
+ is_locked: false
+ created: 2021-05-27T15:24:13+02:00
+ updated: 2021-11-11T10:52:45+01:00
+ closed: 2021-11-11T10:52:45+01:00
diff --git a/modules/migration/file_format_testdata/issue_b.json b/modules/migration/file_format_testdata/issue_b.json
new file mode 100644
index 0000000000..2a824d42fd
--- /dev/null
+++ b/modules/migration/file_format_testdata/issue_b.json
@@ -0,0 +1,5 @@
+[
+ {
+ "number": 1
+ }
+]
diff --git a/modules/migration/file_format_testdata/milestones.json b/modules/migration/file_format_testdata/milestones.json
new file mode 100644
index 0000000000..8fb770d87b
--- /dev/null
+++ b/modules/migration/file_format_testdata/milestones.json
@@ -0,0 +1,20 @@
+[
+ {
+ "title": "title_a",
+ "description": "description_a",
+ "deadline": "1988-04-12T23:20:50.52Z",
+ "created": "1985-04-12T23:20:50.52Z",
+ "updated": "1986-04-12T23:20:50.52Z",
+ "closed": "1987-04-12T23:20:50.52Z",
+ "state": "closed"
+ },
+ {
+ "title": "title_b",
+ "description": "description_b",
+ "deadline": "1998-04-12T23:20:50.52Z",
+ "created": "1995-04-12T23:20:50.52Z",
+ "updated": "1996-04-12T23:20:50.52Z",
+ "closed": null,
+ "state": "open"
+ }
+]
diff --git a/modules/migration/issue.go b/modules/migration/issue.go
index 26812633f9..19781ad984 100644
--- a/modules/migration/issue.go
+++ b/modules/migration/issue.go
@@ -28,21 +28,21 @@ func (c BasicIssueContext) ForeignID() int64 {
// Issue is a standard issue information
type Issue struct {
- Number int64
- PosterID int64 `yaml:"poster_id"`
- PosterName string `yaml:"poster_name"`
- PosterEmail string `yaml:"poster_email"`
- Title string
- Content string
- Ref string
- Milestone string
- State string // closed, open
- IsLocked bool `yaml:"is_locked"`
- Created time.Time
- Updated time.Time
- Closed *time.Time
- Labels []*Label
- Reactions []*Reaction
- Assignees []string
+ Number int64 `json:"number"`
+ PosterID int64 `yaml:"poster_id" json:"poster_id"`
+ PosterName string `yaml:"poster_name" json:"poster_name"`
+ PosterEmail string `yaml:"poster_email" json:"poster_email"`
+ Title string `json:"title"`
+ Content string `json:"content"`
+ Ref string `json:"ref"`
+ Milestone string `json:"milestone"`
+ State string `json:"state"` // closed, open
+ IsLocked bool `yaml:"is_locked" json:"is_locked"`
+ Created time.Time `json:"created"`
+ Updated time.Time `json:"updated"`
+ Closed *time.Time `json:"closed"`
+ Labels []*Label `json:"labels"`
+ Reactions []*Reaction `json:"reactions"`
+ Assignees []string `json:"assignees"`
Context IssueContext `yaml:"-"`
}
diff --git a/modules/migration/label.go b/modules/migration/label.go
index 1a04a1dd3a..f49fbe3ee4 100644
--- a/modules/migration/label.go
+++ b/modules/migration/label.go
@@ -7,7 +7,7 @@ package migration
// Label defines a standard label information
type Label struct {
- Name string
- Color string
- Description string
+ Name string `json:"name"`
+ Color string `json:"color"`
+ Description string `json:"description"`
}
diff --git a/modules/migration/milestone.go b/modules/migration/milestone.go
index 209aafe6a7..f3b725de67 100644
--- a/modules/migration/milestone.go
+++ b/modules/migration/milestone.go
@@ -9,11 +9,11 @@ import "time"
// Milestone defines a standard milestone
type Milestone struct {
- Title string
- Description string
- Deadline *time.Time
- Created time.Time
- Updated *time.Time
- Closed *time.Time
- State string // open, closed
+ Title string `json:"title"`
+ Description string `json:"description"`
+ Deadline *time.Time `json:"deadline"`
+ Created time.Time `json:"created"`
+ Updated *time.Time `json:"updated"`
+ Closed *time.Time `json:"closed"`
+ State string `json:"state"` // open, closed
}
diff --git a/modules/migration/reaction.go b/modules/migration/reaction.go
index 004cff2f94..2ba44a15a2 100644
--- a/modules/migration/reaction.go
+++ b/modules/migration/reaction.go
@@ -6,7 +6,7 @@ package migration
// Reaction represents a reaction to an issue/pr/comment.
type Reaction struct {
- UserID int64 `yaml:"user_id"`
- UserName string `yaml:"user_name"`
- Content string
+ UserID int64 `yaml:"user_id" json:"user_id"`
+ UserName string `yaml:"user_name" json:"user_name"`
+ Content string `json:"content"`
}
diff --git a/modules/migration/schemas/issue.json b/modules/migration/schemas/issue.json
new file mode 100644
index 0000000000..25753c39e2
--- /dev/null
+++ b/modules/migration/schemas/issue.json
@@ -0,0 +1,114 @@
+{
+ "title": "Issue",
+ "description": "Issues associated to a repository within a forge (Gitea, GitLab, etc.).",
+
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "number": {
+ "description": "Unique identifier, relative to the repository.",
+ "type": "number"
+ },
+ "poster_id": {
+ "description": "Unique identifier of the user who authored the issue.",
+ "type": "number"
+ },
+ "poster_name": {
+ "description": "Name of the user who authored the issue.",
+ "type": "string"
+ },
+ "poster_email": {
+ "description": "Email of the user who authored the issue.",
+ "type": "string"
+ },
+ "title": {
+ "description": "Short description displayed as the title.",
+ "type": "string"
+ },
+ "content": {
+ "description": "Long, multiline, description.",
+ "type": "string"
+ },
+ "ref": {
+ "description": "Target branch in the repository.",
+ "type": "string"
+ },
+ "milestone": {
+ "description": "Name of the milestone.",
+ "type": "string"
+ },
+ "state": {
+ "description": "A 'closed' issue will not see any activity in the future, otherwise it is 'open'.",
+ "enum": [
+ "closed",
+ "open"
+ ]
+ },
+ "is_locked": {
+ "description": "A locked issue can only be modified by privileged users.",
+ "type": "boolean"
+ },
+ "created": {
+ "description": "Creation time.",
+ "type": "string",
+ "format": "date-time"
+ },
+ "updated": {
+ "description": "Last update time.",
+ "type": "string",
+ "format": "date-time"
+ },
+ "closed": {
+ "description": "The last time 'state' changed to 'closed'.",
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "labels": {
+ "description": "List of labels.",
+ "type": "array",
+ "items": {
+ "$ref": "label.json"
+ }
+ },
+ "reactions": {
+ "description": "List of reactions.",
+ "type": "array",
+ "items": {
+ "$ref": "reaction.json"
+ }
+ },
+ "assignees": {
+ "description": "List of assignees.",
+ "type": "array",
+ "items": {
+ "description": "Name of a user assigned to the issue.",
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "number",
+ "poster_id",
+ "poster_name",
+ "title",
+ "content",
+ "state",
+ "is_locked",
+ "created",
+ "updated"
+ ]
+ },
+
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "$id": "http://example.com/issue.json",
+ "$$target": "issue.json"
+}
diff --git a/modules/migration/schemas/label.json b/modules/migration/schemas/label.json
new file mode 100644
index 0000000000..561a2e3357
--- /dev/null
+++ b/modules/migration/schemas/label.json
@@ -0,0 +1,28 @@
+{
+ "title": "Label",
+ "description": "Label associated to an issue.",
+
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "description": "Name of the label, unique within the repository.",
+ "type": "string"
+ },
+ "color": {
+ "description": "Color code of the label.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Long, multiline, description.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "name"
+ ],
+
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "$id": "label.json",
+ "$$target": "label.json"
+}
diff --git a/modules/migration/schemas/milestone.json b/modules/migration/schemas/milestone.json
new file mode 100644
index 0000000000..7024ef45d0
--- /dev/null
+++ b/modules/migration/schemas/milestone.json
@@ -0,0 +1,67 @@
+{
+ "title": "Milestone",
+ "description": "Milestone associated to a repository within a forge.",
+
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "title": {
+ "description": "Short description.",
+ "type": "string"
+ },
+ "description": {
+ "description": "Long, multiline, description.",
+ "type": "string"
+ },
+ "deadline": {
+ "description": "Deadline after which the milestone is overdue.",
+ "type": "string",
+ "format": "date-time"
+ },
+ "created": {
+ "description": "Creation time.",
+ "type": "string",
+ "format": "date-time"
+ },
+ "updated": {
+ "description": "Last update time.",
+ "type": "string",
+ "format": "date-time"
+ },
+ "closed": {
+ "description": "The last time 'state' changed to 'closed'.",
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "state": {
+ "description": "A 'closed' issue will not see any activity in the future, otherwise it is 'open'.",
+ "enum": [
+ "closed",
+ "open"
+ ]
+ }
+ },
+ "required": [
+ "title",
+ "description",
+ "deadline",
+ "created",
+ "updated",
+ "closed",
+ "state"
+ ]
+ },
+
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "$id": "http://example.com/milestone.json",
+ "$$target": "milestone.json"
+}
diff --git a/modules/migration/schemas/reaction.json b/modules/migration/schemas/reaction.json
new file mode 100644
index 0000000000..25652514be
--- /dev/null
+++ b/modules/migration/schemas/reaction.json
@@ -0,0 +1,29 @@
+{
+ "title": "Reaction",
+ "description": "Reaction associated to an issue or a comment.",
+
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "user_id": {
+ "description": "Unique identifier of the user who authored the reaction.",
+ "type": "number"
+ },
+ "user_name": {
+ "description": "Name of the user who authored the reaction.",
+ "type": "string"
+ },
+ "content": {
+ "description": "Representation of the reaction",
+ "type": "string"
+ }
+ },
+ "required": [
+ "user_id",
+ "content"
+ ],
+
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "$id": "http://example.com/reaction.json",
+ "$$target": "reaction.json"
+}
diff --git a/modules/migration/schemas_bindata.go b/modules/migration/schemas_bindata.go
new file mode 100644
index 0000000000..d0fef698b4
--- /dev/null
+++ b/modules/migration/schemas_bindata.go
@@ -0,0 +1,10 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//go:build bindata
+// +build bindata
+
+package migration
+
+//go:generate go run ../../build/generate-bindata.go ../../modules/migration/schemas migration bindata.go
diff --git a/modules/migration/schemas_dynamic.go b/modules/migration/schemas_dynamic.go
new file mode 100644
index 0000000000..c883fafe98
--- /dev/null
+++ b/modules/migration/schemas_dynamic.go
@@ -0,0 +1,40 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//go:build !bindata
+// +build !bindata
+
+package migration
+
+import (
+ "io"
+ "net/url"
+ "os"
+ "path"
+ "path/filepath"
+)
+
+func openSchema(s string) (io.ReadCloser, error) {
+ u, err := url.Parse(s)
+ if err != nil {
+ return nil, err
+ }
+ basename := path.Base(u.Path)
+ filename := basename
+ //
+ // Schema reference each other within the schemas directory but
+ // the tests run in the parent directory.
+ //
+ if _, err := os.Stat(filename); os.IsNotExist(err) {
+ filename = filepath.Join("schemas", basename)
+ //
+ // Integration tests run from the git root directory, not the
+ // directory in which the test source is located.
+ //
+ if _, err := os.Stat(filename); os.IsNotExist(err) {
+ filename = filepath.Join("modules/migration/schemas", basename)
+ }
+ }
+ return os.Open(filename)
+}
diff --git a/modules/migration/schemas_static.go b/modules/migration/schemas_static.go
new file mode 100644
index 0000000000..10c83b313a
--- /dev/null
+++ b/modules/migration/schemas_static.go
@@ -0,0 +1,17 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//go:build bindata
+// +build bindata
+
+package migration
+
+import (
+ "io"
+ "path"
+)
+
+func openSchema(filename string) (io.ReadCloser, error) {
+ return Assets.Open(path.Base(filename))
+}
diff --git a/modules/private/restore_repo.go b/modules/private/restore_repo.go
index 6f8eaf927f..347ed5e78a 100644
--- a/modules/private/restore_repo.go
+++ b/modules/private/restore_repo.go
@@ -17,24 +17,26 @@ import (
// RestoreParams structure holds a data for restore repository
type RestoreParams struct {
- RepoDir string
- OwnerName string
- RepoName string
- Units []string
+ RepoDir string
+ OwnerName string
+ RepoName string
+ Units []string
+ Validation bool
}
// RestoreRepo calls the internal RestoreRepo function
-func RestoreRepo(ctx context.Context, repoDir, ownerName, repoName string, units []string) (int, string) {
+func RestoreRepo(ctx context.Context, repoDir, ownerName, repoName string, units []string, validation bool) (int, string) {
reqURL := setting.LocalURL + "api/internal/restore_repo"
req := newInternalRequest(ctx, reqURL, "POST")
req.SetTimeout(3*time.Second, 0) // since the request will spend much time, don't timeout
req = req.Header("Content-Type", "application/json")
jsonBytes, _ := json.Marshal(RestoreParams{
- RepoDir: repoDir,
- OwnerName: ownerName,
- RepoName: repoName,
- Units: units,
+ RepoDir: repoDir,
+ OwnerName: ownerName,
+ RepoName: repoName,
+ Units: units,
+ Validation: validation,
})
req.Body(jsonBytes)
resp, err := req.Response()
diff --git a/routers/private/restore_repo.go b/routers/private/restore_repo.go
index 8dfe569325..34e06e51c2 100644
--- a/routers/private/restore_repo.go
+++ b/routers/private/restore_repo.go
@@ -24,10 +24,11 @@ func RestoreRepo(ctx *myCtx.PrivateContext) {
return
}
params := struct {
- RepoDir string
- OwnerName string
- RepoName string
- Units []string
+ RepoDir string
+ OwnerName string
+ RepoName string
+ Units []string
+ Validation bool
}{}
if err = json.Unmarshal(bs, &params); err != nil {
ctx.JSON(http.StatusInternalServerError, private.Response{
@@ -42,6 +43,7 @@ func RestoreRepo(ctx *myCtx.PrivateContext) {
params.OwnerName,
params.RepoName,
params.Units,
+ params.Validation,
); err != nil {
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: err.Error(),
diff --git a/services/migrations/dump.go b/services/migrations/dump.go
index 01a946673e..1e4b3326b6 100644
--- a/services/migrations/dump.go
+++ b/services/migrations/dump.go
@@ -604,13 +604,13 @@ func updateOptionsUnits(opts *base.MigrateOptions, units []string) {
}
// RestoreRepository restore a repository from the disk directory
-func RestoreRepository(ctx context.Context, baseDir, ownerName, repoName string, units []string) error {
+func RestoreRepository(ctx context.Context, baseDir, ownerName, repoName string, units []string, validation bool) error {
doer, err := user_model.GetAdminUser()
if err != nil {
return err
}
uploader := NewGiteaLocalUploader(ctx, doer, ownerName, repoName)
- downloader, err := NewRepositoryRestorer(ctx, baseDir, ownerName, repoName)
+ downloader, err := NewRepositoryRestorer(ctx, baseDir, ownerName, repoName, validation)
if err != nil {
return err
}
diff --git a/services/migrations/restore.go b/services/migrations/restore.go
index c927de0b2b..d30d90a6c4 100644
--- a/services/migrations/restore.go
+++ b/services/migrations/restore.go
@@ -19,23 +19,25 @@ import (
// RepositoryRestorer implements an Downloader from the local directory
type RepositoryRestorer struct {
base.NullDownloader
- ctx context.Context
- baseDir string
- repoOwner string
- repoName string
+ ctx context.Context
+ baseDir string
+ repoOwner string
+ repoName string
+ validation bool
}
// NewRepositoryRestorer creates a repository restorer which could restore repository from a dumped folder
-func NewRepositoryRestorer(ctx context.Context, baseDir, owner, repoName string) (*RepositoryRestorer, error) {
+func NewRepositoryRestorer(ctx context.Context, baseDir, owner, repoName string, validation bool) (*RepositoryRestorer, error) {
baseDir, err := filepath.Abs(baseDir)
if err != nil {
return nil, err
}
return &RepositoryRestorer{
- ctx: ctx,
- baseDir: baseDir,
- repoOwner: owner,
- repoName: repoName,
+ ctx: ctx,
+ baseDir: baseDir,
+ repoOwner: owner,
+ repoName: repoName,
+ validation: validation,
}, nil
}
@@ -114,7 +116,7 @@ func (r *RepositoryRestorer) GetTopics() ([]string, error) {
func (r *RepositoryRestorer) GetMilestones() ([]*base.Milestone, error) {
milestones := make([]*base.Milestone, 0, 10)
p := filepath.Join(r.baseDir, "milestone.yml")
- _, err := os.Stat(p)
+ err := base.Load(p, &milestones, r.validation)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
@@ -122,15 +124,6 @@ func (r *RepositoryRestorer) GetMilestones() ([]*base.Milestone, error) {
return nil, err
}
- bs, err := os.ReadFile(p)
- if err != nil {
- return nil, err
- }
-
- err = yaml.Unmarshal(bs, &milestones)
- if err != nil {
- return nil, err
- }
return milestones, nil
}
@@ -193,7 +186,7 @@ func (r *RepositoryRestorer) GetLabels() ([]*base.Label, error) {
func (r *RepositoryRestorer) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
issues := make([]*base.Issue, 0, 10)
p := filepath.Join(r.baseDir, "issue.yml")
- _, err := os.Stat(p)
+ err := base.Load(p, &issues, r.validation)
if err != nil {
if os.IsNotExist(err) {
return nil, true, nil
@@ -201,15 +194,6 @@ func (r *RepositoryRestorer) GetIssues(page, perPage int) ([]*base.Issue, bool,
return nil, false, err
}
- bs, err := os.ReadFile(p)
- if err != nil {
- return nil, false, err
- }
-
- err = yaml.Unmarshal(bs, &issues)
- if err != nil {
- return nil, false, err
- }
for _, issue := range issues {
issue.Context = base.BasicIssueContext(issue.Number)
}