aboutsummaryrefslogtreecommitdiffstats
path: root/bridge/github/export.go
diff options
context:
space:
mode:
Diffstat (limited to 'bridge/github/export.go')
-rw-r--r--bridge/github/export.go328
1 files changed, 240 insertions, 88 deletions
diff --git a/bridge/github/export.go b/bridge/github/export.go
index 37207df7..a703e1e2 100644
--- a/bridge/github/export.go
+++ b/bridge/github/export.go
@@ -15,34 +15,54 @@ import (
"github.com/MichaelMure/git-bug/bridge/core"
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/util/git"
)
// githubImporter implement the Importer interface
type githubExporter struct {
- gc *githubv4.Client
- conf core.Configuration
- cachedLabels map[string]githubv4.ID
+ gc *githubv4.Client
+ conf core.Configuration
+
+ // github repository ID
+ repositoryID string
+
+ // cache identifiers used to speed up exporting operations
+ // cleared for each bug
+ cachedIDs map[string]string
+
+ // cache labels used to speed up exporting labels events
+ cachedLabels map[string]string
}
// Init .
func (ge *githubExporter) Init(conf core.Configuration) error {
- ge.gc = buildClient(conf["token"])
ge.conf = conf
- ge.cachedLabels = make(map[string]githubv4.ID)
+ ge.gc = buildClient(conf["token"])
+ ge.cachedIDs = make(map[string]string)
+ ge.cachedLabels = make(map[string]string)
return nil
}
// ExportAll export all event made by the current user to Github
func (ge *githubExporter) ExportAll(repo *cache.RepoCache, since time.Time) error {
- identity, err := repo.GetUserIdentity()
+ user, err := repo.GetUserIdentity()
if err != nil {
return err
}
- allBugsIds := repo.AllBugsIds()
+ // get repository node id
+ ge.repositoryID, err = getRepositoryNodeID(
+ ge.conf[keyOwner],
+ ge.conf[keyProject],
+ ge.conf[keyToken],
+ )
- // collect bugs
- bugs := make([]*cache.BugCache, 0)
+ if err != nil {
+ return err
+ }
+
+ allBugsIds := repo.AllBugsIds()
for _, id := range allBugsIds {
b, err := repo.ResolveBug(id)
if err != nil {
@@ -51,111 +71,229 @@ func (ge *githubExporter) ExportAll(repo *cache.RepoCache, since time.Time) erro
snapshot := b.Snapshot()
- // ignore issues edited before since date
- if snapshot.LastEditTime().Before(since) {
+ // ignore issues created before since date
+ if snapshot.CreatedAt.Before(since) {
continue
}
// if identity participated in a bug
for _, p := range snapshot.Participants {
- if p.Id() == identity.Id() {
- bugs = append(bugs, b)
+ if p.Id() == user.Id() {
+ // try to export the bug and it associated events
+ if err := ge.exportBug(b, user.Identity, since); err != nil {
+ return err
+ }
}
}
}
- // get repository node id
- repositoryID, err := getRepositoryNodeID(
- ge.conf[keyOwner],
- ge.conf[keyProject],
- ge.conf[keyToken],
- )
+ return nil
+}
+
+// exportBug publish bugs and related events
+func (ge *githubExporter) exportBug(b *cache.BugCache, user identity.Interface, since time.Time) error {
+ snapshot := b.Snapshot()
+
+ var bugGithubID string
+ var bugGithubURL string
+ var bugCreationHash string
+
+ // Special case:
+ // if a user try to export a bug that is not already exported to Github (or imported
+ // from Github) and he is not the author of the bug. There is nothing we can do.
+
+ // first operation is always createOp
+ createOp := snapshot.Operations[0].(*bug.CreateOperation)
+ bugAuthorID := createOp.OpBase.Author.Id()
+
+ // get github bug ID
+ githubID, ok := createOp.GetMetadata(keyGithubId)
+ if ok {
+ githubURL, ok := createOp.GetMetadata(keyGithubId)
+ if !ok {
+ // if we find github ID, github URL must be found too
+ panic("expected to find github issue URL")
+ }
+
+ // will be used to mark operation related to a bug as exported
+ bugGithubID = githubID
+ bugGithubURL = githubURL
+
+ } else if !ok && bugAuthorID == user.Id() {
+ // create bug
+ id, url, err := ge.createGithubIssue(ge.repositoryID, createOp.Title, createOp.Message)
+ if err != nil {
+ return fmt.Errorf("creating exporting github issue %v", err)
+ }
+
+ hash, err := createOp.Hash()
+ if err != nil {
+ return fmt.Errorf("comment hash: %v", err)
+ }
+
+ // mark bug creation operation as exported
+ if err := markOperationAsExported(b, hash, id, url); err != nil {
+ return fmt.Errorf("marking operation as exported: %v", err)
+ }
+
+ // cache bug github ID and URL
+ bugGithubID = id
+ bugGithubURL = url
+ } else {
+ // if bug is still not exported and user cannot author bug stop the execution
+
+ //TODO: maybe print a warning ?
+ // this is not an error
+ return nil
+ }
+
+ // get createOp hash
+ hash, err := createOp.Hash()
if err != nil {
return err
}
- for _, b := range bugs {
- snapshot := b.Snapshot()
- bugGithubID := ""
+ bugCreationHash = hash.String()
- for _, op := range snapshot.Operations {
- // treat only operations after since date
- if op.Time().Before(since) {
- continue
- }
+ // cache operation github id
+ ge.cachedIDs[bugCreationHash] = bugGithubID
- // ignore SetMetadata operations
- if _, ok := op.(*bug.SetMetadataOperation); ok {
- continue
- }
+ for _, op := range snapshot.Operations[1:] {
+ // ignore SetMetadata operations
+ if _, ok := op.(*bug.SetMetadataOperation); ok {
+ continue
+ }
- // ignore imported issues and operations from github
- if _, ok := op.GetMetadata(keyGithubId); ok {
- continue
+ // get operation hash
+ hash, err := op.Hash()
+ if err != nil {
+ return fmt.Errorf("reading operation hash: %v", err)
+ }
+
+ // ignore imported (or exported) operations from github
+ // cache the ID of already exported or imported issues and events from Github
+ if id, ok := op.GetMetadata(keyGithubId); ok {
+ ge.cachedIDs[hash.String()] = id
+ continue
+ }
+
+ switch op.(type) {
+ case *bug.AddCommentOperation:
+ opr := op.(*bug.AddCommentOperation)
+
+ // send operation to github
+ id, url, err := ge.addCommentGithubIssue(bugGithubID, opr.Message)
+ if err != nil {
+ return fmt.Errorf("adding comment: %v", err)
}
- // get operation hash
- hash, err := op.Hash()
+ hash, err := opr.Hash()
if err != nil {
- return fmt.Errorf("reading operation hash: %v", err)
+ return fmt.Errorf("comment hash: %v", err)
}
- // ignore already exported issues and operations
- if _, err := b.ResolveOperationWithMetadata("github-exported-op", hash.String()); err != nil {
- continue
+ // mark operation as exported
+ if err := markOperationAsExported(b, hash, id, url); err != nil {
+ return fmt.Errorf("marking operation as exported: %v", err)
}
- switch op.(type) {
- case *bug.CreateOperation:
- opr := op.(*bug.CreateOperation)
- //TODO export files
- bugGithubID, err = ge.createGithubIssue(repositoryID, opr.Title, opr.Message)
- if err != nil {
- return fmt.Errorf("exporting bug %v: %v", b.HumanId(), err)
- }
+ case *bug.EditCommentOperation:
- case *bug.AddCommentOperation:
- opr := op.(*bug.AddCommentOperation)
- bugGithubID, err = ge.addCommentGithubIssue(bugGithubID, opr.Message)
- if err != nil {
- return fmt.Errorf("adding comment %v: %v", "", err)
- }
+ id := bugGithubID
+ url := bugGithubURL
+ opr := op.(*bug.EditCommentOperation)
+ targetHash := opr.Target.String()
- case *bug.EditCommentOperation:
- opr := op.(*bug.EditCommentOperation)
- if err := ge.editCommentGithubIssue(bugGithubID, opr.Message); err != nil {
- return fmt.Errorf("editing comment %v: %v", "", err)
+ // Since github doesn't consider the issue body as a comment
+ if targetHash == bugCreationHash {
+ // case bug creation operation: we need to edit the Github issue
+ if err := ge.updateGithubIssueBody(bugGithubID, opr.Message); err != nil {
+ return fmt.Errorf("editing issue: %v", err)
}
- case *bug.SetStatusOperation:
- opr := op.(*bug.SetStatusOperation)
- if err := ge.updateGithubIssueStatus(bugGithubID, opr.Status); err != nil {
- return fmt.Errorf("updating status %v: %v", bugGithubID, err)
+ } else {
+ // case comment edition operation: we need to edit the Github comment
+ commentID, ok := ge.cachedIDs[targetHash]
+ if !ok {
+ panic("unexpected error: comment id not found")
}
- case *bug.SetTitleOperation:
- opr := op.(*bug.SetTitleOperation)
- if err := ge.updateGithubIssueTitle(bugGithubID, opr.Title); err != nil {
- return fmt.Errorf("editing comment %v: %v", bugGithubID, err)
+ eid, eurl, err := ge.editCommentGithubIssue(commentID, opr.Message)
+ if err != nil {
+ return fmt.Errorf("editing comment: %v", err)
}
- case *bug.LabelChangeOperation:
- opr := op.(*bug.LabelChangeOperation)
- if err := ge.updateGithubIssueLabels(bugGithubID, opr.Added, opr.Removed); err != nil {
- return fmt.Errorf("updating labels %v: %v", bugGithubID, err)
- }
+ // use comment id/url instead of issue id/url
+ id = eid
+ url = eurl
+ }
+
+ hash, err := opr.Hash()
+ if err != nil {
+ return fmt.Errorf("comment hash: %v", err)
+ }
- default:
- // ignore other type of operations
+ // mark operation as exported
+ if err := markOperationAsExported(b, hash, id, url); err != nil {
+ return fmt.Errorf("marking operation as exported: %v", err)
}
- }
+ case *bug.SetStatusOperation:
+ opr := op.(*bug.SetStatusOperation)
+ if err := ge.updateGithubIssueStatus(bugGithubID, opr.Status); err != nil {
+ return fmt.Errorf("updating status %v: %v", bugGithubID, err)
+ }
+
+ hash, err := opr.Hash()
+ if err != nil {
+ return fmt.Errorf("comment hash: %v", err)
+ }
+
+ // mark operation as exported
+ if err := markOperationAsExported(b, hash, bugGithubID, bugGithubURL); err != nil {
+ return fmt.Errorf("marking operation as exported: %v", err)
+ }
- if err := b.CommitAsNeeded(); err != nil {
- return fmt.Errorf("bug commit: %v", err)
+ case *bug.SetTitleOperation:
+ opr := op.(*bug.SetTitleOperation)
+ if err := ge.updateGithubIssueTitle(bugGithubID, opr.Title); err != nil {
+ return fmt.Errorf("editing comment %v: %v", bugGithubID, err)
+ }
+
+ hash, err := opr.Hash()
+ if err != nil {
+ return fmt.Errorf("comment hash: %v", err)
+ }
+
+ // mark operation as exported
+ if err := markOperationAsExported(b, hash, bugGithubID, bugGithubURL); err != nil {
+ return fmt.Errorf("marking operation as exported: %v", err)
+ }
+
+ case *bug.LabelChangeOperation:
+ opr := op.(*bug.LabelChangeOperation)
+ if err := ge.updateGithubIssueLabels(bugGithubID, opr.Added, opr.Removed); err != nil {
+ return fmt.Errorf("updating labels %v: %v", bugGithubID, err)
+ }
+
+ hash, err := opr.Hash()
+ if err != nil {
+ return fmt.Errorf("comment hash: %v", err)
+ }
+
+ // mark operation as exported
+ if err := markOperationAsExported(b, hash, bugGithubID, bugGithubURL); err != nil {
+ return fmt.Errorf("marking operation as exported: %v", err)
+ }
+
+ default:
+ panic("unhandled operation type case")
}
- fmt.Printf("debug: %v", bugGithubID)
+ }
+
+ if err := b.CommitAsNeeded(); err != nil {
+ return fmt.Errorf("bug commit: %v", err)
}
return nil
@@ -201,8 +339,16 @@ func getRepositoryNodeID(owner, project, token string) (string, error) {
return aux.NodeID, nil
}
-func (ge *githubExporter) markOperationAsExported(b *cache.BugCache, opHash string) error {
- return nil
+func markOperationAsExported(b *cache.BugCache, target git.Hash, githubID, githubURL string) error {
+ _, err := b.SetMetadata(
+ target,
+ map[string]string{
+ keyGithubId: githubID,
+ keyGithubUrl: githubURL,
+ },
+ )
+
+ return err
}
// get label from github
@@ -296,6 +442,8 @@ func randomHexColor() string {
return "fffff"
}
+ // fmt.Sprintf("#%.2x%.2x%.2x", rgba.R, rgba.G, rgba.B)
+
return hex.EncodeToString(bytes)
}
@@ -307,6 +455,7 @@ func (ge *githubExporter) getOrCreateGithubLabelID(repositoryID, label string) (
}
// random color
+ //TODO: no random
color := randomHexColor()
// create label and return id
@@ -345,7 +494,7 @@ func (ge *githubExporter) getLabelsIDs(repositoryID string, labels []bug.Label)
}
// create a github issue and return it ID
-func (ge *githubExporter) createGithubIssue(repositoryID, title, body string) (string, error) {
+func (ge *githubExporter) createGithubIssue(repositoryID, title, body string) (string, string, error) {
m := &createIssueMutation{}
input := &githubv4.CreateIssueInput{
RepositoryID: repositoryID,
@@ -354,14 +503,15 @@ func (ge *githubExporter) createGithubIssue(repositoryID, title, body string) (s
}
if err := ge.gc.Mutate(context.TODO(), m, input, nil); err != nil {
- return "", err
+ return "", "", err
}
- return m.CreateIssue.Issue.ID, nil
+ issue := m.CreateIssue.Issue
+ return issue.ID, issue.URL, nil
}
// add a comment to an issue and return it ID
-func (ge *githubExporter) addCommentGithubIssue(subjectID string, body string) (string, error) {
+func (ge *githubExporter) addCommentGithubIssue(subjectID string, body string) (string, string, error) {
m := &addCommentToIssueMutation{}
input := &githubv4.AddCommentInput{
SubjectID: subjectID,
@@ -369,13 +519,14 @@ func (ge *githubExporter) addCommentGithubIssue(subjectID string, body string) (
}
if err := ge.gc.Mutate(context.TODO(), m, input, nil); err != nil {
- return "", err
+ return "", "", err
}
- return m.AddComment.CommentEdge.Node.ID, nil
+ node := m.AddComment.CommentEdge.Node
+ return node.ID, node.URL, nil
}
-func (ge *githubExporter) editCommentGithubIssue(commentID, body string) error {
+func (ge *githubExporter) editCommentGithubIssue(commentID, body string) (string, string, error) {
m := &updateIssueCommentMutation{}
input := &githubv4.UpdateIssueCommentInput{
ID: commentID,
@@ -383,10 +534,11 @@ func (ge *githubExporter) editCommentGithubIssue(commentID, body string) error {
}
if err := ge.gc.Mutate(context.TODO(), m, input, nil); err != nil {
- return err
+ return "", "", err
}
- return nil
+ comment := m.IssueComment
+ return commentID, comment.URL, nil
}
func (ge *githubExporter) updateGithubIssueStatus(id string, status bug.Status) error {