-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
Improve migrations to support migrating milestones/labels/issues/comments/pullrequests #6290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2abe18d
e9fc803
d815335
123bbf4
ed0d7b3
fdf8587
4d6f2ec
91e1e8a
68c45d7
ee40b90
5ba541c
15593a0
236db6a
776e573
6a1d7f2
e805c0e
54ccf25
4f4cf6f
78bfe38
74e49a8
0c9b781
529fc0d
4aae288
766945b
21ba2ba
3997e95
7040908
9dd95f5
f14a09f
d27bffc
6d06ae4
d798b0d
5436028
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
--- | ||
date: "2019-04-15T17:29:00+08:00" | ||
title: "Advanced: Migrations Interfaces" | ||
slug: "migrations-interfaces" | ||
weight: 30 | ||
toc: true | ||
draft: false | ||
menu: | ||
sidebar: | ||
parent: "advanced" | ||
name: "Migrations Interfaces" | ||
weight: 55 | ||
identifier: "migrations-interfaces" | ||
--- | ||
|
||
# Migration Features | ||
|
||
The new migration features were introduced in Gitea 1.9.0. It defines two interfaces to support migrating | ||
repositories data from other git host platforms to gitea or, in the future migrating gitea data to other | ||
git host platforms. Currently, only the migrations from github via APIv3 to Gitea is implemented. | ||
lunny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
First of all, Gitea defines some standard objects in packages `modules/migrations/base`. They are | ||
lunny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
`Repository`, `Milestone`, `Release`, `Label`, `Issue`, `Comment`, `PullRequest`. | ||
|
||
## Downloader Interfaces | ||
|
||
To migrate from a new git host platform, there are two steps to be updated. | ||
lunny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
- You should implement a `Downloader` which will get all kinds of repository informations. | ||
lunny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- You should implement a `DownloaderFactory` which is used to detect if the URL matches and | ||
lunny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
create a Downloader. | ||
lunny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- You'll need to register the `DownloaderFactory` via `RegisterDownloaderFactory` on init. | ||
lunny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```Go | ||
type Downloader interface { | ||
GetRepoInfo() (*Repository, error) | ||
GetMilestones() ([]*Milestone, error) | ||
GetReleases() ([]*Release, error) | ||
GetLabels() ([]*Label, error) | ||
GetIssues(start, limit int) ([]*Issue, error) | ||
GetComments(issueNumber int64) ([]*Comment, error) | ||
GetPullRequests(start, limit int) ([]*PullRequest, error) | ||
} | ||
``` | ||
|
||
```Go | ||
type DownloaderFactory interface { | ||
Match(opts MigrateOptions) (bool, error) | ||
New(opts MigrateOptions) (Downloader, error) | ||
} | ||
``` | ||
|
||
## Uploader Interface | ||
|
||
Currently, only a `GiteaLocalUploader` is implemented, so we only save downloaded | ||
lunny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data via this `Uploader` on the local Gitea instance. Other uploaders are not supported | ||
lunny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
and will be implemented in future. | ||
|
||
```Go | ||
// Uploader uploads all the informations | ||
type Uploader interface { | ||
CreateRepo(repo *Repository, includeWiki bool) error | ||
CreateMilestone(milestone *Milestone) error | ||
CreateRelease(release *Release) error | ||
CreateLabel(label *Label) error | ||
CreateIssue(issue *Issue) error | ||
CreateComment(issueNumber int64, comment *Comment) error | ||
CreatePullRequest(pr *PullRequest) error | ||
Rollback() error | ||
} | ||
|
||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// Copyright 2019 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 models | ||
|
||
import "github.com/go-xorm/xorm" | ||
|
||
// InsertIssue insert one issue to database | ||
func InsertIssue(issue *Issue, labelIDs []int64) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is this different to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will not trigger notifications. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But then I'd prefer to have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to do that, but it's so different between theses two functions I would like let it duplicated currently. Maybe send a refactor PR after this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough, sounds good. |
||
sess := x.NewSession() | ||
if err := sess.Begin(); err != nil { | ||
return err | ||
} | ||
|
||
if err := insertIssue(sess, issue, labelIDs); err != nil { | ||
return err | ||
} | ||
return sess.Commit() | ||
} | ||
|
||
func insertIssue(sess *xorm.Session, issue *Issue, labelIDs []int64) error { | ||
if issue.MilestoneID > 0 { | ||
sess.Incr("num_issues") | ||
if issue.IsClosed { | ||
sess.Incr("num_closed_issues") | ||
} | ||
if _, err := sess.ID(issue.MilestoneID).NoAutoTime().Update(new(Milestone)); err != nil { | ||
return err | ||
} | ||
} | ||
if _, err := sess.NoAutoTime().Insert(issue); err != nil { | ||
return err | ||
} | ||
var issueLabels = make([]IssueLabel, 0, len(labelIDs)) | ||
for _, labelID := range labelIDs { | ||
issueLabels = append(issueLabels, IssueLabel{ | ||
IssueID: issue.ID, | ||
LabelID: labelID, | ||
}) | ||
} | ||
if _, err := sess.Insert(issueLabels); err != nil { | ||
return err | ||
} | ||
if !issue.IsPull { | ||
sess.ID(issue.RepoID).Incr("num_issues") | ||
if issue.IsClosed { | ||
sess.Incr("num_closed_issues") | ||
} | ||
} else { | ||
sess.ID(issue.RepoID).Incr("num_pulls") | ||
if issue.IsClosed { | ||
sess.Incr("num_closed_pulls") | ||
} | ||
} | ||
if _, err := sess.NoAutoTime().Update(issue.Repo); err != nil { | ||
return err | ||
} | ||
|
||
sess.Incr("num_issues") | ||
if issue.IsClosed { | ||
sess.Incr("num_closed_issues") | ||
} | ||
if _, err := sess.In("id", labelIDs).Update(new(Label)); err != nil { | ||
return err | ||
} | ||
|
||
if issue.MilestoneID > 0 { | ||
if _, err := sess.ID(issue.MilestoneID).SetExpr("completeness", "num_closed_issues * 100 / num_issues").Update(new(Milestone)); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// InsertComment inserted a comment | ||
func InsertComment(comment *Comment) error { | ||
sess := x.NewSession() | ||
defer sess.Close() | ||
if err := sess.Begin(); err != nil { | ||
return err | ||
} | ||
if _, err := sess.NoAutoTime().Insert(comment); err != nil { | ||
return err | ||
} | ||
if _, err := sess.ID(comment.IssueID).Incr("num_comments").Update(new(Issue)); err != nil { | ||
return err | ||
} | ||
return sess.Commit() | ||
} | ||
|
||
// InsertPullRequest inserted a pull request | ||
func InsertPullRequest(pr *PullRequest, labelIDs []int64) error { | ||
sess := x.NewSession() | ||
defer sess.Close() | ||
if err := sess.Begin(); err != nil { | ||
return err | ||
} | ||
if err := insertIssue(sess, pr.Issue, labelIDs); err != nil { | ||
return err | ||
} | ||
pr.IssueID = pr.Issue.ID | ||
if _, err := sess.NoAutoTime().Insert(pr); err != nil { | ||
return err | ||
} | ||
return sess.Commit() | ||
} | ||
|
||
// MigrateRelease migrates release | ||
func MigrateRelease(rel *Release) error { | ||
sess := x.NewSession() | ||
if err := sess.Begin(); err != nil { | ||
return err | ||
} | ||
|
||
var oriRel = Release{ | ||
RepoID: rel.RepoID, | ||
TagName: rel.TagName, | ||
} | ||
exist, err := sess.Get(&oriRel) | ||
if err != nil { | ||
return err | ||
} | ||
if !exist { | ||
if _, err := sess.NoAutoTime().Insert(rel); err != nil { | ||
return err | ||
} | ||
} else { | ||
rel.ID = oriRel.ID | ||
if _, err := sess.ID(rel.ID).Cols("target, title, note, is_tag, num_commits").Update(rel); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
for i := 0; i < len(rel.Attachments); i++ { | ||
rel.Attachments[i].ReleaseID = rel.ID | ||
} | ||
|
||
if _, err := sess.NoAutoTime().Insert(rel.Attachments); err != nil { | ||
return err | ||
} | ||
|
||
return sess.Commit() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -896,6 +896,7 @@ type MigrateRepoOptions struct { | |
IsPrivate bool | ||
IsMirror bool | ||
RemoteAddr string | ||
Wiki bool // include wiki repository | ||
} | ||
|
||
/* | ||
|
@@ -917,7 +918,7 @@ func wikiRemoteURL(remote string) string { | |
return "" | ||
} | ||
|
||
// MigrateRepository migrates a existing repository from other project hosting. | ||
// MigrateRepository migrates an existing repository from other project hosting. | ||
func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, error) { | ||
repo, err := CreateRepository(doer, u, CreateRepoOptions{ | ||
Name: opts.Name, | ||
|
@@ -930,7 +931,6 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err | |
} | ||
|
||
repoPath := RepoPath(u.Name, opts.Name) | ||
wikiPath := WikiPath(u.Name, opts.Name) | ||
|
||
if u.IsOrganization() { | ||
t, err := u.GetOwnerTeam() | ||
|
@@ -956,22 +956,25 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err | |
return repo, fmt.Errorf("Clone: %v", err) | ||
} | ||
|
||
wikiRemotePath := wikiRemoteURL(opts.RemoteAddr) | ||
if len(wikiRemotePath) > 0 { | ||
if err := os.RemoveAll(wikiPath); err != nil { | ||
return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) | ||
} | ||
|
||
if err = git.Clone(wikiRemotePath, wikiPath, git.CloneRepoOptions{ | ||
Mirror: true, | ||
Quiet: true, | ||
Timeout: migrateTimeout, | ||
Branch: "master", | ||
}); err != nil { | ||
log.Warn("Clone wiki: %v", err) | ||
if opts.Wiki { | ||
wikiPath := WikiPath(u.Name, opts.Name) | ||
wikiRemotePath := wikiRemoteURL(opts.RemoteAddr) | ||
if len(wikiRemotePath) > 0 { | ||
if err := os.RemoveAll(wikiPath); err != nil { | ||
return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) | ||
} | ||
|
||
if err = git.Clone(wikiRemotePath, wikiPath, git.CloneRepoOptions{ | ||
Mirror: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this always a mirror? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These codes just moved from above because I added There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is probably ok and we certainly want at least part of what it's doing regarding copying the remote tracking branches. We should check what happens when we remove the upstream at the end of the migration and check that the refspec configuration is cleaned up though. |
||
Quiet: true, | ||
Timeout: migrateTimeout, | ||
Branch: "master", | ||
}); err != nil { | ||
log.Warn("Clone wiki: %v", err) | ||
if err := os.RemoveAll(wikiPath); err != nil { | ||
return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// Copyright 2019 The Gitea Authors. All rights reserved. | ||
// Copyright 2018 Jonas Franz. All rights reserved. | ||
// Use of this source code is governed by a MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package base | ||
|
||
import "time" | ||
|
||
// Comment is a standard comment information | ||
type Comment struct { | ||
PosterName string | ||
PosterEmail string | ||
Created time.Time | ||
Content string | ||
Reactions *Reactions | ||
} |
Uh oh!
There was an error while loading. Please reload this page.