diff options
Diffstat (limited to 'modules/migrations/gitea.go')
-rw-r--r-- | modules/migrations/gitea.go | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/modules/migrations/gitea.go b/modules/migrations/gitea.go new file mode 100644 index 0000000000..dcffb360e3 --- /dev/null +++ b/modules/migrations/gitea.go @@ -0,0 +1,403 @@ +// 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 migrations + +import ( + "fmt" + "io" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "sync" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/migrations/base" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + + gouuid "github.com/satori/go.uuid" +) + +var ( + _ base.Uploader = &GiteaLocalUploader{} +) + +// GiteaLocalUploader implements an Uploader to gitea sites +type GiteaLocalUploader struct { + doer *models.User + repoOwner string + repoName string + repo *models.Repository + labels sync.Map + milestones sync.Map + issues sync.Map + gitRepo *git.Repository + prHeadCache map[string]struct{} +} + +// NewGiteaLocalUploader creates an gitea Uploader via gitea API v1 +func NewGiteaLocalUploader(doer *models.User, repoOwner, repoName string) *GiteaLocalUploader { + return &GiteaLocalUploader{ + doer: doer, + repoOwner: repoOwner, + repoName: repoName, + prHeadCache: make(map[string]struct{}), + } +} + +// CreateRepo creates a repository +func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, includeWiki bool) error { + owner, err := models.GetUserByName(g.repoOwner) + if err != nil { + return err + } + + r, err := models.MigrateRepository(g.doer, owner, models.MigrateRepoOptions{ + Name: g.repoName, + Description: repo.Description, + IsMirror: repo.IsMirror, + RemoteAddr: repo.CloneURL, + IsPrivate: repo.IsPrivate, + Wiki: includeWiki, + }) + if err != nil { + return err + } + g.repo = r + g.gitRepo, err = git.OpenRepository(r.RepoPath()) + return err +} + +// CreateMilestone creates milestone +func (g *GiteaLocalUploader) CreateMilestone(milestone *base.Milestone) error { + var deadline util.TimeStamp + if milestone.Deadline != nil { + deadline = util.TimeStamp(milestone.Deadline.Unix()) + } + if deadline == 0 { + deadline = util.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.UILocation).Unix()) + } + var ms = models.Milestone{ + RepoID: g.repo.ID, + Name: milestone.Title, + Content: milestone.Description, + IsClosed: milestone.State == "close", + DeadlineUnix: deadline, + } + if ms.IsClosed && milestone.Closed != nil { + ms.ClosedDateUnix = util.TimeStamp(milestone.Closed.Unix()) + } + err := models.NewMilestone(&ms) + + if err != nil { + return err + } + g.milestones.Store(ms.Name, ms.ID) + return nil +} + +// CreateLabel creates label +func (g *GiteaLocalUploader) CreateLabel(label *base.Label) error { + var lb = models.Label{ + RepoID: g.repo.ID, + Name: label.Name, + Description: label.Description, + Color: fmt.Sprintf("#%s", label.Color), + } + err := models.NewLabel(&lb) + if err != nil { + return err + } + g.labels.Store(lb.Name, lb.ID) + return nil +} + +// CreateRelease creates release +func (g *GiteaLocalUploader) CreateRelease(release *base.Release) error { + var rel = models.Release{ + RepoID: g.repo.ID, + PublisherID: g.doer.ID, + TagName: release.TagName, + LowerTagName: strings.ToLower(release.TagName), + Target: release.TargetCommitish, + Title: release.Name, + Sha1: release.TargetCommitish, + Note: release.Body, + IsDraft: release.Draft, + IsPrerelease: release.Prerelease, + IsTag: false, + CreatedUnix: util.TimeStamp(release.Created.Unix()), + } + + // calc NumCommits + commit, err := g.gitRepo.GetCommit(rel.TagName) + if err != nil { + return fmt.Errorf("GetCommit: %v", err) + } + rel.NumCommits, err = commit.CommitsCount() + if err != nil { + return fmt.Errorf("CommitsCount: %v", err) + } + + for _, asset := range release.Assets { + var attach = models.Attachment{ + UUID: gouuid.NewV4().String(), + Name: asset.Name, + DownloadCount: int64(*asset.DownloadCount), + Size: int64(*asset.Size), + CreatedUnix: util.TimeStamp(asset.Created.Unix()), + } + + // download attachment + resp, err := http.Get(asset.URL) + if err != nil { + return err + } + defer resp.Body.Close() + + localPath := attach.LocalPath() + if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil { + return fmt.Errorf("MkdirAll: %v", err) + } + + fw, err := os.Create(localPath) + if err != nil { + return fmt.Errorf("Create: %v", err) + } + defer fw.Close() + + if _, err := io.Copy(fw, resp.Body); err != nil { + return err + } + + rel.Attachments = append(rel.Attachments, &attach) + } + + return models.MigrateRelease(&rel) +} + +// CreateIssue creates issue +func (g *GiteaLocalUploader) CreateIssue(issue *base.Issue) error { + var labelIDs []int64 + for _, label := range issue.Labels { + id, ok := g.labels.Load(label.Name) + if !ok { + return fmt.Errorf("Label %s missing when create issue", label.Name) + } + labelIDs = append(labelIDs, id.(int64)) + } + + var milestoneID int64 + if issue.Milestone != "" { + milestone, ok := g.milestones.Load(issue.Milestone) + if !ok { + return fmt.Errorf("Milestone %s missing when create issue", issue.Milestone) + } + milestoneID = milestone.(int64) + } + + var is = models.Issue{ + RepoID: g.repo.ID, + Repo: g.repo, + Index: issue.Number, + PosterID: g.doer.ID, + Title: issue.Title, + Content: issue.Content, + IsClosed: issue.State == "closed", + IsLocked: issue.IsLocked, + MilestoneID: milestoneID, + CreatedUnix: util.TimeStamp(issue.Created.Unix()), + } + if issue.Closed != nil { + is.ClosedUnix = util.TimeStamp(issue.Closed.Unix()) + } + + err := models.InsertIssue(&is, labelIDs) + if err != nil { + return err + } + g.issues.Store(issue.Number, is.ID) + // TODO: add reactions + return err +} + +// CreateComment creates comment +func (g *GiteaLocalUploader) CreateComment(issueNumber int64, comment *base.Comment) error { + var issueID int64 + if issueIDStr, ok := g.issues.Load(issueNumber); !ok { + issue, err := models.GetIssueByIndex(g.repo.ID, issueNumber) + if err != nil { + return err + } + issueID = issue.ID + g.issues.Store(issueNumber, issueID) + } else { + issueID = issueIDStr.(int64) + } + + var cm = models.Comment{ + IssueID: issueID, + Type: models.CommentTypeComment, + PosterID: g.doer.ID, + Content: comment.Content, + CreatedUnix: util.TimeStamp(comment.Created.Unix()), + } + err := models.InsertComment(&cm) + // TODO: Reactions + return err +} + +// CreatePullRequest creates pull request +func (g *GiteaLocalUploader) CreatePullRequest(pr *base.PullRequest) error { + var labelIDs []int64 + for _, label := range pr.Labels { + id, ok := g.labels.Load(label.Name) + if !ok { + return fmt.Errorf("Label %s missing when create issue", label.Name) + } + labelIDs = append(labelIDs, id.(int64)) + } + + var milestoneID int64 + if pr.Milestone != "" { + milestone, ok := g.milestones.Load(pr.Milestone) + if !ok { + return fmt.Errorf("Milestone %s missing when create issue", pr.Milestone) + } + milestoneID = milestone.(int64) + } + + // download patch file + resp, err := http.Get(pr.PatchURL) + if err != nil { + return err + } + defer resp.Body.Close() + pullDir := filepath.Join(g.repo.RepoPath(), "pulls") + if err = os.MkdirAll(pullDir, os.ModePerm); err != nil { + return err + } + f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number))) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(f, resp.Body) + if err != nil { + return err + } + + // set head information + pullHead := filepath.Join(g.repo.RepoPath(), "refs", "pull", fmt.Sprintf("%d", pr.Number)) + if err := os.MkdirAll(pullHead, os.ModePerm); err != nil { + return err + } + p, err := os.Create(filepath.Join(pullHead, "head")) + if err != nil { + return err + } + defer p.Close() + _, err = p.WriteString(pr.Head.SHA) + if err != nil { + return err + } + + var head = "unknown repository" + if pr.IsForkPullRequest() { + if pr.Head.OwnerName != "" { + remote := pr.Head.OwnerName + _, ok := g.prHeadCache[remote] + if !ok { + // git remote add + err := g.gitRepo.AddRemote(remote, pr.Head.CloneURL, true) + if err != nil { + log.Error("AddRemote failed: %s", err) + } else { + g.prHeadCache[remote] = struct{}{} + ok = true + } + } + + if ok { + _, err = git.NewCommand("fetch", remote, pr.Head.Ref).RunInDir(g.repo.RepoPath()) + if err != nil { + log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err) + } else { + headBranch := filepath.Join(g.repo.RepoPath(), "refs", "heads", pr.Head.OwnerName, pr.Head.Ref) + if err := os.MkdirAll(filepath.Dir(headBranch), os.ModePerm); err != nil { + return err + } + b, err := os.Create(headBranch) + if err != nil { + return err + } + defer b.Close() + _, err = b.WriteString(pr.Head.SHA) + if err != nil { + return err + } + head = pr.Head.OwnerName + "/" + pr.Head.Ref + } + } + } + } else { + head = pr.Head.Ref + } + + var pullRequest = models.PullRequest{ + HeadRepoID: g.repo.ID, + HeadBranch: head, + HeadUserName: g.repoOwner, + BaseRepoID: g.repo.ID, + BaseBranch: pr.Base.Ref, + MergeBase: pr.Base.SHA, + Index: pr.Number, + HasMerged: pr.Merged, + + Issue: &models.Issue{ + RepoID: g.repo.ID, + Repo: g.repo, + Title: pr.Title, + Index: pr.Number, + PosterID: g.doer.ID, + Content: pr.Content, + MilestoneID: milestoneID, + IsPull: true, + IsClosed: pr.State == "closed", + IsLocked: pr.IsLocked, + CreatedUnix: util.TimeStamp(pr.Created.Unix()), + }, + } + + if pullRequest.Issue.IsClosed && pr.Closed != nil { + pullRequest.Issue.ClosedUnix = util.TimeStamp(pr.Closed.Unix()) + } + if pullRequest.HasMerged && pr.MergedTime != nil { + pullRequest.MergedUnix = util.TimeStamp(pr.MergedTime.Unix()) + pullRequest.MergedCommitID = pr.MergeCommitSHA + pullRequest.MergerID = g.doer.ID + } + + // TODO: reactions + // TODO: assignees + + return models.InsertPullRequest(&pullRequest, labelIDs) +} + +// Rollback when migrating failed, this will rollback all the changes. +func (g *GiteaLocalUploader) Rollback() error { + if g.repo != nil && g.repo.ID > 0 { + if err := models.DeleteRepository(g.doer, g.repo.OwnerID, g.repo.ID); err != nil { + return err + } + } + return nil +} |