aboutsummaryrefslogblamecommitdiffstats
path: root/bridge/github/iterator.go
blob: 76c5cbfaffc887c65fb7940c80294bdec82959b5 (plain) (tree)
1
2
3
4
5
6
7
8
9



                 
             

              

                               


                                      










                                  
                                     
                                    

 




                              








                                         
                                

 







                                  










                                                                                                                                    








                                                            















                                                         





























                                                                                



                                                                            



                                                            



                                                           



                                                       




                                                                












































                                                                                                 

                              








                                                                

























































                                                                                                                        
 












































                                                                                                







































































                                                                                                            

 




















                                        
 
                                                                              

                                                                        










                                                                     
                                                                     


                    


                                                      


                       









                                       
                                                   
                                                                                                                                
                       
                                 
                                

                                   




                                                          

                                                                  




                                                          

                                                                  




                                                          

                                                                  


                          


                                      



                                                 
                                                                                                



                                                                             

                                                                                




























                                                                                  

                                                           


                                                                                                              



                 
                                      



                                  
                                      
                                                                 


                                                                                        



                            

                                                          



                                    
                   

 

                                                                           
                                     



                            



                               



                                                                              

                                                                              
                                

         

                                                    


                            
                                             
                                                                       

                             
                                                                             
                                      
                                                    
 
                            



                                                                      

 
                                           
                                               

                                                    

 


                                                                                                 



                            



                               

                                                                                                 
                         
                                          


                            
                                                          



                                  
                                                


                            
                                                                   
 
                                     
                                                                                











                                                                                                       
 
                                                                 

                      

                                                                                           


                            






                                                                                                        
 


                                                                                                       


                            




                                    

                                                          

                                                                                  

 
                                          
                                                                 


                                                                                          




                                                 
                                                                                   
                                             
                                      

                                         
                                       





                                               








                                                                                                                        


                   

                                                                                           




                                         



                               


                                                                              

                                                                                           
                                           
                                                     

                 
                                                         





                                                       
                                                                                          
                                         

         
                                                                                  









                                                                                    
                                       



                                                
                                                                 
                                            
                                             

         
                                                 





                                               
                                                                                  
                                 

 
                                                    


                                                     

                                                                                           

         
                                                                                  
                                                     
                                                           

 
                                            
                                                                 


                                                                                              



                            
                                                                                                                      
                                         
                                         



                                                 
                                        


                                         







                                                                                                            


                   

                                                                                               




                                           



                               

                                               

                                                                                                                              
                                             
                                                       

                 
                                                           





                                                             
                                                                                                
                                           

         
                                                                                                                           
                                       
                                                          



                                                
                                                                                      
                                              
                                               

         
                                                                    



                                                 

                                         
                                                                                   
                
                                                                                                                                                     

         
                                                                                                         
 
                                   

 
                                                        




                                                                                                                                                
                                                                                                                                                                       

 




                                                             
package github

import (
	"context"
	"fmt"
	"time"

	
        "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 issueIter struct {
        iterVars
        query         issueQuery
        issueEditIter []issueEditIter
        timelineIter  []timelineIter
}

type issueEditIter struct {
        iterVars
        query issueEditQuery_A
}

type timelineIter struct {
        iterVars
        query           timelineQuery
        commentEditIter []commentEditIter
}


type commentEditIter struct {
        iterVars
        query commentEditQuery_A
}

type iterVars struct {
        index     int
        capacity  int
        variables varmap
}

type varmap map[string]interface{}


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),
                },
        }
	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)
	}
	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 = ""
}

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_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_A) currIssueItem() *issue {
        return &i.issueIter.query.Repository.Issues.Nodes[i.issueIter.index]
}

func (i *iterator_A) currIssueEditIter() *issueEditIter {
        return &i.issueIter.issueEditIter[i.issueIter.index]
}

func (i *iterator_A) currTimelineIter() *timelineIter {
        return &i.issueIter.timelineIter[i.issueIter.index]
}

func (i *iterator_A) currIssueGqlNodeId() githubv4.ID {
        return i.currIssueItem().Id
}

func (i *iterator_A) currCommentEditIter() *commentEditIter {
        timelineIter := i.currTimelineIter()
        return &timelineIter.commentEditIter[timelineIter.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{}
}

type commentEditIterator struct {
	index     int
	query     commentEditQuery
	variables map[string]interface{}
}

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
}

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
}

// 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),
			},
		},
	}

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

// Error return last encountered error
func (i *iterator) Error() error {
	return i.err
}

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 {
		i.err = err
		return false
	}

	issues := i.timeline.query.Repository.Issues.Nodes
	if len(issues) == 0 {
		return false
	}

	i.reverseTimelineEditNodes()
	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 {
		return false
	}

	if i.ctx.Err() != nil {
		return false
	}

	// 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
	}

	issues := i.timeline.query.Repository.Issues
	if !issues.PageInfo.HasNextPage {
		return false
	}

	// 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
}

// IssueValue return the actual issue value
func (i *iterator) IssueValue() issueTimeline {
	issues := i.timeline.query.Repository.Issues
	return issues.Nodes[0]
}

// 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
	}

	if i.ctx.Err() != nil {
		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 {
		return false
	}

	if i.timeline.index < len(timelineItems.Edges)-1 {
		i.timeline.index++
		return true
	}

	if !timelineItems.PageInfo.HasNextPage {
		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
	}
	// 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 {
		return false
	}

	i.reverseTimelineEditNodes()
	i.timeline.index = 0
	return true
}

// 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
}

func (i *iterator) queryIssueEdit() bool {
	ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout)
	defer cancel()

	if err := i.gc.Query(ctx, &i.issueEdit.query, i.issueEdit.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
		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()
	}
	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 {
		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
		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]
	}

	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
		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
		return false
	}

	reverseEdits(commentEdits.Nodes)

	i.commentEdit.index = 0
	i.timeline.commentEdit.index = -2
	return i.nextValidCommentEdit()
}

func (i *iterator) 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
}

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

		if !commentEdits.PageInfo.HasPreviousPage {
			i.timeline.commentEdit.index = -1
			i.commentEdit.index = -1
			return false
		}

		// if there is more comment edits, query them
		i.commentEdit.variables["commentEditBefore"] = commentEdits.PageInfo.StartCursor
		return i.queryCommentEdit()
	}

	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 {
		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
		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]
	}

	return i.timeline.query.Repository.Issues.Nodes[0].TimelineItems.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes[i.timeline.commentEdit.index]
}

func reverseEdits(edits []userContentEdit) {
	for i, j := 0, len(edits)-1; i < j; i, j = i+1, j-1 {
		edits[i], edits[j] = edits[j], edits[i]
	}
}