diff options
Diffstat (limited to 'bridge/github/import.go')
-rw-r--r-- | bridge/github/import.go | 385 |
1 files changed, 177 insertions, 208 deletions
diff --git a/bridge/github/import.go b/bridge/github/import.go index 5337c474..ceb35ef0 100644 --- a/bridge/github/import.go +++ b/bridge/github/import.go @@ -54,64 +54,80 @@ func (gi *githubImporter) ImportAll(ctx context.Context, repo *cache.RepoCache, go func() { defer close(gi.out) - - // Loop over all matching issues - for event := range gi.mediator.Issues { - var issue issue - var issueEdits <-chan userContentEditEvent - var timelineItems <-chan timelineEvent - switch e := event.(type) { - case messageEvent: - fmt.Println(e.msg) - continue - case issueData: - issue = e.issue - issueEdits = e.issueEdits - timelineItems = e.timelineItems - default: - panic(fmt.Sprint("Unknown event type")) + var currBug *cache.BugCache + var currEvent ImportEvent + var nextEvent ImportEvent + var err error + for { + // We need the current event and one look ahead event. + currEvent = nextEvent + if currEvent == nil { + currEvent = gi.mediator.NextImportEvent() } - // create issue - b, err := gi.ensureIssue(ctx, repo, &issue, issueEdits) - if err != nil { - err := fmt.Errorf("issue creation: %v", err) - out <- core.NewImportError(err, "") - return + if currEvent == nil { + break } - - // loop over timeline items - for event := range timelineItems { - var item timelineItem - var edits <-chan userContentEditEvent - switch e := event.(type) { - case messageEvent: - fmt.Println(e.msg) - continue - case timelineData: - item = e.timelineItem - edits = e.userContentEdits + nextEvent = gi.mediator.NextImportEvent() + + switch event := currEvent.(type) { + case MessageEvent: + fmt.Println(event.msg) + case IssueEvent: + // first: commit what is being held in currBug + if err = gi.commit(currBug, out); err != nil { + out <- core.NewImportError(err, "") + return + } + // second: create new issue + switch next := nextEvent.(type) { + case IssueEditEvent: + // consuming and using next event + nextEvent = nil + currBug, err = gi.ensureIssue(ctx, repo, &event.issue, &next.userContentEdit) default: - panic(fmt.Sprint("Unknown event type")) + currBug, err = gi.ensureIssue(ctx, repo, &event.issue, nil) + } + if err != nil { + err := fmt.Errorf("issue creation: %v", err) + out <- core.NewImportError(err, "") + return + } + case IssueEditEvent: + err = gi.ensureIssueEdit(ctx, repo, currBug, event.issueId, &event.userContentEdit) + if err != nil { + err = fmt.Errorf("issue edit: %v", err) + out <- core.NewImportError(err, "") + return + } + case TimelineEvent: + if next, ok := nextEvent.(CommentEditEvent); ok && event.Typename == "IssueComment" { + // consuming and using next event + nextEvent = nil + err = gi.ensureComment(ctx, repo, currBug, &event.timelineItem.IssueComment, &next.userContentEdit) + } else { + err = gi.ensureTimelineItem(ctx, repo, currBug, &event.timelineItem) } - err := gi.ensureTimelineItem(ctx, repo, b, &item, edits) if err != nil { err = fmt.Errorf("timeline item creation: %v", err) out <- core.NewImportError(err, "") return } - } - - if !b.NeedCommit() { - out <- core.NewImportNothing(b.Id(), "no imported operation") - } else if err := b.Commit(); err != nil { - // commit bug state - err = fmt.Errorf("bug commit: %v", err) - out <- core.NewImportError(err, "") - return + case CommentEditEvent: + err = gi.ensureCommentEdit(ctx, repo, currBug, event.commentId, &event.userContentEdit) + if err != nil { + err = fmt.Errorf("comment edit: %v", err) + out <- core.NewImportError(err, "") + return + } + default: + panic("Unknown event type") } } - - if err := gi.mediator.Error(); err != nil { + // commit what is being held in currBug before returning + if err = gi.commit(currBug, out); err != nil { + out <- core.NewImportError(err, "") + } + if err = gi.mediator.Error(); err != nil { gi.out <- core.NewImportError(err, "") } }() @@ -119,27 +135,21 @@ func (gi *githubImporter) ImportAll(ctx context.Context, repo *cache.RepoCache, return out, nil } -// getNextUserContentEdit reads the input channel, handles messages, and returns the next -// userContentEditData. -func getNextUserContentEdit(in <-chan userContentEditEvent) (*userContentEditData, bool) { - for { - event, hasEvent := <-in - if !hasEvent { - return nil, false - } - switch e := event.(type) { - case messageEvent: - fmt.Println(e.msg) - continue - case userContentEditData: - return &e, true - default: - panic(fmt.Sprint("Unknown event type")) - } +func (gi *githubImporter) commit(b *cache.BugCache, out chan<- core.ImportResult) error { + if b == nil { + return nil } + if !b.NeedCommit() { + out <- core.NewImportNothing(b.Id(), "no imported operation") + return nil + } else if err := b.Commit(); err != nil { + // commit bug state + return fmt.Errorf("bug commit: %v", err) + } + return nil } -func (gi *githubImporter) ensureIssue(ctx context.Context, repo *cache.RepoCache, issue *issue, issueEditEvents <-chan userContentEditEvent) (*cache.BugCache, error) { +func (gi *githubImporter) ensureIssue(ctx context.Context, repo *cache.RepoCache, issue *issue, issueEdit *userContentEdit) (*cache.BugCache, error) { author, err := gi.ensurePerson(ctx, repo, issue.Author) if err != nil { return nil, err @@ -150,14 +160,13 @@ func (gi *githubImporter) ensureIssue(ctx context.Context, repo *cache.RepoCache return excerpt.CreateMetadata[core.MetaKeyOrigin] == target && excerpt.CreateMetadata[metaKeyGithubId] == parseId(issue.Id) }) - if err != nil && err != bug.ErrBugNotExist { + if err == nil { + return b, nil + } + if err != bug.ErrBugNotExist { return nil, err } - // get first issue edit - // if it exists, then it holds the bug creation - firstEdit, hasEdit := getNextUserContentEdit(issueEditEvents) - // At Github there exist issues with seemingly empty titles. An example is // https://github.com/NixOS/nixpkgs/issues/72730 . // The title provided by the GraphQL API actually consists of a space followed by a @@ -168,70 +177,49 @@ func (gi *githubImporter) ensureIssue(ctx context.Context, repo *cache.RepoCache title = EMPTY_TITLE_PLACEHOLDER } - if err == bug.ErrBugNotExist { - var textInput string - if hasEdit { - // use the first issue edit: it represents the bug creation itself - textInput = string(*firstEdit.Diff) - } else { - // if there are no issue edits then the issue struct holds the bug creation - textInput = string(issue.Body) - } - cleanText, err := text.Cleanup(textInput) - if err != nil { - return nil, err - } - // create bug - b, _, err = repo.NewBugRaw( - author, - issue.CreatedAt.Unix(), - title, // TODO: this is the *current* title, not the original one - cleanText, - nil, - map[string]string{ - core.MetaKeyOrigin: target, - metaKeyGithubId: parseId(issue.Id), - metaKeyGithubUrl: issue.Url.String(), - }) - if err != nil { - return nil, err - } - // importing a new bug - gi.out <- core.NewImportBug(b.Id()) + var textInput string + if issueEdit != nil { + // use the first issue edit: it represents the bug creation itself + textInput = string(*issueEdit.Diff) + } else { + // if there are no issue edits then the issue struct holds the bug creation + textInput = string(issue.Body) } - if b == nil { - return nil, fmt.Errorf("finding or creating issue") + cleanText, err := text.Cleanup(textInput) + if err != nil { + return nil, err } - // process remaining issue edits, if they exist - for { - edit, hasEdit := getNextUserContentEdit(issueEditEvents) - if !hasEdit { - break - } - // other edits will be added as CommentEdit operations - target, err := b.ResolveOperationWithMetadata(metaKeyGithubId, parseId(issue.Id)) - if err == cache.ErrNoMatchingOp { - // original comment is missing somehow, issuing a warning - gi.out <- core.NewImportWarning(fmt.Errorf("comment ID %s to edit is missing", parseId(issue.Id)), b.Id()) - continue - } - if err != nil { - return nil, err - } - err = gi.ensureCommentEdit(ctx, repo, b, target, &edit.userContentEdit) - if err != nil { - return nil, err - } + // create bug + b, _, err = repo.NewBugRaw( + author, + issue.CreatedAt.Unix(), + title, // TODO: this is the *current* title, not the original one + cleanText, + nil, + map[string]string{ + core.MetaKeyOrigin: target, + metaKeyGithubId: parseId(issue.Id), + metaKeyGithubUrl: issue.Url.String(), + }) + if err != nil { + return nil, err } + // importing a new bug + gi.out <- core.NewImportBug(b.Id()) + return b, nil } -func (gi *githubImporter) ensureTimelineItem(ctx context.Context, repo *cache.RepoCache, b *cache.BugCache, item *timelineItem, commentEdits <-chan userContentEditEvent) error { +func (gi *githubImporter) ensureIssueEdit(ctx context.Context, repo *cache.RepoCache, bug *cache.BugCache, ghIssueId githubv4.ID, edit *userContentEdit) error { + return gi.ensureCommentEdit(ctx, repo, bug, ghIssueId, edit) +} + +func (gi *githubImporter) ensureTimelineItem(ctx context.Context, repo *cache.RepoCache, b *cache.BugCache, item *timelineItem) error { switch item.Typename { case "IssueComment": - err := gi.ensureComment(ctx, repo, b, &item.IssueComment, commentEdits) + err := gi.ensureComment(ctx, repo, b, &item.IssueComment, nil) if err != nil { return fmt.Errorf("timeline comment creation: %v", err) } @@ -390,75 +378,62 @@ func (gi *githubImporter) ensureTimelineItem(ctx context.Context, repo *cache.Re return nil } -func (gi *githubImporter) ensureComment(ctx context.Context, repo *cache.RepoCache, b *cache.BugCache, comment *issueComment, commentEditEvents <-chan userContentEditEvent) error { - author, err := gi.ensurePerson(ctx, repo, comment.Author) +func (gi *githubImporter) ensureCommentEdit(ctx context.Context, repo *cache.RepoCache, b *cache.BugCache, ghTargetId githubv4.ID, edit *userContentEdit) error { + // find comment + target, err := b.ResolveOperationWithMetadata(metaKeyGithubId, parseId(ghTargetId)) if err != nil { return err } - - targetOpID, err := b.ResolveOperationWithMetadata(metaKeyGithubId, parseId(comment.Id)) - if err != nil && err != cache.ErrNoMatchingOp { + _, err = b.ResolveOperationWithMetadata(metaKeyGithubId, parseId(edit.Id)) + if err == nil { + return nil + } + if err != cache.ErrNoMatchingOp { // real error return err } - firstEdit, hasEdit := getNextUserContentEdit(commentEditEvents) - if err == cache.ErrNoMatchingOp { - var textInput string - if hasEdit { - // use the first comment edit: it represents the comment creation itself - textInput = string(*firstEdit.Diff) - } else { - // if there are not comment edits, then the comment struct holds the comment creation - textInput = string(comment.Body) - } - cleanText, err := text.Cleanup(textInput) - if err != nil { - return err - } - // add comment operation - op, err := b.AddCommentRaw( - author, - comment.CreatedAt.Unix(), - cleanText, - nil, - map[string]string{ - metaKeyGithubId: parseId(comment.Id), - metaKeyGithubUrl: comment.Url.String(), - }, - ) - if err != nil { - return err - } + editor, err := gi.ensurePerson(ctx, repo, edit.Editor) + if err != nil { + return err + } - gi.out <- core.NewImportComment(op.Id()) - targetOpID = op.Id() + if edit.DeletedAt != nil { + // comment deletion, not supported yet + return nil } - if targetOpID == "" { - return fmt.Errorf("finding or creating issue comment") + + cleanText, err := text.Cleanup(string(*edit.Diff)) + if err != nil { + return err } - // process remaining comment edits, if they exist - for { - edit, hasEdit := getNextUserContentEdit(commentEditEvents) - if !hasEdit { - break - } - // ensure editor identity - _, err := gi.ensurePerson(ctx, repo, edit.Editor) - if err != nil { - return err - } - err = gi.ensureCommentEdit(ctx, repo, b, targetOpID, &edit.userContentEdit) - if err != nil { - return err - } + // comment edition + op, err := b.EditCommentRaw( + editor, + edit.CreatedAt.Unix(), + target, + cleanText, + map[string]string{ + metaKeyGithubId: parseId(edit.Id), + }, + ) + + if err != nil { + return err } + + gi.out <- core.NewImportCommentEdition(op.Id()) return nil } -func (gi *githubImporter) ensureCommentEdit(ctx context.Context, repo *cache.RepoCache, b *cache.BugCache, target entity.Id, edit *userContentEdit) error { - _, err := b.ResolveOperationWithMetadata(metaKeyGithubId, parseId(edit.Id)) +func (gi *githubImporter) ensureComment(ctx context.Context, repo *cache.RepoCache, b *cache.BugCache, comment *issueComment, firstEdit *userContentEdit) error { + author, err := gi.ensurePerson(ctx, repo, comment.Author) + if err != nil { + return err + } + + _, err = b.ResolveOperationWithMetadata(metaKeyGithubId, parseId(comment.Id)) if err == nil { return nil } @@ -467,41 +442,35 @@ func (gi *githubImporter) ensureCommentEdit(ctx context.Context, repo *cache.Rep return err } - editor, err := gi.ensurePerson(ctx, repo, edit.Editor) + var textInput string + if firstEdit != nil { + // use the first comment edit: it represents the comment creation itself + textInput = string(*firstEdit.Diff) + } else { + // if there are not comment edits, then the comment struct holds the comment creation + textInput = string(comment.Body) + } + cleanText, err := text.Cleanup(textInput) if err != nil { return err } - switch { - case edit.DeletedAt != nil: - // comment deletion, not supported yet - return nil - - case edit.DeletedAt == nil: - - cleanText, err := text.Cleanup(string(*edit.Diff)) - if err != nil { - return err - } - - // comment edition - op, err := b.EditCommentRaw( - editor, - edit.CreatedAt.Unix(), - target, - cleanText, - map[string]string{ - metaKeyGithubId: parseId(edit.Id), - }, - ) - - if err != nil { - return err - } - - gi.out <- core.NewImportCommentEdition(op.Id()) - return nil + // add comment operation + op, err := b.AddCommentRaw( + author, + comment.CreatedAt.Unix(), + cleanText, + nil, + map[string]string{ + metaKeyGithubId: parseId(comment.Id), + metaKeyGithubUrl: comment.Url.String(), + }, + ) + if err != nil { + return err } + + gi.out <- core.NewImportComment(op.Id()) return nil } |