2018-09-24 20:22:32 +03:00
|
|
|
package github
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2018-09-25 20:10:38 +03:00
|
|
|
"strings"
|
2018-09-24 20:22:32 +03:00
|
|
|
|
|
|
|
"github.com/MichaelMure/git-bug/bridge/core"
|
2018-09-25 20:10:38 +03:00
|
|
|
"github.com/MichaelMure/git-bug/bug"
|
2018-09-24 20:22:32 +03:00
|
|
|
"github.com/MichaelMure/git-bug/cache"
|
2019-01-17 05:09:08 +03:00
|
|
|
"github.com/MichaelMure/git-bug/identity"
|
2018-10-02 14:02:16 +03:00
|
|
|
"github.com/MichaelMure/git-bug/util/git"
|
2018-09-24 20:22:32 +03:00
|
|
|
"github.com/shurcooL/githubv4"
|
|
|
|
)
|
|
|
|
|
2018-09-25 20:10:38 +03:00
|
|
|
const keyGithubId = "github-id"
|
|
|
|
const keyGithubUrl = "github-url"
|
2019-01-17 05:09:08 +03:00
|
|
|
const keyGithubLogin = "github-login"
|
2018-09-25 20:10:38 +03:00
|
|
|
|
|
|
|
// githubImporter implement the Importer interface
|
2018-10-06 12:55:16 +03:00
|
|
|
type githubImporter struct {
|
|
|
|
client *githubv4.Client
|
|
|
|
conf core.Configuration
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gi *githubImporter) Init(conf core.Configuration) error {
|
|
|
|
gi.conf = conf
|
|
|
|
gi.client = buildClient(conf)
|
2018-09-24 20:22:32 +03:00
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
return nil
|
2018-10-06 12:55:16 +03:00
|
|
|
}
|
2018-09-24 20:22:32 +03:00
|
|
|
|
2018-10-06 12:55:16 +03:00
|
|
|
func (gi *githubImporter) ImportAll(repo *cache.RepoCache) error {
|
2018-10-02 00:34:45 +03:00
|
|
|
q := &issueTimelineQuery{}
|
2018-09-24 20:22:32 +03:00
|
|
|
variables := map[string]interface{}{
|
2018-10-06 12:55:16 +03:00
|
|
|
"owner": githubv4.String(gi.conf[keyUser]),
|
|
|
|
"name": githubv4.String(gi.conf[keyProject]),
|
2018-10-02 14:02:16 +03:00
|
|
|
"issueFirst": githubv4.Int(1),
|
|
|
|
"issueAfter": (*githubv4.String)(nil),
|
|
|
|
"timelineFirst": githubv4.Int(10),
|
|
|
|
"timelineAfter": (*githubv4.String)(nil),
|
2018-10-02 00:34:45 +03:00
|
|
|
|
|
|
|
// Fun fact, github provide the comment edition in reverse chronological
|
|
|
|
// order, because haha. Look at me, I'm dying of laughter.
|
2018-10-02 14:02:16 +03:00
|
|
|
"issueEditLast": githubv4.Int(10),
|
|
|
|
"issueEditBefore": (*githubv4.String)(nil),
|
|
|
|
"commentEditLast": githubv4.Int(10),
|
|
|
|
"commentEditBefore": (*githubv4.String)(nil),
|
2018-09-24 20:22:32 +03:00
|
|
|
}
|
|
|
|
|
2018-09-25 20:10:38 +03:00
|
|
|
var b *cache.BugCache
|
|
|
|
|
2018-09-24 20:22:32 +03:00
|
|
|
for {
|
2018-10-06 12:55:16 +03:00
|
|
|
err := gi.client.Query(context.TODO(), &q, variables)
|
2018-09-24 20:22:32 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-10-02 00:34:45 +03:00
|
|
|
if len(q.Repository.Issues.Nodes) == 0 {
|
|
|
|
return nil
|
2018-09-25 20:10:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
issue := q.Repository.Issues.Nodes[0]
|
|
|
|
|
|
|
|
if b == nil {
|
2018-10-06 12:55:16 +03:00
|
|
|
b, err = gi.ensureIssue(repo, issue, variables)
|
2018-09-25 20:10:38 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-02 14:02:16 +03:00
|
|
|
for _, itemEdge := range q.Repository.Issues.Nodes[0].Timeline.Edges {
|
2019-01-19 18:01:06 +03:00
|
|
|
err = gi.ensureTimelineItem(repo, b, itemEdge.Cursor, itemEdge.Node, variables)
|
2019-01-17 05:09:08 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-10-02 14:02:16 +03:00
|
|
|
}
|
2018-09-24 20:22:32 +03:00
|
|
|
|
2018-09-25 20:10:38 +03:00
|
|
|
if !issue.Timeline.PageInfo.HasNextPage {
|
|
|
|
err = b.CommitAsNeeded()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
b = nil
|
|
|
|
|
|
|
|
if !q.Repository.Issues.PageInfo.HasNextPage {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
variables["issueAfter"] = githubv4.NewString(q.Repository.Issues.PageInfo.EndCursor)
|
|
|
|
variables["timelineAfter"] = (*githubv4.String)(nil)
|
|
|
|
continue
|
2018-09-24 20:22:32 +03:00
|
|
|
}
|
2018-09-25 20:10:38 +03:00
|
|
|
|
|
|
|
variables["timelineAfter"] = githubv4.NewString(issue.Timeline.PageInfo.EndCursor)
|
2018-09-24 20:22:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-10-06 12:55:16 +03:00
|
|
|
func (gi *githubImporter) Import(repo *cache.RepoCache, id string) error {
|
2018-09-24 20:22:32 +03:00
|
|
|
fmt.Println("IMPORT")
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2018-09-25 20:10:38 +03:00
|
|
|
|
2018-10-06 12:55:16 +03:00
|
|
|
func (gi *githubImporter) ensureIssue(repo *cache.RepoCache, issue issueTimeline, rootVariables map[string]interface{}) (*cache.BugCache, error) {
|
2018-09-25 20:10:38 +03:00
|
|
|
fmt.Printf("import issue: %s\n", issue.Title)
|
|
|
|
|
2018-10-02 00:34:45 +03:00
|
|
|
b, err := repo.ResolveBugCreateMetadata(keyGithubId, parseId(issue.Id))
|
|
|
|
if err != nil && err != bug.ErrBugNotExist {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
author, err := gi.makePerson(repo, issue.Author)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-10-02 00:34:45 +03:00
|
|
|
// if there is no edit, the UserContentEdits given by github is empty. That
|
|
|
|
// means that the original message is given by the issue message.
|
2018-10-05 18:53:06 +03:00
|
|
|
//
|
2018-10-02 00:34:45 +03:00
|
|
|
// if there is edits, the UserContentEdits given by github contains both the
|
|
|
|
// original message and the following edits. The issue message give the last
|
|
|
|
// version so we don't care about that.
|
2018-10-05 18:53:06 +03:00
|
|
|
//
|
|
|
|
// the tricky part: for an issue older than the UserContentEdits API, github
|
|
|
|
// doesn't have the previous message version anymore and give an edition
|
|
|
|
// with .Diff == nil. We have to filter them.
|
2018-10-02 00:34:45 +03:00
|
|
|
|
|
|
|
if len(issue.UserContentEdits.Nodes) == 0 {
|
|
|
|
if err == bug.ErrBugNotExist {
|
|
|
|
b, err = repo.NewBugRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
author,
|
2018-10-02 00:34:45 +03:00
|
|
|
issue.CreatedAt.Unix(),
|
|
|
|
// Todo: this might not be the initial title, we need to query the
|
|
|
|
// timeline to be sure
|
|
|
|
issue.Title,
|
|
|
|
cleanupText(string(issue.Body)),
|
|
|
|
nil,
|
|
|
|
map[string]string{
|
|
|
|
keyGithubId: parseId(issue.Id),
|
|
|
|
keyGithubUrl: issue.Url.String(),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// reverse the order, because github
|
|
|
|
reverseEdits(issue.UserContentEdits.Nodes)
|
|
|
|
|
2018-10-05 18:53:06 +03:00
|
|
|
for i, edit := range issue.UserContentEdits.Nodes {
|
|
|
|
if b != nil && i == 0 {
|
|
|
|
// The first edit in the github result is the creation itself, we already have that
|
|
|
|
continue
|
|
|
|
}
|
2018-10-02 00:34:45 +03:00
|
|
|
|
2018-10-05 18:53:06 +03:00
|
|
|
if b == nil {
|
|
|
|
if edit.Diff == nil {
|
|
|
|
// not enough data given by github for old edit, ignore them
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// we create the bug as soon as we have a legit first edition
|
|
|
|
b, err = repo.NewBugRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
author,
|
2018-10-05 18:53:06 +03:00
|
|
|
issue.CreatedAt.Unix(),
|
|
|
|
// Todo: this might not be the initial title, we need to query the
|
|
|
|
// timeline to be sure
|
|
|
|
issue.Title,
|
|
|
|
cleanupText(string(*edit.Diff)),
|
|
|
|
nil,
|
|
|
|
map[string]string{
|
|
|
|
keyGithubId: parseId(issue.Id),
|
|
|
|
keyGithubUrl: issue.Url.String(),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
continue
|
2018-10-02 00:34:45 +03:00
|
|
|
}
|
|
|
|
|
2018-10-05 18:53:06 +03:00
|
|
|
target, err := b.ResolveTargetWithMetadata(keyGithubId, parseId(issue.Id))
|
2018-10-02 14:02:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-02 00:34:45 +03:00
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
err = gi.ensureCommentEdit(repo, b, target, edit)
|
2018-10-02 00:34:45 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !issue.UserContentEdits.PageInfo.HasNextPage {
|
2018-10-05 18:53:06 +03:00
|
|
|
// if we still didn't get a legit edit, create the bug from the issue data
|
|
|
|
if b == nil {
|
|
|
|
return repo.NewBugRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
author,
|
2018-10-05 18:53:06 +03:00
|
|
|
issue.CreatedAt.Unix(),
|
|
|
|
// Todo: this might not be the initial title, we need to query the
|
|
|
|
// timeline to be sure
|
|
|
|
issue.Title,
|
|
|
|
cleanupText(string(issue.Body)),
|
|
|
|
nil,
|
|
|
|
map[string]string{
|
|
|
|
keyGithubId: parseId(issue.Id),
|
|
|
|
keyGithubUrl: issue.Url.String(),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
2018-10-02 00:34:45 +03:00
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have more edit, querying them
|
|
|
|
|
|
|
|
q := &issueEditQuery{}
|
|
|
|
variables := map[string]interface{}{
|
2018-10-02 14:02:16 +03:00
|
|
|
"owner": rootVariables["owner"],
|
|
|
|
"name": rootVariables["name"],
|
|
|
|
"issueFirst": rootVariables["issueFirst"],
|
|
|
|
"issueAfter": rootVariables["issueAfter"],
|
|
|
|
"issueEditLast": githubv4.Int(10),
|
|
|
|
"issueEditBefore": issue.UserContentEdits.PageInfo.StartCursor,
|
2018-10-02 00:34:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
2018-10-06 12:55:16 +03:00
|
|
|
err := gi.client.Query(context.TODO(), &q, variables)
|
2018-10-02 00:34:45 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
edits := q.Repository.Issues.Nodes[0].UserContentEdits
|
|
|
|
|
|
|
|
if len(edits.Nodes) == 0 {
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
2018-10-05 18:53:06 +03:00
|
|
|
for _, edit := range edits.Nodes {
|
|
|
|
if b == nil {
|
|
|
|
if edit.Diff == nil {
|
|
|
|
// not enough data given by github for old edit, ignore them
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// we create the bug as soon as we have a legit first edition
|
|
|
|
b, err = repo.NewBugRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
author,
|
2018-10-05 18:53:06 +03:00
|
|
|
issue.CreatedAt.Unix(),
|
|
|
|
// Todo: this might not be the initial title, we need to query the
|
|
|
|
// timeline to be sure
|
|
|
|
issue.Title,
|
|
|
|
cleanupText(string(*edit.Diff)),
|
|
|
|
nil,
|
|
|
|
map[string]string{
|
|
|
|
keyGithubId: parseId(issue.Id),
|
|
|
|
keyGithubUrl: issue.Url.String(),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-02 00:34:45 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-10-05 18:53:06 +03:00
|
|
|
target, err := b.ResolveTargetWithMetadata(keyGithubId, parseId(issue.Id))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
err = gi.ensureCommentEdit(repo, b, target, edit)
|
2018-10-02 00:34:45 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !edits.PageInfo.HasNextPage {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2018-10-02 14:02:16 +03:00
|
|
|
variables["issueEditBefore"] = edits.PageInfo.StartCursor
|
2018-10-02 00:34:45 +03:00
|
|
|
}
|
|
|
|
|
2018-09-25 20:10:38 +03:00
|
|
|
// TODO: check + import files
|
|
|
|
|
2018-10-05 18:53:06 +03:00
|
|
|
// if we still didn't get a legit edit, create the bug from the issue data
|
|
|
|
if b == nil {
|
|
|
|
return repo.NewBugRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
author,
|
2018-10-05 18:53:06 +03:00
|
|
|
issue.CreatedAt.Unix(),
|
|
|
|
// Todo: this might not be the initial title, we need to query the
|
|
|
|
// timeline to be sure
|
|
|
|
issue.Title,
|
|
|
|
cleanupText(string(issue.Body)),
|
|
|
|
nil,
|
|
|
|
map[string]string{
|
|
|
|
keyGithubId: parseId(issue.Id),
|
|
|
|
keyGithubUrl: issue.Url.String(),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-10-02 00:34:45 +03:00
|
|
|
return b, nil
|
2018-09-25 20:10:38 +03:00
|
|
|
}
|
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
func (gi *githubImporter) ensureTimelineItem(repo *cache.RepoCache, b *cache.BugCache, cursor githubv4.String, item timelineItem, rootVariables map[string]interface{}) error {
|
2018-10-02 14:02:16 +03:00
|
|
|
fmt.Printf("import %s\n", item.Typename)
|
|
|
|
|
2018-09-25 20:10:38 +03:00
|
|
|
switch item.Typename {
|
|
|
|
case "IssueComment":
|
2019-01-19 18:01:06 +03:00
|
|
|
return gi.ensureComment(repo, b, cursor, item.IssueComment, rootVariables)
|
2018-09-25 20:10:38 +03:00
|
|
|
|
|
|
|
case "LabeledEvent":
|
2018-10-02 14:16:07 +03:00
|
|
|
id := parseId(item.LabeledEvent.Id)
|
|
|
|
_, err := b.ResolveTargetWithMetadata(keyGithubId, id)
|
|
|
|
if err != cache.ErrNoMatchingOp {
|
|
|
|
return err
|
|
|
|
}
|
2019-01-19 18:01:06 +03:00
|
|
|
author, err := gi.makePerson(repo, item.LabeledEvent.Actor)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-10-02 14:16:07 +03:00
|
|
|
_, err = b.ChangeLabelsRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
author,
|
2018-09-25 20:10:38 +03:00
|
|
|
item.LabeledEvent.CreatedAt.Unix(),
|
|
|
|
[]string{
|
|
|
|
string(item.LabeledEvent.Label.Name),
|
|
|
|
},
|
|
|
|
nil,
|
2018-10-02 14:16:07 +03:00
|
|
|
map[string]string{keyGithubId: id},
|
2018-09-25 20:10:38 +03:00
|
|
|
)
|
|
|
|
return err
|
|
|
|
|
|
|
|
case "UnlabeledEvent":
|
2018-10-02 14:16:07 +03:00
|
|
|
id := parseId(item.UnlabeledEvent.Id)
|
|
|
|
_, err := b.ResolveTargetWithMetadata(keyGithubId, id)
|
|
|
|
if err != cache.ErrNoMatchingOp {
|
|
|
|
return err
|
|
|
|
}
|
2019-01-19 18:01:06 +03:00
|
|
|
author, err := gi.makePerson(repo, item.UnlabeledEvent.Actor)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-10-02 14:16:07 +03:00
|
|
|
_, err = b.ChangeLabelsRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
author,
|
2018-09-25 20:10:38 +03:00
|
|
|
item.UnlabeledEvent.CreatedAt.Unix(),
|
|
|
|
nil,
|
|
|
|
[]string{
|
|
|
|
string(item.UnlabeledEvent.Label.Name),
|
|
|
|
},
|
2018-10-02 14:16:07 +03:00
|
|
|
map[string]string{keyGithubId: id},
|
2018-09-25 20:10:38 +03:00
|
|
|
)
|
|
|
|
return err
|
|
|
|
|
|
|
|
case "ClosedEvent":
|
2018-10-02 14:16:07 +03:00
|
|
|
id := parseId(item.ClosedEvent.Id)
|
|
|
|
_, err := b.ResolveTargetWithMetadata(keyGithubId, id)
|
|
|
|
if err != cache.ErrNoMatchingOp {
|
|
|
|
return err
|
|
|
|
}
|
2019-01-19 18:01:06 +03:00
|
|
|
author, err := gi.makePerson(repo, item.ClosedEvent.Actor)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-09-25 20:10:38 +03:00
|
|
|
return b.CloseRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
author,
|
2018-09-25 20:10:38 +03:00
|
|
|
item.ClosedEvent.CreatedAt.Unix(),
|
2018-10-02 14:16:07 +03:00
|
|
|
map[string]string{keyGithubId: id},
|
2018-09-25 20:10:38 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
case "ReopenedEvent":
|
2018-10-02 14:16:07 +03:00
|
|
|
id := parseId(item.ReopenedEvent.Id)
|
|
|
|
_, err := b.ResolveTargetWithMetadata(keyGithubId, id)
|
|
|
|
if err != cache.ErrNoMatchingOp {
|
|
|
|
return err
|
|
|
|
}
|
2019-01-19 18:01:06 +03:00
|
|
|
author, err := gi.makePerson(repo, item.ReopenedEvent.Actor)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-09-25 20:10:38 +03:00
|
|
|
return b.OpenRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
author,
|
2018-09-25 20:10:38 +03:00
|
|
|
item.ReopenedEvent.CreatedAt.Unix(),
|
2018-10-02 14:16:07 +03:00
|
|
|
map[string]string{keyGithubId: id},
|
2018-09-25 20:10:38 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
case "RenamedTitleEvent":
|
2018-10-02 14:16:07 +03:00
|
|
|
id := parseId(item.RenamedTitleEvent.Id)
|
|
|
|
_, err := b.ResolveTargetWithMetadata(keyGithubId, id)
|
|
|
|
if err != cache.ErrNoMatchingOp {
|
|
|
|
return err
|
|
|
|
}
|
2019-01-19 18:01:06 +03:00
|
|
|
author, err := gi.makePerson(repo, item.RenamedTitleEvent.Actor)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-09-25 20:10:38 +03:00
|
|
|
return b.SetTitleRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
author,
|
2018-09-25 20:10:38 +03:00
|
|
|
item.RenamedTitleEvent.CreatedAt.Unix(),
|
|
|
|
string(item.RenamedTitleEvent.CurrentTitle),
|
2018-10-02 14:16:07 +03:00
|
|
|
map[string]string{keyGithubId: id},
|
2018-09-25 20:10:38 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
default:
|
|
|
|
fmt.Println("ignore event ", item.Typename)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
func (gi *githubImporter) ensureComment(repo *cache.RepoCache, b *cache.BugCache, cursor githubv4.String, comment issueComment, rootVariables map[string]interface{}) error {
|
2018-10-02 14:02:16 +03:00
|
|
|
target, err := b.ResolveTargetWithMetadata(keyGithubId, parseId(comment.Id))
|
|
|
|
if err != nil && err != cache.ErrNoMatchingOp {
|
|
|
|
// real error
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
author, err := gi.makePerson(repo, comment.Author)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-10-02 14:02:16 +03:00
|
|
|
// if there is no edit, the UserContentEdits given by github is empty. That
|
|
|
|
// means that the original message is given by the comment message.
|
2018-10-05 18:53:06 +03:00
|
|
|
//
|
2018-10-02 14:02:16 +03:00
|
|
|
// if there is edits, the UserContentEdits given by github contains both the
|
|
|
|
// original message and the following edits. The comment message give the last
|
|
|
|
// version so we don't care about that.
|
2018-10-05 18:53:06 +03:00
|
|
|
//
|
|
|
|
// the tricky part: for a comment older than the UserContentEdits API, github
|
|
|
|
// doesn't have the previous message version anymore and give an edition
|
|
|
|
// with .Diff == nil. We have to filter them.
|
2018-10-02 14:02:16 +03:00
|
|
|
|
|
|
|
if len(comment.UserContentEdits.Nodes) == 0 {
|
|
|
|
if err == cache.ErrNoMatchingOp {
|
|
|
|
err = b.AddCommentRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
author,
|
2018-10-02 14:02:16 +03:00
|
|
|
comment.CreatedAt.Unix(),
|
|
|
|
cleanupText(string(comment.Body)),
|
|
|
|
nil,
|
|
|
|
map[string]string{
|
|
|
|
keyGithubId: parseId(comment.Id),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// reverse the order, because github
|
|
|
|
reverseEdits(comment.UserContentEdits.Nodes)
|
|
|
|
|
|
|
|
for i, edit := range comment.UserContentEdits.Nodes {
|
2018-10-05 18:53:06 +03:00
|
|
|
if target != "" && i == 0 {
|
2018-10-02 14:02:16 +03:00
|
|
|
// The first edit in the github result is the comment creation itself, we already have that
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-10-05 18:53:06 +03:00
|
|
|
if target == "" {
|
|
|
|
if edit.Diff == nil {
|
|
|
|
// not enough data given by github for old edit, ignore them
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
err = b.AddCommentRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
author,
|
2018-10-05 18:53:06 +03:00
|
|
|
comment.CreatedAt.Unix(),
|
|
|
|
cleanupText(string(*edit.Diff)),
|
|
|
|
nil,
|
|
|
|
map[string]string{
|
|
|
|
keyGithubId: parseId(comment.Id),
|
|
|
|
keyGithubUrl: comment.Url.String(),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
err := gi.ensureCommentEdit(repo, b, target, edit)
|
2018-10-02 14:02:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !comment.UserContentEdits.PageInfo.HasNextPage {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have more edit, querying them
|
|
|
|
|
|
|
|
q := &commentEditQuery{}
|
|
|
|
variables := map[string]interface{}{
|
|
|
|
"owner": rootVariables["owner"],
|
|
|
|
"name": rootVariables["name"],
|
|
|
|
"issueFirst": rootVariables["issueFirst"],
|
|
|
|
"issueAfter": rootVariables["issueAfter"],
|
|
|
|
"timelineFirst": githubv4.Int(1),
|
|
|
|
"timelineAfter": cursor,
|
|
|
|
"commentEditLast": githubv4.Int(10),
|
|
|
|
"commentEditBefore": comment.UserContentEdits.PageInfo.StartCursor,
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
2018-10-06 12:55:16 +03:00
|
|
|
err := gi.client.Query(context.TODO(), &q, variables)
|
2018-10-02 14:02:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
edits := q.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits
|
|
|
|
|
|
|
|
if len(edits.Nodes) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, edit := range edits.Nodes {
|
|
|
|
if i == 0 {
|
|
|
|
// The first edit in the github result is the creation itself, we already have that
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
err := gi.ensureCommentEdit(repo, b, target, edit)
|
2018-10-02 14:02:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !edits.PageInfo.HasNextPage {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
variables["commentEditBefore"] = edits.PageInfo.StartCursor
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: check + import files
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
func (gi *githubImporter) ensureCommentEdit(repo *cache.RepoCache, b *cache.BugCache, target git.Hash, edit userContentEdit) error {
|
2018-10-05 18:53:06 +03:00
|
|
|
if edit.Diff == nil {
|
|
|
|
// this happen if the event is older than early 2018, Github doesn't have the data before that.
|
|
|
|
// Best we can do is to ignore the event.
|
|
|
|
return nil
|
2018-10-02 00:34:45 +03:00
|
|
|
}
|
|
|
|
|
2018-10-05 18:53:06 +03:00
|
|
|
if edit.Editor == nil {
|
|
|
|
return fmt.Errorf("no editor")
|
2018-10-02 00:34:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
_, err := b.ResolveTargetWithMetadata(keyGithubId, parseId(edit.Id))
|
|
|
|
if err == nil {
|
|
|
|
// already imported
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if err != cache.ErrNoMatchingOp {
|
|
|
|
// real error
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-10-05 18:53:06 +03:00
|
|
|
fmt.Println("import edition")
|
2018-10-02 00:34:45 +03:00
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
editor, err := gi.makePerson(repo, edit.Editor)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-10-02 00:34:45 +03:00
|
|
|
switch {
|
|
|
|
case edit.DeletedAt != nil:
|
|
|
|
// comment deletion, not supported yet
|
|
|
|
|
|
|
|
case edit.DeletedAt == nil:
|
|
|
|
// comment edition
|
|
|
|
err := b.EditCommentRaw(
|
2019-01-19 18:01:06 +03:00
|
|
|
editor,
|
2018-10-02 00:34:45 +03:00
|
|
|
edit.CreatedAt.Unix(),
|
2018-10-02 14:02:16 +03:00
|
|
|
target,
|
2018-10-02 00:34:45 +03:00
|
|
|
cleanupText(string(*edit.Diff)),
|
|
|
|
map[string]string{
|
|
|
|
keyGithubId: parseId(edit.Id),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-09-25 20:10:38 +03:00
|
|
|
// makePerson create a bug.Person from the Github data
|
2019-02-17 18:12:06 +03:00
|
|
|
func (gi *githubImporter) makePerson(repo *cache.RepoCache, actor *actor) (*cache.IdentityCache, error) {
|
2019-01-19 18:01:06 +03:00
|
|
|
// When a user has been deleted, Github return a null actor, while displaying a profile named "ghost"
|
|
|
|
// in it's UI. So we need a special case to get it.
|
2018-10-06 12:55:16 +03:00
|
|
|
if actor == nil {
|
2019-01-19 18:01:06 +03:00
|
|
|
return gi.getGhost(repo)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look first in the cache
|
|
|
|
i, err := repo.ResolveIdentityImmutableMetadata(keyGithubLogin, string(actor.Login))
|
|
|
|
if err == nil {
|
|
|
|
return i, nil
|
|
|
|
}
|
|
|
|
if _, ok := err.(identity.ErrMultipleMatch); ok {
|
|
|
|
return nil, err
|
2018-10-06 12:55:16 +03:00
|
|
|
}
|
2019-01-19 18:01:06 +03:00
|
|
|
|
2018-10-07 19:27:23 +03:00
|
|
|
var name string
|
|
|
|
var email string
|
|
|
|
|
|
|
|
switch actor.Typename {
|
|
|
|
case "User":
|
|
|
|
if actor.User.Name != nil {
|
|
|
|
name = string(*(actor.User.Name))
|
|
|
|
}
|
|
|
|
email = string(actor.User.Email)
|
|
|
|
case "Organization":
|
|
|
|
if actor.Organization.Name != nil {
|
|
|
|
name = string(*(actor.Organization.Name))
|
|
|
|
}
|
|
|
|
if actor.Organization.Email != nil {
|
|
|
|
email = string(*(actor.Organization.Email))
|
|
|
|
}
|
|
|
|
case "Bot":
|
|
|
|
}
|
2018-10-06 12:55:16 +03:00
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
return repo.NewIdentityRaw(
|
|
|
|
name,
|
|
|
|
email,
|
|
|
|
string(actor.Login),
|
|
|
|
string(actor.AvatarUrl),
|
|
|
|
map[string]string{
|
|
|
|
keyGithubLogin: string(actor.Login),
|
|
|
|
},
|
|
|
|
)
|
2018-09-25 20:10:38 +03:00
|
|
|
}
|
|
|
|
|
2019-02-17 18:12:06 +03:00
|
|
|
func (gi *githubImporter) getGhost(repo *cache.RepoCache) (*cache.IdentityCache, error) {
|
2019-01-19 18:01:06 +03:00
|
|
|
// Look first in the cache
|
|
|
|
i, err := repo.ResolveIdentityImmutableMetadata(keyGithubLogin, "ghost")
|
|
|
|
if err == nil {
|
|
|
|
return i, nil
|
|
|
|
}
|
|
|
|
if _, ok := err.(identity.ErrMultipleMatch); ok {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-10-06 12:55:16 +03:00
|
|
|
var q userQuery
|
|
|
|
|
|
|
|
variables := map[string]interface{}{
|
|
|
|
"login": githubv4.String("ghost"),
|
|
|
|
}
|
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
err = gi.client.Query(context.TODO(), &q, variables)
|
2018-10-06 12:55:16 +03:00
|
|
|
if err != nil {
|
2019-01-19 18:01:06 +03:00
|
|
|
return nil, err
|
2018-10-06 12:55:16 +03:00
|
|
|
}
|
|
|
|
|
2018-10-07 19:27:23 +03:00
|
|
|
var name string
|
|
|
|
if q.User.Name != nil {
|
|
|
|
name = string(*q.User.Name)
|
|
|
|
}
|
|
|
|
|
2019-01-19 18:01:06 +03:00
|
|
|
return repo.NewIdentityRaw(
|
2019-01-17 05:09:08 +03:00
|
|
|
name,
|
2019-01-19 18:01:06 +03:00
|
|
|
string(q.User.Email),
|
2019-01-17 05:09:08 +03:00
|
|
|
string(q.User.Login),
|
|
|
|
string(q.User.AvatarUrl),
|
2019-01-19 18:01:06 +03:00
|
|
|
map[string]string{
|
|
|
|
keyGithubLogin: string(q.User.Login),
|
|
|
|
},
|
2019-01-17 05:09:08 +03:00
|
|
|
)
|
2018-10-06 12:55:16 +03:00
|
|
|
}
|
|
|
|
|
2018-09-25 20:10:38 +03:00
|
|
|
// parseId convert the unusable githubv4.ID (an interface{}) into a string
|
|
|
|
func parseId(id githubv4.ID) string {
|
|
|
|
return fmt.Sprintf("%v", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func cleanupText(text string) string {
|
|
|
|
// windows new line, Github, really ?
|
2018-10-02 14:16:07 +03:00
|
|
|
text = strings.Replace(text, "\r\n", "\n", -1)
|
|
|
|
|
|
|
|
// trim extra new line not displayed in the github UI but still present in the data
|
|
|
|
return strings.TrimSpace(text)
|
2018-09-25 20:10:38 +03:00
|
|
|
}
|
2018-10-02 00:34:45 +03:00
|
|
|
|
|
|
|
func reverseEdits(edits []userContentEdit) []userContentEdit {
|
|
|
|
for i, j := 0, len(edits)-1; i < j; i, j = i+1, j-1 {
|
|
|
|
edits[i], edits[j] = edits[j], edits[i]
|
|
|
|
}
|
|
|
|
return edits
|
|
|
|
}
|