diff options
author | Michael Muré <batolettre@gmail.com> | 2019-07-23 19:50:58 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-07-23 19:50:58 +0200 |
commit | 9ecbcb1cf6348b95b31ccef3f9722be078dbe223 (patch) | |
tree | d855b993905051d5ff5dbc3e30460bc09fa2e2c4 /bridge/gitlab/import.go | |
parent | ca00c9c6b84f0b1333e40666ab979d0d8fdc4036 (diff) | |
parent | 29fdd37ce69b48aa9fc3c1b829ff67818041068f (diff) | |
download | git-bug-9ecbcb1cf6348b95b31ccef3f9722be078dbe223.tar.gz |
Merge pull request #179 from MichaelMure/gitlab-support
Add gitlab bridge configuration and importer
Diffstat (limited to 'bridge/gitlab/import.go')
-rw-r--r-- | bridge/gitlab/import.go | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/bridge/gitlab/import.go b/bridge/gitlab/import.go new file mode 100644 index 00000000..8f4ceec9 --- /dev/null +++ b/bridge/gitlab/import.go @@ -0,0 +1,338 @@ +package gitlab + +import ( + "fmt" + "strconv" + "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/identity" + "github.com/MichaelMure/git-bug/util/git" + "github.com/MichaelMure/git-bug/util/text" +) + +// gitlabImporter implement the Importer interface +type gitlabImporter struct { + conf core.Configuration + + // iterator + iterator *iterator + + // number of imported issues + importedIssues int + + // number of imported identities + importedIdentities int +} + +func (gi *gitlabImporter) Init(conf core.Configuration) error { + gi.conf = conf + return nil +} + +// ImportAll iterate over all the configured repository issues (notes) and ensure the creation +// of the missing issues / comments / label events / title changes ... +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) + } + } + + if err := gi.iterator.Error(); err != nil { + fmt.Printf("import error: %v\n", err) + return err + } + + // commit bug state + if err := b.CommitAsNeeded(); err != nil { + return fmt.Errorf("bug commit: %v", err) + } + } + + fmt.Printf("Successfully imported %d issues and %d identities from Gitlab\n", gi.importedIssues, gi.importedIdentities) + return nil +} + +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 == nil { + return b, nil + } + + // if bug was never imported + cleanText, err := text.Cleanup(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{ + core.KeyOrigin: target, + keyGitlabId: parseID(issue.ID), + keyGitlabUrl: issue.WebURL, + keyGitlabProject: gi.conf[keyProjectID], + }, + ) + + if err != nil { + return nil, err + } + + // importing a new bug + gi.importedIssues++ + + return b, nil +} + +func (gi *gitlabImporter) ensureNote(repo *cache.RepoCache, b *cache.BugCache, note *gitlab.Note) error { + id := parseID(note.ID) + + // 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() + + firstComment := b.Snapshot().Comments[0] + // since gitlab doesn't provide the issue history + // we should check for "changed the description" notes and compare issue texts + // TODO: Check only one time and ignore next 'description change' within one issue + if issue.Description != firstComment.Message { + + // comment edition + _, err = b.EditCommentRaw( + author, + note.UpdatedAt.Unix(), + git.Hash(firstComment.Id()), + issue.Description, + map[string]string{ + keyGitlabId: id, + }, + ) + + return err + } + + case NOTE_COMMENT: + hash, errResolve := b.ResolveOperationWithMetadata(keyGitlabId, id) + if errResolve != cache.ErrNoMatchingOp { + return errResolve + } + + cleanText, err := text.Cleanup(body) + if err != nil { + return err + } + + // if we didn't import the comment + if errResolve == cache.ErrNoMatchingOp { + + // add comment operation + _, err = b.AddCommentRaw( + author, + note.CreatedAt.Unix(), + cleanText, + nil, + map[string]string{ + keyGitlabId: id, + }, + ) + + return err + } + + // if comment was already exported + + // search for last comment update + comment, err := b.Snapshot().SearchComment(hash) + if err != nil { + return err + } + + // compare local bug comment with the new note body + if comment.Message != cleanText { + // comment edition + _, err = b.EditCommentRaw( + author, + note.UpdatedAt.Unix(), + git.Hash(comment.Id()), + cleanText, + nil, + ) + + return err + } + + return nil + + case NOTE_TITLE_CHANGED: + // title change events are given new notes + _, err = b.SetTitleRaw( + author, + note.CreatedAt.Unix(), + body, + map[string]string{ + keyGitlabId: id, + }, + ) + + return err + + case NOTE_UNKNOWN: + return nil + + default: + panic("unhandled note type") + } + + 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: + err = fmt.Errorf("unexpected label event action") + } + + return err +} + +func (gi *gitlabImporter) ensurePerson(repo *cache.RepoCache, id int) (*cache.IdentityCache, error) { + // Look first in the cache + i, err := repo.ResolveIdentityImmutableMetadata(keyGitlabId, strconv.Itoa(id)) + if err == nil { + return i, nil + } + if _, ok := err.(identity.ErrMultipleMatch); ok { + return nil, err + } + + client := buildClient(gi.conf["token"]) + + user, _, err := client.Users.GetUser(id) + if err != nil { + return nil, err + } + + // importing a new identity + gi.importedIdentities++ + + return repo.NewIdentityRaw( + user.Name, + user.PublicEmail, + user.Username, + user.AvatarURL, + map[string]string{ + // because Gitlab + keyGitlabId: strconv.Itoa(id), + keyGitlabLogin: user.Username, + }, + ) +} + +func parseID(id int) string { + return fmt.Sprintf("%d", id) +} |