Integrate new Github Bridge import

Alexander Scharinger created

Change summary

bridge/github/import.go       |   2 
bridge/github/import_query.go |  97 ---
bridge/github/iterator.go     | 876 +++++++++---------------------------
3 files changed, 221 insertions(+), 754 deletions(-)

Detailed changes

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 {

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 {

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()
 }