From 8b6c896369bc48599bc97181a3f3a85a9425af87 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Wed, 17 Jul 2019 00:06:42 +0200 Subject: bridge/gitlab: complete importer --- bridge/gitlab/import.go | 311 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 309 insertions(+), 2 deletions(-) diff --git a/bridge/gitlab/import.go b/bridge/gitlab/import.go index dec90a6c..1ac8eaf3 100644 --- a/bridge/gitlab/import.go +++ b/bridge/gitlab/import.go @@ -1,10 +1,15 @@ package gitlab import ( + "fmt" "time" + "github.com/xanzy/go-gitlab" + "github.com/MichaelMure/git-bug/bridge/core" + "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/util/text" ) const ( @@ -14,6 +19,9 @@ const ( type gitlabImporter struct { conf core.Configuration + // iterator + iterator *iterator + // number of imported issues importedIssues int @@ -21,10 +29,309 @@ type gitlabImporter struct { importedIdentities int } -func (*gitlabImporter) Init(conf core.Configuration) error { +func (gi *gitlabImporter) Init(conf core.Configuration) error { + gi.conf = conf + return nil +} + +func (gi *gitlabImporter) ImportAll(repo *cache.RepoCache, since time.Time) error { + gi.iterator = NewIterator(gi.conf[keyProjectID], gi.conf[keyToken], since) + + // Loop over all matching issues + for gi.iterator.NextIssue() { + issue := gi.iterator.IssueValue() + fmt.Printf("importing issue: %v\n", issue.Title) + + // create issue + b, err := gi.ensureIssue(repo, issue) + if err != nil { + return fmt.Errorf("issue creation: %v", err) + } + + // Loop over all notes + for gi.iterator.NextNote() { + note := gi.iterator.NoteValue() + if err := gi.ensureNote(repo, b, note); err != nil { + return fmt.Errorf("note creation: %v", err) + } + } + + // Loop over all label events + for gi.iterator.NextLabelEvent() { + labelEvent := gi.iterator.LabelEventValue() + if err := gi.ensureLabelEvent(repo, b, labelEvent); err != nil { + return fmt.Errorf("label event creation: %v", err) + } + + } + + // commit bug state + if err := b.CommitAsNeeded(); err != nil { + return fmt.Errorf("bug commit: %v", err) + } + } + + if err := gi.iterator.Error(); err != nil { + fmt.Printf("import error: %v\n", err) + return err + } + + fmt.Printf("Successfully imported %d issues and %d identities from Gitlab\n", gi.importedIssues, gi.importedIdentities) return nil } -func (*gitlabImporter) ImportAll(repo *cache.RepoCache, since time.Time) error { +func (gi *gitlabImporter) ensureIssue(repo *cache.RepoCache, issue *gitlab.Issue) (*cache.BugCache, error) { + // ensure issue author + author, err := gi.ensurePerson(repo, issue.Author.ID) + if err != nil { + return nil, err + } + + // resolve bug + b, err := repo.ResolveBugCreateMetadata(keyGitlabUrl, issue.WebURL) + if err != nil && err != bug.ErrBugNotExist { + return nil, err + } + + if err == bug.ErrBugNotExist { + cleanText, err := text.Cleanup(string(issue.Description)) + if err != nil { + return nil, err + } + + // create bug + b, _, err = repo.NewBugRaw( + author, + issue.CreatedAt.Unix(), + issue.Title, + cleanText, + nil, + map[string]string{ + keyOrigin: target, + keyGitlabId: parseID(issue.ID), + keyGitlabUrl: issue.WebURL, + }, + ) + + if err != nil { + return nil, err + } + + // importing a new bug + gi.importedIssues++ + + return b, nil + } + + return nil, nil +} + +func (gi *gitlabImporter) ensureNote(repo *cache.RepoCache, b *cache.BugCache, note *gitlab.Note) error { + id := parseID(note.ID) + + hash, err := b.ResolveOperationWithMetadata(keyGitlabId, id) + if err != cache.ErrNoMatchingOp { + return err + } + + // ensure issue author + author, err := gi.ensurePerson(repo, note.Author.ID) + if err != nil { + return err + } + + noteType, body := GetNoteType(note) + switch noteType { + case NOTE_CLOSED: + _, err = b.CloseRaw( + author, + note.CreatedAt.Unix(), + map[string]string{ + keyGitlabId: id, + }, + ) + return err + + case NOTE_REOPENED: + _, err = b.OpenRaw( + author, + note.CreatedAt.Unix(), + map[string]string{ + keyGitlabId: id, + }, + ) + return err + + case NOTE_DESCRIPTION_CHANGED: + issue := gi.iterator.IssueValue() + + // since gitlab doesn't provide the issue history + // we should check for "changed the description" notes and compare issue texts + + if issue.Description != b.Snapshot().Comments[0].Message { + // comment edition + _, err = b.EditCommentRaw( + author, + note.UpdatedAt.Unix(), + target, + issue.Description, + map[string]string{ + keyGitlabId: id, + keyGitlabUrl: "", + }, + ) + + return err + + } + + case NOTE_COMMENT: + + cleanText, err := text.Cleanup(body) + if err != nil { + return err + } + + // if we didn't import the comment + if err == cache.ErrNoMatchingOp { + + // add comment operation + _, err = b.AddCommentRaw( + author, + note.CreatedAt.Unix(), + cleanText, + nil, + map[string]string{ + keyGitlabId: id, + keyGitlabUrl: "", + }, + ) + + return err + } + + // if comment was already exported + + // if note wasn't updated + if note.UpdatedAt.Equal(*note.CreatedAt) { + return nil + } + + // search for last comment update + timeline, err := b.Snapshot().SearchTimelineItem(hash) + if err != nil { + return err + } + + item, ok := timeline.(*bug.AddCommentTimelineItem) + if !ok { + return fmt.Errorf("expected add comment time line") + } + + // compare local bug comment with the new note body + if item.Message != cleanText { + // comment edition + _, err = b.EditCommentRaw( + author, + note.UpdatedAt.Unix(), + target, + cleanText, + map[string]string{ + // no metadata unique metadata to store + keyGitlabId: "", + keyGitlabUrl: "", + }, + ) + + return err + } + + return nil + + case NOTE_TITLE_CHANGED: + + _, err = b.SetTitleRaw( + author, + note.CreatedAt.Unix(), + body, + map[string]string{ + keyGitlabId: id, + keyGitlabUrl: "", + }, + ) + + return err + + default: + // non handled note types + + return nil + } + return nil } + +func (gi *gitlabImporter) ensureLabelEvent(repo *cache.RepoCache, b *cache.BugCache, labelEvent *gitlab.LabelEvent) error { + _, err := b.ResolveOperationWithMetadata(keyGitlabId, parseID(labelEvent.ID)) + if err != cache.ErrNoMatchingOp { + return err + } + + // ensure issue author + author, err := gi.ensurePerson(repo, labelEvent.User.ID) + if err != nil { + return err + } + + switch labelEvent.Action { + case "add": + _, err = b.ForceChangeLabelsRaw( + author, + labelEvent.CreatedAt.Unix(), + []string{labelEvent.Label.Name}, + nil, + map[string]string{ + keyGitlabId: parseID(labelEvent.ID), + }, + ) + + case "remove": + _, err = b.ForceChangeLabelsRaw( + author, + labelEvent.CreatedAt.Unix(), + nil, + []string{labelEvent.Label.Name}, + map[string]string{ + keyGitlabId: parseID(labelEvent.ID), + }, + ) + + default: + panic("unexpected label event action") + } + + return err +} + +func (gi *gitlabImporter) ensurePerson(repo *cache.RepoCache, id int) (*cache.IdentityCache, error) { + client := buildClient(gi.conf["token"]) + + user, _, err := client.Users.GetUser(id) + if err != nil { + return nil, err + } + + return repo.NewIdentityRaw( + user.Name, + user.PublicEmail, + user.Username, + user.AvatarURL, + map[string]string{ + keyGitlabLogin: user.Username, + }, + ) +} + +func parseID(id int) string { + return fmt.Sprintf("%d", id) +} -- cgit