diff options
author | Alexander Scharinger <rng.dynamics@gmail.com> | 2021-01-24 21:27:14 +0100 |
---|---|---|
committer | Alexander Scharinger <rng.dynamics@gmail.com> | 2021-01-24 21:37:44 +0100 |
commit | 2d6f34acadc9df4461127b994b0b01b98ff52d43 (patch) | |
tree | 2596d224b30c43c8a24235183460a3e4efc4826b | |
parent | 2c0cf105278389f2c9cd3ce875b64ae36731a1ab (diff) | |
download | git-bug-2d6f34acadc9df4461127b994b0b01b98ff52d43.tar.gz |
Integrate new Github Bridge import
-rw-r--r-- | bridge/github/import.go | 2 | ||||
-rw-r--r-- | bridge/github/import_query.go | 97 | ||||
-rw-r--r-- | bridge/github/iterator.go | 959 |
3 files changed, 246 insertions, 812 deletions
diff --git a/bridge/github/import.go b/bridge/github/import.go index 78e93436..e8a4d3cb 100644 --- a/bridge/github/import.go +++ b/bridge/github/import.go @@ -100,7 +100,7 @@ func (gi *githubImporter) ImportAll(ctx context.Context, repo *cache.RepoCache, return out, nil } -func (gi *githubImporter) ensureIssue(repo *cache.RepoCache, issue issueTimeline) (*cache.BugCache, error) { +func (gi *githubImporter) ensureIssue(repo *cache.RepoCache, issue issue) (*cache.BugCache, error) { // ensure issue author author, err := gi.ensurePerson(repo, issue.Author) if err != nil { diff --git a/bridge/github/import_query.go b/bridge/github/import_query.go index ef26b3e8..52aef9e9 100644 --- a/bridge/github/import_query.go +++ b/bridge/github/import_query.go @@ -47,14 +47,9 @@ type userContentEdit struct { } type issueComment struct { - authorEvent - Body githubv4.String - Url githubv4.URI - - UserContentEdits struct { - Nodes []userContentEdit - PageInfo pageInfo - } `graphql:"userContentEdits(last: $commentEditLast, before: $commentEditBefore)"` + authorEvent // NOTE: contains Id + Body githubv4.String + Url githubv4.URI } type timelineItem struct { @@ -96,86 +91,6 @@ type timelineItem struct { } `graphql:"... on RenamedTitleEvent"` } -type issueTimeline struct { - authorEvent - Title string - Number githubv4.Int - Body githubv4.String - Url githubv4.URI - - TimelineItems struct { - Edges []struct { - Cursor githubv4.String - Node timelineItem - } - PageInfo pageInfo - } `graphql:"timelineItems(first: $timelineFirst, after: $timelineAfter)"` - - UserContentEdits struct { - Nodes []userContentEdit - PageInfo pageInfo - } `graphql:"userContentEdits(last: $issueEditLast, before: $issueEditBefore)"` -} - -// Alex -type timelineItemsQuery struct { - Repository struct { - Issue struct { - TimelineItems struct { - Edges []struct { - Cursor githubv4.String - Node timelineItem - } - PageInfo pageInfo - } `graphql:"timelineItems(first: $timelineFirst, after: $timelineAfter)"` - } `graphql:"issue(number: $issueNumber)"` - } `graphql:"repository(owner: $owner, name: $name)"` -} - -type issueEdit struct { - UserContentEdits struct { - Nodes []userContentEdit - PageInfo pageInfo - } `graphql:"userContentEdits(last: $issueEditLast, before: $issueEditBefore)"` -} - -type issueTimelineQuery struct { - Repository struct { - Issues struct { - Nodes []issueTimeline - PageInfo pageInfo - } `graphql:"issues(first: $issueFirst, after: $issueAfter, orderBy: {field: CREATED_AT, direction: ASC}, filterBy: {since: $issueSince})"` - } `graphql:"repository(owner: $owner, name: $name)"` -} - -type issueEditQuery struct { - Repository struct { - Issues struct { - Nodes []issueEdit - PageInfo pageInfo - } `graphql:"issues(first: $issueFirst, after: $issueAfter, orderBy: {field: CREATED_AT, direction: ASC}, filterBy: {since: $issueSince})"` - } `graphql:"repository(owner: $owner, name: $name)"` -} - -type commentEditQuery struct { - Repository struct { - Issues struct { - Nodes []struct { - Timeline struct { - Nodes []struct { - IssueComment struct { - UserContentEdits struct { - Nodes []userContentEdit - PageInfo pageInfo - } `graphql:"userContentEdits(last: $commentEditLast, before: $commentEditBefore)"` - } `graphql:"... on IssueComment"` - } - } `graphql:"timeline(first: $timelineFirst, after: $timelineAfter)"` - } - } `graphql:"issues(first: $issueFirst, after: $issueAfter, orderBy: {field: CREATED_AT, direction: ASC}, filterBy: {since: $issueSince})"` - } `graphql:"repository(owner: $owner, name: $name)"` -} - type ghostQuery struct { User struct { Login githubv4.String @@ -209,7 +124,7 @@ type issueQuery struct { Issues struct { Nodes []issue PageInfo pageInfo - } `graphql:"issues(first: $issueFirst, after: $issueAfter, orderBy: {field: CREATED_AT, direction: ASC})"` //, filterBy: {since: $issueSince})"` + } `graphql:"issues(first: $issueFirst, after: $issueAfter, orderBy: {field: CREATED_AT, direction: ASC}), filterBy: {since: $issueSince})"` } `graphql:"repository(owner: $owner, name: $name)"` } @@ -221,7 +136,7 @@ type issue struct { Url githubv4.URI } -type issueEditQuery_A struct { +type issueEditQuery struct { Node struct { Typename githubv4.String `graphql:"__typename"` Issue struct { @@ -246,7 +161,7 @@ type timelineQuery struct { } `graphql:"node(id: $gqlNodeId)"` } -type commentEditQuery_A struct { +type commentEditQuery struct { Node struct { Typename githubv4.String `graphql:"__typename"` IssueComment struct { diff --git a/bridge/github/iterator.go b/bridge/github/iterator.go index 76c5cbfa..ab473a52 100644 --- a/bridge/github/iterator.go +++ b/bridge/github/iterator.go @@ -2,790 +2,342 @@ package github import ( "context" - "fmt" "time" - - "github.com/pkg/errors" + "github.com/pkg/errors" "github.com/shurcooL/githubv4" ) -type iterator_A struct { - gc *githubv4.Client - since time.Time - ctx context.Context - err error - issueIter issueIter +type iterator struct { + gc *githubv4.Client + since time.Time + ctx context.Context + err error + issueIter issueIter } type issueIter struct { - iterVars - query issueQuery - issueEditIter []issueEditIter - timelineIter []timelineIter + iterVars + query issueQuery + issueEditIter []issueEditIter + timelineIter []timelineIter } type issueEditIter struct { - iterVars - query issueEditQuery_A + iterVars + query issueEditQuery } type timelineIter struct { - iterVars - query timelineQuery - commentEditIter []commentEditIter + iterVars + query timelineQuery + commentEditIter []commentEditIter } - type commentEditIter struct { - iterVars - query commentEditQuery_A + iterVars + query commentEditQuery } type iterVars struct { - index int - capacity int - variables varmap + index int + capacity int + variables varmap } type varmap map[string]interface{} +func newIterVars(capacity int) iterVars { + return iterVars{ + index: -1, + capacity: capacity, + variables: varmap{}, + } +} -func NewIterator_A(ctx context.Context, client *githubv4.Client, capacity int, owner, project string, since time.Time) *iterator_A { - i := &iterator_A{ - gc: client, - since: since, - ctx: ctx, - issueIter: issueIter{ - iterVars: newIterVars(capacity), - }, - } +func NewIterator(ctx context.Context, client *githubv4.Client, capacity int, owner, project string, since time.Time) *iterator { + i := &iterator{ + gc: client, + since: since, + ctx: ctx, + issueIter: issueIter{ + iterVars: newIterVars(capacity), + timelineIter: make([]timelineIter, capacity), + issueEditIter: make([]issueEditIter, capacity), + }, + } i.issueIter.variables.setOwnerProject(owner, project) - for idx := range i.issueIter.issueEditIter { - ie := &i.issueIter.issueEditIter[idx] - ie.iterVars = newIterVars(capacity) - } - for i1 := range i.issueIter.timelineIter { - tli := &i.issueIter.timelineIter[i1] - tli.iterVars = newIterVars(capacity) + for idx := range i.issueIter.issueEditIter { + ie := &i.issueIter.issueEditIter[idx] + ie.iterVars = newIterVars(capacity) + } + for i1 := range i.issueIter.timelineIter { + tli := &i.issueIter.timelineIter[i1] + tli.iterVars = newIterVars(capacity) + tli.commentEditIter = make([]commentEditIter, capacity) + for i2 := range tli.commentEditIter { + cei := &tli.commentEditIter[i2] + cei.iterVars = newIterVars(capacity) + } } i.resetIssueVars() return i } -func newIterVars(capacity int) iterVars { - return iterVars{ - index: -1, - capacity: capacity, - variables: varmap{}, - } -} - func (v *varmap) setOwnerProject(owner, project string) { - (*v)["owner"] = githubv4.String(owner) - (*v)["name"] = githubv4.String(project) -} - -func (i *iterator_A) resetIssueVars() { - vars := &i.issueIter.variables - (*vars)["issueFirst"] = githubv4.Int(i.issueIter.capacity) - (*vars)["issueAfter"] = (*githubv4.String)(nil) - // I am not sure if the since variable should be used. - //(*vars)["issueSince"] = githubv4.DateTime{Time: i.since} - i.issueIter.query.Repository.Issues.PageInfo.HasNextPage = true - i.issueIter.query.Repository.Issues.PageInfo.EndCursor = "" + (*v)["owner"] = githubv4.String(owner) + (*v)["name"] = githubv4.String(project) } -func (i *iterator_A) resetIssueEditVars() { - for idx := range i.issueIter.issueEditIter { - ie := &i.issueIter.issueEditIter[idx] - ie.variables["issueEditLast"] = githubv4.Int(ie.capacity) - ie.variables["issueEditBefore"] = (*githubv4.String)(nil) - ie.query.Node.Issue.UserContentEdits.PageInfo.HasNextPage = true - ie.query.Node.Issue.UserContentEdits.PageInfo.EndCursor = "" - } +func (i *iterator) resetIssueVars() { + vars := &i.issueIter.variables + (*vars)["issueFirst"] = githubv4.Int(i.issueIter.capacity) + (*vars)["issueAfter"] = (*githubv4.String)(nil) + // Only query issues after the given date. This varaible is used in the GraphQL query. + (*vars)["issueSince"] = githubv4.DateTime{Time: i.since} + i.issueIter.query.Repository.Issues.PageInfo.HasNextPage = true + i.issueIter.query.Repository.Issues.PageInfo.EndCursor = "" } -func (i *iterator_A) resetTimelineVars() { - for idx := range i.issueIter.timelineIter { - ip := &i.issueIter.timelineIter[idx] - ip.variables["timelineFirst"] = githubv4.Int(ip.capacity) - ip.variables["timelineAfter"] = (*githubv4.String)(nil) - ip.query.Node.Issue.TimelineItems.PageInfo.HasNextPage = true - ip.query.Node.Issue.TimelineItems.PageInfo.EndCursor = "" - } +func (i *iterator) resetIssueEditVars() { + for idx := range i.issueIter.issueEditIter { + ie := &i.issueIter.issueEditIter[idx] + ie.variables["issueEditLast"] = githubv4.Int(ie.capacity) + ie.variables["issueEditBefore"] = (*githubv4.String)(nil) + ie.query.Node.Issue.UserContentEdits.PageInfo.HasNextPage = true + ie.query.Node.Issue.UserContentEdits.PageInfo.EndCursor = "" + } } -func (i *iterator_A) currIssueItem() *issue { - return &i.issueIter.query.Repository.Issues.Nodes[i.issueIter.index] +func (i *iterator) resetTimelineVars() { + for idx := range i.issueIter.timelineIter { + ip := &i.issueIter.timelineIter[idx] + ip.variables["timelineFirst"] = githubv4.Int(ip.capacity) + ip.variables["timelineAfter"] = (*githubv4.String)(nil) + ip.query.Node.Issue.TimelineItems.PageInfo.HasNextPage = true + ip.query.Node.Issue.TimelineItems.PageInfo.EndCursor = "" + } } -func (i *iterator_A) currIssueEditIter() *issueEditIter { - return &i.issueIter.issueEditIter[i.issueIter.index] +func (i *iterator) resetCommentEditVars() { + for i1 := range i.issueIter.timelineIter { + for i2 := range i.issueIter.timelineIter[i1].commentEditIter { + ce := &i.issueIter.timelineIter[i1].commentEditIter[i2] + ce.variables["commentEditLast"] = githubv4.Int(ce.capacity) + ce.variables["commentEditBefore"] = (*githubv4.String)(nil) + ce.query.Node.IssueComment.UserContentEdits.PageInfo.HasNextPage = true + ce.query.Node.IssueComment.UserContentEdits.PageInfo.EndCursor = "" + } + } } -func (i *iterator_A) currTimelineIter() *timelineIter { - return &i.issueIter.timelineIter[i.issueIter.index] +// Error return last encountered error +func (i *iterator) Error() error { + if i.err != nil { + return i.err + } + return i.ctx.Err() // might return nil } -func (i *iterator_A) currIssueGqlNodeId() githubv4.ID { - return i.currIssueItem().Id +func (i *iterator) HasError() bool { + return i.err != nil || i.ctx.Err() != nil } -func (i *iterator_A) currCommentEditIter() *commentEditIter { - timelineIter := i.currTimelineIter() - return &timelineIter.commentEditIter[timelineIter.index] +func (i *iterator) currIssueItem() *issue { + return &i.issueIter.query.Repository.Issues.Nodes[i.issueIter.index] } -// Error return last encountered error -func (i *iterator_A) Error() error { - if i.err != nil { - return i.err - } - return i.ctx.Err() // might return nil -} - -func (i *iterator_A) HasError() bool { - return i.err != nil || i.ctx.Err() != nil -} - -func (i *iterator_A) NextIssue() bool { - if i.HasError() { - return false - } - index := &i.issueIter.index - issues := &i.issueIter.query.Repository.Issues - issueItems := &issues.Nodes - if 0 <= *index && *index < len(*issueItems)-1 { - *index += 1 - return true - } - - if !issues.PageInfo.HasNextPage { - return false - } - nextIssue := i.queryIssue() - return nextIssue -} - -func (i *iterator_A) IssueValue() issue { - return *i.currIssueItem() -} - -func (i *iterator_A) queryIssue() bool { - ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout) - defer cancel() - if endCursor := i.issueIter.query.Repository.Issues.PageInfo.EndCursor; endCursor != "" { - i.issueIter.variables["issueAfter"] = endCursor - } - if err := i.gc.Query(ctx, &i.issueIter.query, i.issueIter.variables); err != nil { - i.err = err - return false - } - i.resetIssueEditVars() - i.resetTimelineVars() - issueItems := &i.issueIter.query.Repository.Issues.Nodes - if len(*issueItems) <= 0 { - i.issueIter.index = -1 - return false - } - i.issueIter.index = 0 - return true -} - -func (i *iterator_A) NextIssueEdit() bool { - if i.HasError() { - return false - } - ieIter := i.currIssueEditIter() - ieIdx := &ieIter.index - ieItems := ieIter.query.Node.Issue.UserContentEdits - if 0 <= *ieIdx && *ieIdx < len(ieItems.Nodes)-1 { - *ieIdx += 1 - return i.nextValidIssueEdit() - } - if !ieItems.PageInfo.HasNextPage { - return false - } - querySucc := i.queryIssueEdit() - if !querySucc { - return false - } - return i.nextValidIssueEdit() -} - -func (i *iterator_A) nextValidIssueEdit() bool { - // issueEdit.Diff == nil 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. - if issueEdit := i.IssueEditValue(); issueEdit.Diff == nil || string(*issueEdit.Diff) == "" { - return i.NextIssueEdit() - } - return true -} - -func (i *iterator_A) IssueEditValue() userContentEdit { - iei := i.currIssueEditIter() - return iei.query.Node.Issue.UserContentEdits.Nodes[iei.index] -} - -func (i *iterator_A) queryIssueEdit() bool { - ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout) - defer cancel() - iei := i.currIssueEditIter() - if endCursor := iei.query.Node.Issue.UserContentEdits.PageInfo.EndCursor; endCursor != "" { - iei.variables["issueEditBefore"] = endCursor - } - iei.variables["gqlNodeId"] = i.currIssueGqlNodeId() - if err := i.gc.Query(ctx, &iei.query, iei.variables); err != nil { - i.err = err - return false - } - issueEditItems := iei.query.Node.Issue.UserContentEdits.Nodes - if len(issueEditItems) <= 0 { - iei.index = -1 - return false - } - // The UserContentEditConnection in the Github API serves its elements in reverse chronological - // order. For our purpose we have to reverse the edits. - reverseEdits(issueEditItems) - iei.index = 0 - return true -} - -func (i *iterator_A) NextTimelineItem() bool { - if i.HasError() { - return false - } - tlIter := &i.issueIter.timelineIter[i.issueIter.index] - tlIdx := &tlIter.index - tlItems := tlIter.query.Node.Issue.TimelineItems - if 0 <= *tlIdx && *tlIdx < len(tlItems.Nodes)-1 { - *tlIdx += 1 - return true - } - if !tlItems.PageInfo.HasNextPage { - return false - } - nextTlItem := i.queryTimeline() - return nextTlItem -} - -func (i *iterator_A) TimelineItemValue() timelineItem { - tli := i.currTimelineIter() - return tli.query.Node.Issue.TimelineItems.Nodes[tli.index] -} - -func (i *iterator_A) queryTimeline() bool { - ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout) - defer cancel() - tli := i.currTimelineIter() - if endCursor := tli.query.Node.Issue.TimelineItems.PageInfo.EndCursor; endCursor != "" { - tli.variables["timelineAfter"] = endCursor - } - tli.variables["gqlNodeId"] = i.currIssueGqlNodeId() - if err := i.gc.Query(ctx, &tli.query, tli.variables); err != nil { - i.err = err - return false - } - //i.resetCommentEditVars() - timelineItems := &tli.query.Node.Issue.TimelineItems - if len(timelineItems.Nodes) <= 0 { - tli.index = -1 - return false - } - tli.index = 0 - return true -} - -func (i *iterator_A) NextCommentEdit() bool { - if i.HasError() { - return false - } - - tmlnVal := i.TimelineItemValue() - if tmlnVal.Typename != "IssueComment" { - // The timeline iterator does not point to a comment. - i.err = errors.New("Call to NextCommentEdit() while timeline item is not a comment") - return false - } - - cei := i.currCommentEditIter() - ceIdx := &cei.index - ceItems := &cei.query.Node.IssueComment.UserContentEdits - if 0 <= *ceIdx && *ceIdx < len(ceItems.Nodes)-1 { - *ceIdx += 1 - return i.nextValidCommentEdit() - } - if !ceItems.PageInfo.HasNextPage { - return false - } - querySucc := i.queryCommentEdit() - if !querySucc { - return false - } - return i.nextValidCommentEdit() -} - -func (i *iterator_A) nextValidCommentEdit() bool { - // if comment edit diff is a nil pointer or points to an empty string look for next value - if commentEdit := i.CommentEditValue(); commentEdit.Diff == nil || string(*commentEdit.Diff) == "" { - return i.NextCommentEdit() - } - return true -} - -func (i *iterator_A) CommentEditValue() userContentEdit { - cei := i.currCommentEditIter() - return cei.query.Node.IssueComment.UserContentEdits.Nodes[cei.index] -} - - -func (i *iterator_A) queryCommentEdit() bool { - ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout) - defer cancel() - cei := i.currCommentEditIter() - - if endCursor := cei.query.Node.IssueComment.UserContentEdits.PageInfo.EndCursor; endCursor != "" { - cei.variables["commentEditBefore"] = endCursor - } - tmlnVal := i.TimelineItemValue() - if tmlnVal.Typename != "IssueComment" { - i.err = errors.New("Call to queryCommentEdit() while timeline item is not a comment") - return false - } - cei.variables["gqlNodeId"] = tmlnVal.IssueComment.Id - if err := i.gc.Query(ctx, &cei.query, cei.variables); err != nil { - i.err = err - return false - } - ceItems := cei.query.Node.IssueComment.UserContentEdits.Nodes - if len(ceItems) <= 0 { - cei.index = -1 - return false - } - // The UserContentEditConnection in the Github API serves its elements in reverse chronological - // order. For our purpose we have to reverse the edits. - reverseEdits(ceItems) - cei.index = 0 - return true -} - - -type indexer struct{ index int } - -type issueEditIterator struct { - index int - query issueEditQuery - variables map[string]interface{} +func (i *iterator) currIssueEditIter() *issueEditIter { + return &i.issueIter.issueEditIter[i.issueIter.index] } -type commentEditIterator struct { - index int - query commentEditQuery - variables map[string]interface{} +func (i *iterator) currTimelineIter() *timelineIter { + return &i.issueIter.timelineIter[i.issueIter.index] } -type timelineIterator struct { - index int - query issueTimelineQuery - variables map[string]interface{} - - issueEdit indexer - commentEdit indexer - - // Alex: It would be really help clearity to get rid of this variable. - // lastEndCursor cache the timeline end cursor for one iteration - lastEndCursor githubv4.String +func (i *iterator) currCommentEditIter() *commentEditIter { + timelineIter := i.currTimelineIter() + return &timelineIter.commentEditIter[timelineIter.index] } -type iterator struct { - // github graphql client - gc *githubv4.Client - - // if since is given the iterator will query only the updated - // and created issues after this date - since time.Time - - // number of timelines/userEditcontent/issueEdit to query - // at a time, more capacity = more used memory = less queries - // to make - capacity int - - // shared context used for all graphql queries - ctx context.Context - - // sticky error - err error - - // timeline iterator - timeline timelineIterator - - // issue edit iterator - issueEdit issueEditIterator - - // comment edit iterator - commentEdit commentEditIterator +func (i *iterator) currIssueGqlNodeId() githubv4.ID { + return i.currIssueItem().Id } -// NewIterator create and initialize a new iterator -func NewIterator(ctx context.Context, client *githubv4.Client, capacity int, owner, project string, since time.Time) *iterator { - i := &iterator{ - gc: client, - since: since, - capacity: capacity, - ctx: ctx, - timeline: timelineIterator{ - index: -1, - issueEdit: indexer{-1}, - commentEdit: indexer{-1}, - variables: map[string]interface{}{ - "owner": githubv4.String(owner), - "name": githubv4.String(project), - }, - }, - commentEdit: commentEditIterator{ - index: -1, - variables: map[string]interface{}{ - "owner": githubv4.String(owner), - "name": githubv4.String(project), - }, - }, - issueEdit: issueEditIterator{ - index: -1, - variables: map[string]interface{}{ - "owner": githubv4.String(owner), - "name": githubv4.String(project), - }, - }, +func (i *iterator) NextIssue() bool { + if i.HasError() { + return false + } + index := &i.issueIter.index + issues := &i.issueIter.query.Repository.Issues + issueItems := &issues.Nodes + if 0 <= *index && *index < len(*issueItems)-1 { + *index += 1 + return true } - i.initTimelineQueryVariables() - return i -} - -// init issue timeline variables -func (i *iterator) initTimelineQueryVariables() { - i.timeline.variables["issueFirst"] = githubv4.Int(1) // each query one single issue only - i.timeline.variables["issueAfter"] = (*githubv4.String)(nil) - i.timeline.variables["issueSince"] = githubv4.DateTime{Time: i.since} - i.timeline.variables["timelineFirst"] = githubv4.Int(i.capacity) - i.timeline.variables["timelineAfter"] = (*githubv4.String)(nil) - // Fun fact, github provide the comment edition in reverse chronological - // order, because haha. Look at me, I'm dying of laughter. - i.timeline.variables["issueEditLast"] = githubv4.Int(i.capacity) - i.timeline.variables["issueEditBefore"] = (*githubv4.String)(nil) - i.timeline.variables["commentEditLast"] = githubv4.Int(i.capacity) - i.timeline.variables["commentEditBefore"] = (*githubv4.String)(nil) -} - -// init issue edit variables -func (i *iterator) initIssueEditQueryVariables() { - i.issueEdit.variables["issueFirst"] = githubv4.Int(1) - i.issueEdit.variables["issueAfter"] = i.timeline.variables["issueAfter"] - i.issueEdit.variables["issueSince"] = githubv4.DateTime{Time: i.since} - i.issueEdit.variables["issueEditLast"] = githubv4.Int(i.capacity) - i.issueEdit.variables["issueEditBefore"] = (*githubv4.String)(nil) -} - -// init issue comment variables -func (i *iterator) initCommentEditQueryVariables() { - i.commentEdit.variables["issueFirst"] = githubv4.Int(1) - i.commentEdit.variables["issueAfter"] = i.timeline.variables["issueAfter"] - i.commentEdit.variables["issueSince"] = githubv4.DateTime{Time: i.since} - i.commentEdit.variables["timelineFirst"] = githubv4.Int(1) - i.commentEdit.variables["timelineAfter"] = (*githubv4.String)(nil) - i.commentEdit.variables["commentEditLast"] = githubv4.Int(i.capacity) - i.commentEdit.variables["commentEditBefore"] = (*githubv4.String)(nil) -} - -// reverse UserContentEdits arrays in both of the issue and -// comment timelines -func (i *iterator) reverseTimelineEditNodes() { - node := i.timeline.query.Repository.Issues.Nodes[0] - reverseEdits(node.UserContentEdits.Nodes) - for index, ce := range node.TimelineItems.Edges { - if ce.Node.Typename == "IssueComment" && len(node.TimelineItems.Edges) != 0 { - reverseEdits(node.TimelineItems.Edges[index].Node.IssueComment.UserContentEdits.Nodes) - } + if !issues.PageInfo.HasNextPage { + return false } + nextIssue := i.queryIssue() + return nextIssue } -// Error return last encountered error -func (i *iterator) Error() error { - return i.err +func (i *iterator) IssueValue() issue { + return *i.currIssueItem() } func (i *iterator) queryIssue() bool { ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout) defer cancel() - - if err := i.gc.Query(ctx, &i.timeline.query, i.timeline.variables); err != nil { + if endCursor := i.issueIter.query.Repository.Issues.PageInfo.EndCursor; endCursor != "" { + i.issueIter.variables["issueAfter"] = endCursor + } + if err := i.gc.Query(ctx, &i.issueIter.query, i.issueIter.variables); err != nil { i.err = err return false } - - issues := i.timeline.query.Repository.Issues.Nodes - if len(issues) == 0 { + i.resetIssueEditVars() + i.resetTimelineVars() + issueItems := &i.issueIter.query.Repository.Issues.Nodes + if len(*issueItems) <= 0 { + i.issueIter.index = -1 return false } - - i.reverseTimelineEditNodes() + i.issueIter.index = 0 return true } -// NextIssue try to query the next issue and return true. Only one issue is -// queried at each call. -func (i *iterator) NextIssue() bool { - if i.err != nil { +func (i *iterator) NextIssueEdit() bool { + if i.HasError() { return false } - - if i.ctx.Err() != nil { - return false + ieIter := i.currIssueEditIter() + ieIdx := &ieIter.index + ieItems := ieIter.query.Node.Issue.UserContentEdits + if 0 <= *ieIdx && *ieIdx < len(ieItems.Nodes)-1 { + *ieIdx += 1 + return i.nextValidIssueEdit() } - - // if $issueAfter variable is nil we can directly make the first query - if i.timeline.variables["issueAfter"] == (*githubv4.String)(nil) { - nextIssue := i.queryIssue() - // prevent from infinite loop by setting a non nil cursor - issues := i.timeline.query.Repository.Issues - i.timeline.variables["issueAfter"] = issues.PageInfo.EndCursor - return nextIssue + if !ieItems.PageInfo.HasNextPage { + return false } - - issues := i.timeline.query.Repository.Issues - if !issues.PageInfo.HasNextPage { + querySucc := i.queryIssueEdit() + if !querySucc { return false } + return i.nextValidIssueEdit() +} - // if we have more issues, query them - i.timeline.variables["timelineAfter"] = (*githubv4.String)(nil) - i.timeline.index = -1 - - timelineEndCursor := issues.Nodes[0].TimelineItems.PageInfo.EndCursor - // store cursor for future use - i.timeline.lastEndCursor = timelineEndCursor - - // query issue block - nextIssue := i.queryIssue() - i.timeline.variables["issueAfter"] = issues.PageInfo.EndCursor - - return nextIssue +func (i *iterator) nextValidIssueEdit() bool { + // issueEdit.Diff == nil 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. + if issueEdit := i.IssueEditValue(); issueEdit.Diff == nil || string(*issueEdit.Diff) == "" { + return i.NextIssueEdit() + } + return true } -// IssueValue return the actual issue value -func (i *iterator) IssueValue() issueTimeline { - issues := i.timeline.query.Repository.Issues - return issues.Nodes[0] +func (i *iterator) IssueEditValue() userContentEdit { + iei := i.currIssueEditIter() + return iei.query.Node.Issue.UserContentEdits.Nodes[iei.index] } -// NextTimelineItem return true if there is a next timeline item and increments the index by one. -// It is used iterates over all the timeline items. Extra queries are made if it is necessary. -func (i *iterator) NextTimelineItem() bool { - if i.err != nil { - return false +func (i *iterator) queryIssueEdit() bool { + ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout) + defer cancel() + iei := i.currIssueEditIter() + if endCursor := iei.query.Node.Issue.UserContentEdits.PageInfo.EndCursor; endCursor != "" { + iei.variables["issueEditBefore"] = endCursor } - - if i.ctx.Err() != nil { + iei.variables["gqlNodeId"] = i.currIssueGqlNodeId() + if err := i.gc.Query(ctx, &iei.query, iei.variables); err != nil { + i.err = err return false } - - timelineItems := i.timeline.query.Repository.Issues.Nodes[0].TimelineItems - // after NextIssue call it's good to check wether we have some timelineItems items or not - // Alex: Correct? - if len(timelineItems.Edges) == 0 { + issueEditItems := iei.query.Node.Issue.UserContentEdits.Nodes + if len(issueEditItems) <= 0 { + iei.index = -1 return false } + // The UserContentEditConnection in the Github API serves its elements in reverse chronological + // order. For our purpose we have to reverse the edits. + reverseEdits(issueEditItems) + iei.index = 0 + return true +} - if i.timeline.index < len(timelineItems.Edges)-1 { - i.timeline.index++ - return true - } - - if !timelineItems.PageInfo.HasNextPage { +func (i *iterator) NextTimelineItem() bool { + if i.HasError() { return false } - - i.timeline.lastEndCursor = timelineItems.PageInfo.EndCursor - - // more timelines, query them - i.timeline.variables["timelineAfter"] = timelineItems.PageInfo.EndCursor - // HACK - var query timelineItemsQuery - // var variables map[string]interface{} - variables := make(map[string]interface{}) - variables["owner"] = i.timeline.variables["owner"] - variables["name"] = i.timeline.variables["name"] - variables["issueNumber"] = i.timeline.query.Repository.Issues.Nodes[0].Number - fmt.Println("### Alex using issue number ", i.timeline.query.Repository.Issues.Nodes[0].Number) - variables["timelineFirst"] = i.timeline.variables["timelineFirst"] - variables["timelineAfter"] = i.timeline.variables["timelineAfter"] - variables["commentEditLast"] = i.timeline.variables["commentEditLast"] - variables["commentEditBefore"] = i.timeline.variables["commentEditBefore"] - - ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout) - defer cancel() - - // if err := i.gc.Query(ctx, &i.timeline.query, i.timeline.variables); err != nil { - if err := i.gc.Query(ctx, &query, variables); err != nil { - i.err = err - return false + tlIter := &i.issueIter.timelineIter[i.issueIter.index] + tlIdx := &tlIter.index + tlItems := tlIter.query.Node.Issue.TimelineItems + if 0 <= *tlIdx && *tlIdx < len(tlItems.Nodes)-1 { + *tlIdx += 1 + return true } - // HACK - fmt.Println("### Alex after the query") - i.timeline.variables["timelineFirst"] = variables["timelineFirst"] - i.timeline.variables["timelineAfter"] = variables["timelineAfter"] - i.timeline.variables["commentEditLast"] = variables["commentEditLast"] - i.timeline.variables["commentEditBefore"] = variables["commentEditBefore"] - i.timeline.query.Repository.Issues.Nodes[0].TimelineItems = query.Repository.Issue.TimelineItems - - timelineItems = i.timeline.query.Repository.Issues.Nodes[0].TimelineItems - // (in case github returns something weird) just for safety: better return a false than a panic - if len(timelineItems.Edges) == 0 { + if !tlItems.PageInfo.HasNextPage { return false } - - i.reverseTimelineEditNodes() - i.timeline.index = 0 - return true + nextTlItem := i.queryTimeline() + return nextTlItem } -// TimelineItemValue return the actual timeline item value func (i *iterator) TimelineItemValue() timelineItem { - timelineItems := i.timeline.query.Repository.Issues.Nodes[0].TimelineItems - return timelineItems.Edges[i.timeline.index].Node + tli := i.currTimelineIter() + return tli.query.Node.Issue.TimelineItems.Nodes[tli.index] } -func (i *iterator) queryIssueEdit() bool { +func (i *iterator) queryTimeline() bool { ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout) defer cancel() - - if err := i.gc.Query(ctx, &i.issueEdit.query, i.issueEdit.variables); err != nil { + tli := i.currTimelineIter() + if endCursor := tli.query.Node.Issue.TimelineItems.PageInfo.EndCursor; endCursor != "" { + tli.variables["timelineAfter"] = endCursor + } + tli.variables["gqlNodeId"] = i.currIssueGqlNodeId() + if err := i.gc.Query(ctx, &tli.query, tli.variables); err != nil { i.err = err - //i.timeline.issueEdit.index = -1 return false } - - issueEdits := i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits - // reverse issue edits because github - reverseEdits(issueEdits.Nodes) - - // this is not supposed to happen - if len(issueEdits.Nodes) == 0 { - i.timeline.issueEdit.index = -1 + i.resetCommentEditVars() + timelineItems := &tli.query.Node.Issue.TimelineItems + if len(timelineItems.Nodes) <= 0 { + tli.index = -1 return false } - - i.issueEdit.index = 0 - i.timeline.issueEdit.index = -2 - return i.nextValidIssueEdit() -} - -func (i *iterator) nextValidIssueEdit() bool { - // issueEdit.Diff == nil 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. - if issueEdit := i.IssueEditValue(); issueEdit.Diff == nil || string(*issueEdit.Diff) == "" { - return i.NextIssueEdit() - } + tli.index = 0 return true } -// NextIssueEdit return true if there is a next issue edit and increments the index by one. -// It is used iterates over all the issue edits. Extra queries are made if it is necessary. -func (i *iterator) NextIssueEdit() bool { - if i.err != nil { - return false - } - - if i.ctx.Err() != nil { - return false - } - - // this mean we looped over all available issue edits in the timeline. - // now we have to use i.issueEditQuery - if i.timeline.issueEdit.index == -2 { - issueEdits := i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits - if i.issueEdit.index < len(issueEdits.Nodes)-1 { - i.issueEdit.index++ - return i.nextValidIssueEdit() - } - - if !issueEdits.PageInfo.HasPreviousPage { - i.timeline.issueEdit.index = -1 - i.issueEdit.index = -1 - return false - } - - // if there is more edits, query them - i.issueEdit.variables["issueEditBefore"] = issueEdits.PageInfo.StartCursor - return i.queryIssueEdit() - } - - issueEdits := i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits - // if there is no edit, the UserContentEdits given by github is empty. That - // means that the original message is given by the issue message. - // - // 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. - // - // 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. - if len(issueEdits.Nodes) == 0 { +func (i *iterator) NextCommentEdit() bool { + if i.HasError() { return false } - // loop over them timeline comment edits - if i.timeline.issueEdit.index < len(issueEdits.Nodes)-1 { - i.timeline.issueEdit.index++ - return i.nextValidIssueEdit() - } - - if !issueEdits.PageInfo.HasPreviousPage { - i.timeline.issueEdit.index = -1 + tmlnVal := i.TimelineItemValue() + if tmlnVal.Typename != "IssueComment" { + // The timeline iterator does not point to a comment. + i.err = errors.New("Call to NextCommentEdit() while timeline item is not a comment") return false } - // if there is more edits, query them - i.initIssueEditQueryVariables() - i.issueEdit.variables["issueEditBefore"] = issueEdits.PageInfo.StartCursor - return i.queryIssueEdit() -} - -// IssueEditValue return the actual issue edit value -func (i *iterator) IssueEditValue() userContentEdit { - // if we are using issue edit query - if i.timeline.issueEdit.index == -2 { - issueEdits := i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits - return issueEdits.Nodes[i.issueEdit.index] + cei := i.currCommentEditIter() + ceIdx := &cei.index + ceItems := &cei.query.Node.IssueComment.UserContentEdits + if 0 <= *ceIdx && *ceIdx < len(ceItems.Nodes)-1 { + *ceIdx += 1 + return i.nextValidCommentEdit() } - - issueEdits := i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits - // else get it from timeline issue edit query - return issueEdits.Nodes[i.timeline.issueEdit.index] -} - -func (i *iterator) queryCommentEdit() bool { - ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout) - defer cancel() - - if err := i.gc.Query(ctx, &i.commentEdit.query, i.commentEdit.variables); err != nil { - i.err = err + if !ceItems.PageInfo.HasNextPage { return false } - - commentEdits := i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits - // this is not supposed to happen - if len(commentEdits.Nodes) == 0 { - i.timeline.commentEdit.index = -1 + querySucc := i.queryCommentEdit() + if !querySucc { return false } - - reverseEdits(commentEdits.Nodes) - - i.commentEdit.index = 0 - i.timeline.commentEdit.index = -2 return i.nextValidCommentEdit() } @@ -797,72 +349,39 @@ func (i *iterator) nextValidCommentEdit() bool { return true } -// NextCommentEdit return true if there is a next comment edit and increments the index by one. -// It is used iterates over all the comment edits. Extra queries are made if it is necessary. -func (i *iterator) NextCommentEdit() bool { - if i.err != nil { - return false - } - - if i.ctx.Err() != nil { - return false - } - - // same as NextIssueEdit - if i.timeline.commentEdit.index == -2 { - commentEdits := i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits - if i.commentEdit.index < len(commentEdits.Nodes)-1 { - i.commentEdit.index++ - return i.nextValidCommentEdit() - } +func (i *iterator) CommentEditValue() userContentEdit { + cei := i.currCommentEditIter() + return cei.query.Node.IssueComment.UserContentEdits.Nodes[cei.index] +} - if !commentEdits.PageInfo.HasPreviousPage { - i.timeline.commentEdit.index = -1 - i.commentEdit.index = -1 - return false - } +func (i *iterator) queryCommentEdit() bool { + ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout) + defer cancel() + cei := i.currCommentEditIter() - // if there is more comment edits, query them - i.commentEdit.variables["commentEditBefore"] = commentEdits.PageInfo.StartCursor - return i.queryCommentEdit() + if endCursor := cei.query.Node.IssueComment.UserContentEdits.PageInfo.EndCursor; endCursor != "" { + cei.variables["commentEditBefore"] = endCursor } - - commentEdits := i.timeline.query.Repository.Issues.Nodes[0].TimelineItems.Edges[i.timeline.index].Node.IssueComment - // if there is no comment edits - if len(commentEdits.UserContentEdits.Nodes) == 0 { + tmlnVal := i.TimelineItemValue() + if tmlnVal.Typename != "IssueComment" { + i.err = errors.New("Call to queryCommentEdit() while timeline item is not a comment") return false } - - // loop over them timeline comment edits - if i.timeline.commentEdit.index < len(commentEdits.UserContentEdits.Nodes)-1 { - i.timeline.commentEdit.index++ - return i.nextValidCommentEdit() - } - - if !commentEdits.UserContentEdits.PageInfo.HasPreviousPage { - i.timeline.commentEdit.index = -1 + cei.variables["gqlNodeId"] = tmlnVal.IssueComment.Id + if err := i.gc.Query(ctx, &cei.query, cei.variables); err != nil { + i.err = err return false } - - i.initCommentEditQueryVariables() - if i.timeline.index == 0 { - i.commentEdit.variables["timelineAfter"] = i.timeline.lastEndCursor - } else { - i.commentEdit.variables["timelineAfter"] = i.timeline.query.Repository.Issues.Nodes[0].TimelineItems.Edges[i.timeline.index-1].Cursor - } - - i.commentEdit.variables["commentEditBefore"] = commentEdits.UserContentEdits.PageInfo.StartCursor - - return i.queryCommentEdit() -} - -// CommentEditValue return the actual comment edit value -func (i *iterator) CommentEditValue() userContentEdit { - if i.timeline.commentEdit.index == -2 { - return i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes[i.commentEdit.index] + ceItems := cei.query.Node.IssueComment.UserContentEdits.Nodes + if len(ceItems) <= 0 { + cei.index = -1 + return false } - - return i.timeline.query.Repository.Issues.Nodes[0].TimelineItems.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes[i.timeline.commentEdit.index] + // The UserContentEditConnection in the Github API serves its elements in reverse chronological + // order. For our purpose we have to reverse the edits. + reverseEdits(ceItems) + cei.index = 0 + return true } func reverseEdits(edits []userContentEdit) { |