diff options
Diffstat (limited to 'bridge')
-rw-r--r-- | bridge/bridges.go | 12 | ||||
-rw-r--r-- | bridge/core/bridge.go | 6 | ||||
-rw-r--r-- | bridge/core/export.go | 11 | ||||
-rw-r--r-- | bridge/core/import.go | 14 | ||||
-rw-r--r-- | bridge/core/token.go | 182 | ||||
-rw-r--r-- | bridge/github/github.go | 4 | ||||
-rw-r--r-- | bridge/github/import_query.go | 4 | ||||
-rw-r--r-- | bridge/github/iterator.go | 101 | ||||
-rw-r--r-- | bridge/gitlab/export_test.go | 5 | ||||
-rw-r--r-- | bridge/gitlab/gitlab.go | 4 | ||||
-rw-r--r-- | bridge/launchpad/launchpad.go | 4 |
11 files changed, 289 insertions, 58 deletions
diff --git a/bridge/bridges.go b/bridge/bridges.go index dcb35af1..9bbf3941 100644 --- a/bridge/bridges.go +++ b/bridge/bridges.go @@ -3,13 +3,19 @@ package bridge import ( "github.com/MichaelMure/git-bug/bridge/core" - _ "github.com/MichaelMure/git-bug/bridge/github" - _ "github.com/MichaelMure/git-bug/bridge/gitlab" - _ "github.com/MichaelMure/git-bug/bridge/launchpad" + "github.com/MichaelMure/git-bug/bridge/github" + "github.com/MichaelMure/git-bug/bridge/gitlab" + "github.com/MichaelMure/git-bug/bridge/launchpad" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/repository" ) +func init() { + core.Register(&github.Github{}) + core.Register(&gitlab.Gitlab{}) + core.Register(&launchpad.Launchpad{}) +} + // Targets return all known bridge implementation target func Targets() []string { return core.Targets() diff --git a/bridge/core/bridge.go b/bridge/core/bridge.go index e90476eb..a3133b9c 100644 --- a/bridge/core/bridge.go +++ b/bridge/core/bridge.go @@ -71,6 +71,12 @@ func Targets() []string { return result } +// TargetExist return true if the given target has a bridge implementation +func TargetExist(target string) bool { + _, ok := bridgeImpl[target] + return ok +} + // Instantiate a new Bridge for a repo, from the given target and name func NewBridge(repo *cache.RepoCache, target string, name string) (*Bridge, error) { implType, ok := bridgeImpl[target] diff --git a/bridge/core/export.go b/bridge/core/export.go index 558b3d78..0f45404c 100644 --- a/bridge/core/export.go +++ b/bridge/core/export.go @@ -10,13 +10,24 @@ type ExportEvent int const ( _ ExportEvent = iota + + // Bug has been exported on the remote tracker ExportEventBug + // Comment has been exported on the remote tracker ExportEventComment + // Comment has been edited on the remote tracker ExportEventCommentEdition + // Bug's status has been changed on on the remote tracker ExportEventStatusChange + // Bug's title has been changed on the remote tracker ExportEventTitleEdition + // Bug's labels have been changed on the remote tracker ExportEventLabelChange + + // Nothing changed on the bug ExportEventNothing + + // Error happened during export ExportEventError ) diff --git a/bridge/core/import.go b/bridge/core/import.go index cff30f61..e4771d2c 100644 --- a/bridge/core/import.go +++ b/bridge/core/import.go @@ -10,14 +10,26 @@ type ImportEvent int const ( _ ImportEvent = iota + + // Bug has been created ImportEventBug + // Comment has been created ImportEventComment + // Comment has been edited ImportEventCommentEdition + // Bug's status has changed ImportEventStatusChange + // Bug's title has changed ImportEventTitleEdition + // Bug's labels changed ImportEventLabelChange - ImportEventIdentity + // Nothing happened on a Bug ImportEventNothing + + // Identity has been created + ImportEventIdentity + + // Error happened during import ImportEventError ) diff --git a/bridge/core/token.go b/bridge/core/token.go new file mode 100644 index 00000000..2ceabca2 --- /dev/null +++ b/bridge/core/token.go @@ -0,0 +1,182 @@ +package core + +import ( + "crypto/sha256" + "errors" + "fmt" + "regexp" + "sort" + "strings" + "time" + + "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/repository" +) + +const ( + tokenConfigKeyPrefix = "git-bug.token" + tokenValueKey = "value" + tokenTargetKey = "target" + tokenCreateTimeKey = "createtime" +) + +var ErrTokenNotExist = errors.New("token doesn't exist") + +func NewErrMultipleMatchToken(matching []entity.Id) *entity.ErrMultipleMatch { + return entity.NewErrMultipleMatch("token", matching) +} + +// Token holds an API access token data +type Token struct { + Value string + Target string + CreateTime time.Time +} + +// NewToken instantiate a new token +func NewToken(value, target string) *Token { + return &Token{ + Value: value, + Target: target, + CreateTime: time.Now(), + } +} + +func (t *Token) ID() entity.Id { + sum := sha256.Sum256([]byte(t.Value)) + return entity.Id(fmt.Sprintf("%x", sum)) +} + +// Validate ensure token important fields are valid +func (t *Token) Validate() error { + if t.Value == "" { + return fmt.Errorf("missing value") + } + if t.Target == "" { + return fmt.Errorf("missing target") + } + if t.CreateTime.IsZero() || t.CreateTime.Equal(time.Time{}) { + return fmt.Errorf("missing creation time") + } + if !TargetExist(t.Target) { + return fmt.Errorf("unknown target") + } + return nil +} + +// LoadToken loads a token from the repo config +func LoadToken(repo repository.RepoCommon, id entity.Id) (*Token, error) { + keyPrefix := fmt.Sprintf("git-bug.token.%s.", id) + + // read token config pairs + rawconfigs, err := repo.GlobalConfig().ReadAll(keyPrefix) + if err != nil { + // Not exactly right due to the limitation of ReadAll() + return nil, ErrTokenNotExist + } + + // trim key prefix + configs := make(map[string]string) + for key, value := range rawconfigs { + newKey := strings.TrimPrefix(key, keyPrefix) + configs[newKey] = value + } + + token := &Token{} + + token.Value = configs[tokenValueKey] + token.Target = configs[tokenTargetKey] + if createTime, ok := configs[tokenCreateTimeKey]; ok { + if t, err := repository.ParseTimestamp(createTime); err == nil { + token.CreateTime = t + } + } + + return token, nil +} + +// LoadTokenPrefix load a token from the repo config with a prefix +func LoadTokenPrefix(repo repository.RepoCommon, prefix string) (*Token, error) { + tokens, err := ListTokens(repo) + if err != nil { + return nil, err + } + + // preallocate but empty + matching := make([]entity.Id, 0, 5) + + for _, id := range tokens { + if id.HasPrefix(prefix) { + matching = append(matching, id) + } + } + + if len(matching) > 1 { + return nil, NewErrMultipleMatchToken(matching) + } + + if len(matching) == 0 { + return nil, ErrTokenNotExist + } + + return LoadToken(repo, matching[0]) +} + +// ListTokens return a map representing the stored tokens in the repo config and global config +// along with their type (global: true, local:false) +func ListTokens(repo repository.RepoCommon) ([]entity.Id, error) { + configs, err := repo.GlobalConfig().ReadAll(tokenConfigKeyPrefix + ".") + if err != nil { + return nil, err + } + + re, err := regexp.Compile(tokenConfigKeyPrefix + `.([^.]+)`) + if err != nil { + panic(err) + } + + set := make(map[string]interface{}) + + for key := range configs { + res := re.FindStringSubmatch(key) + + if res == nil { + continue + } + + set[res[1]] = nil + } + + result := make([]entity.Id, 0, len(set)) + for key := range set { + result = append(result, entity.Id(key)) + } + + sort.Sort(entity.Alphabetical(result)) + + return result, nil +} + +// StoreToken stores a token in the repo config +func StoreToken(repo repository.RepoCommon, token *Token) error { + storeValueKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenValueKey) + err := repo.GlobalConfig().StoreString(storeValueKey, token.Value) + if err != nil { + return err + } + + storeTargetKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenTargetKey) + err = repo.GlobalConfig().StoreString(storeTargetKey, token.Target) + if err != nil { + return err + } + + createTimeKey := fmt.Sprintf("git-bug.token.%s.%s", token.ID().String(), tokenCreateTimeKey) + return repo.GlobalConfig().StoreTimestamp(createTimeKey, token.CreateTime) +} + +// RemoveToken removes a token from the repo config +func RemoveToken(repo repository.RepoCommon, id entity.Id) error { + keyPrefix := fmt.Sprintf("git-bug.token.%s", id) + return repo.GlobalConfig().RemoveAll(keyPrefix) +} diff --git a/bridge/github/github.go b/bridge/github/github.go index 176bdd84..e4fb03dd 100644 --- a/bridge/github/github.go +++ b/bridge/github/github.go @@ -10,10 +10,6 @@ import ( "github.com/MichaelMure/git-bug/bridge/core" ) -func init() { - core.Register(&Github{}) -} - type Github struct{} func (*Github) Target() string { diff --git a/bridge/github/import_query.go b/bridge/github/import_query.go index 62d3227b..f5cad299 100644 --- a/bridge/github/import_query.go +++ b/bridge/github/import_query.go @@ -102,13 +102,13 @@ type issueTimeline struct { Body githubv4.String Url githubv4.URI - Timeline struct { + TimelineItems struct { Edges []struct { Cursor githubv4.String Node timelineItem } PageInfo pageInfo - } `graphql:"timeline(first: $timelineFirst, after: $timelineAfter)"` + } `graphql:"timelineItems(first: $timelineFirst, after: $timelineAfter)"` UserContentEdits struct { Nodes []userContentEdit diff --git a/bridge/github/iterator.go b/bridge/github/iterator.go index a97ed036..d1d7900f 100644 --- a/bridge/github/iterator.go +++ b/bridge/github/iterator.go @@ -138,9 +138,9 @@ func (i *iterator) initCommentEditQueryVariables() { func (i *iterator) reverseTimelineEditNodes() { node := i.timeline.query.Repository.Issues.Nodes[0] reverseEdits(node.UserContentEdits.Nodes) - for index, ce := range node.Timeline.Edges { - if ce.Node.Typename == "IssueComment" && len(node.Timeline.Edges) != 0 { - reverseEdits(node.Timeline.Edges[index].Node.IssueComment.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) } } } @@ -159,7 +159,8 @@ func (i *iterator) queryIssue() bool { return false } - if len(i.timeline.query.Repository.Issues.Nodes) == 0 { + issues := i.timeline.query.Repository.Issues.Nodes + if len(issues) == 0 { return false } @@ -178,29 +179,35 @@ func (i *iterator) NextIssue() bool { if i.timeline.variables["issueAfter"] == (*githubv4.String)(nil) { nextIssue := i.queryIssue() // prevent from infinite loop by setting a non nil cursor - i.timeline.variables["issueAfter"] = i.timeline.query.Repository.Issues.PageInfo.EndCursor + issues := i.timeline.query.Repository.Issues + i.timeline.variables["issueAfter"] = issues.PageInfo.EndCursor return nextIssue } - if !i.timeline.query.Repository.Issues.PageInfo.HasNextPage { + 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.variables["issueAfter"] = i.timeline.query.Repository.Issues.PageInfo.EndCursor i.timeline.index = -1 + timelineEndCursor := issues.Nodes[0].TimelineItems.PageInfo.EndCursor // store cursor for future use - i.timeline.lastEndCursor = i.timeline.query.Repository.Issues.Nodes[0].Timeline.PageInfo.EndCursor + i.timeline.lastEndCursor = timelineEndCursor // query issue block - return i.queryIssue() + nextIssue := i.queryIssue() + i.timeline.variables["issueAfter"] = issues.PageInfo.EndCursor + + return nextIssue } // IssueValue return the actual issue value func (i *iterator) IssueValue() issueTimeline { - return i.timeline.query.Repository.Issues.Nodes[0] + 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. @@ -214,23 +221,25 @@ func (i *iterator) NextTimelineItem() bool { return false } - if len(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges) == 0 { + 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 + if len(timelineItems.Edges) == 0 { return false } - if i.timeline.index < len(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges)-1 { + if i.timeline.index < len(timelineItems.Edges)-1 { i.timeline.index++ return true } - if !i.timeline.query.Repository.Issues.Nodes[0].Timeline.PageInfo.HasNextPage { + if !timelineItems.PageInfo.HasNextPage { return false } - i.timeline.lastEndCursor = i.timeline.query.Repository.Issues.Nodes[0].Timeline.PageInfo.EndCursor + i.timeline.lastEndCursor = timelineItems.PageInfo.EndCursor // more timelines, query them - i.timeline.variables["timelineAfter"] = i.timeline.query.Repository.Issues.Nodes[0].Timeline.PageInfo.EndCursor + i.timeline.variables["timelineAfter"] = timelineItems.PageInfo.EndCursor ctx, cancel := context.WithTimeout(i.ctx, defaultTimeout) defer cancel() @@ -240,6 +249,12 @@ func (i *iterator) NextTimelineItem() bool { return false } + 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 @@ -247,7 +262,8 @@ func (i *iterator) NextTimelineItem() bool { // TimelineItemValue return the actual timeline item value func (i *iterator) TimelineItemValue() timelineItem { - return i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node + timelineItems := i.timeline.query.Repository.Issues.Nodes[0].TimelineItems + return timelineItems.Edges[i.timeline.index].Node } func (i *iterator) queryIssueEdit() bool { @@ -260,11 +276,12 @@ func (i *iterator) queryIssueEdit() bool { return false } + issueEdits := i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits // reverse issue edits because github - reverseEdits(i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes) + reverseEdits(issueEdits.Nodes) // this is not supposed to happen - if len(i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes) == 0 { + if len(issueEdits.Nodes) == 0 { i.timeline.issueEdit.index = -1 return false } @@ -297,22 +314,24 @@ func (i *iterator) NextIssueEdit() bool { // 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 { - if i.issueEdit.index < len(i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes)-1 { + issueEdits := i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits + if i.issueEdit.index < len(issueEdits.Nodes)-1 { i.issueEdit.index++ return i.nextValidIssueEdit() } - if !i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.PageInfo.HasPreviousPage { + 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"] = i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.PageInfo.StartCursor + 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. // @@ -323,24 +342,24 @@ func (i *iterator) NextIssueEdit() bool { // 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(i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes) == 0 { + if len(issueEdits.Nodes) == 0 { return false } // loop over them timeline comment edits - if i.timeline.issueEdit.index < len(i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes)-1 { + if i.timeline.issueEdit.index < len(issueEdits.Nodes)-1 { i.timeline.issueEdit.index++ return i.nextValidIssueEdit() } - if !i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.PageInfo.HasPreviousPage { + if !issueEdits.PageInfo.HasPreviousPage { i.timeline.issueEdit.index = -1 return false } // if there is more edits, query them i.initIssueEditQueryVariables() - i.issueEdit.variables["issueEditBefore"] = i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.PageInfo.StartCursor + i.issueEdit.variables["issueEditBefore"] = issueEdits.PageInfo.StartCursor return i.queryIssueEdit() } @@ -348,11 +367,13 @@ func (i *iterator) NextIssueEdit() bool { func (i *iterator) IssueEditValue() userContentEdit { // if we are using issue edit query if i.timeline.issueEdit.index == -2 { - return i.issueEdit.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes[i.issueEdit.index] + 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 i.timeline.query.Repository.Issues.Nodes[0].UserContentEdits.Nodes[i.timeline.issueEdit.index] + return issueEdits.Nodes[i.timeline.issueEdit.index] } func (i *iterator) queryCommentEdit() bool { @@ -364,13 +385,14 @@ func (i *iterator) queryCommentEdit() bool { return false } + commentEdits := i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits // this is not supposed to happen - if len(i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes) == 0 { + if len(commentEdits.Nodes) == 0 { i.timeline.commentEdit.index = -1 return false } - reverseEdits(i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes) + reverseEdits(commentEdits.Nodes) i.commentEdit.index = 0 i.timeline.commentEdit.index = -2 @@ -398,35 +420,36 @@ func (i *iterator) NextCommentEdit() bool { // same as NextIssueEdit if i.timeline.commentEdit.index == -2 { - - if i.commentEdit.index < len(i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.Nodes)-1 { + 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 !i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.PageInfo.HasPreviousPage { + 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"] = i.commentEdit.query.Repository.Issues.Nodes[0].Timeline.Nodes[0].IssueComment.UserContentEdits.PageInfo.StartCursor + 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(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes) == 0 { + if len(commentEdits.UserContentEdits.Nodes) == 0 { return false } // loop over them timeline comment edits - if i.timeline.commentEdit.index < len(i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes)-1 { + if i.timeline.commentEdit.index < len(commentEdits.UserContentEdits.Nodes)-1 { i.timeline.commentEdit.index++ return i.nextValidCommentEdit() } - if !i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.PageInfo.HasPreviousPage { + if !commentEdits.UserContentEdits.PageInfo.HasPreviousPage { i.timeline.commentEdit.index = -1 return false } @@ -435,10 +458,10 @@ func (i *iterator) NextCommentEdit() bool { if i.timeline.index == 0 { i.commentEdit.variables["timelineAfter"] = i.timeline.lastEndCursor } else { - i.commentEdit.variables["timelineAfter"] = i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index-1].Cursor + i.commentEdit.variables["timelineAfter"] = i.timeline.query.Repository.Issues.Nodes[0].TimelineItems.Edges[i.timeline.index-1].Cursor } - i.commentEdit.variables["commentEditBefore"] = i.timeline.query.Repository.Issues.Nodes[0].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.PageInfo.StartCursor + i.commentEdit.variables["commentEditBefore"] = commentEdits.UserContentEdits.PageInfo.StartCursor return i.queryCommentEdit() } @@ -449,7 +472,7 @@ func (i *iterator) CommentEditValue() userContentEdit { 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].Timeline.Edges[i.timeline.index].Node.IssueComment.UserContentEdits.Nodes[i.timeline.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) { diff --git a/bridge/gitlab/export_test.go b/bridge/gitlab/export_test.go index 46c8c494..26b47bfb 100644 --- a/bridge/gitlab/export_test.go +++ b/bridge/gitlab/export_test.go @@ -284,8 +284,11 @@ func createRepository(ctx context.Context, name, token string) (int, error) { }, gitlab.WithContext(ctx), ) + if err != nil { + return 0, err + } - return project.ID, err + return project.ID, nil } // delete repository need a token with scope 'delete_repo' diff --git a/bridge/gitlab/gitlab.go b/bridge/gitlab/gitlab.go index 7e5c37cc..d976d813 100644 --- a/bridge/gitlab/gitlab.go +++ b/bridge/gitlab/gitlab.go @@ -23,10 +23,6 @@ const ( defaultTimeout = 60 * time.Second ) -func init() { - core.Register(&Gitlab{}) -} - type Gitlab struct{} func (*Gitlab) Target() string { diff --git a/bridge/launchpad/launchpad.go b/bridge/launchpad/launchpad.go index 1fd9edc2..030d9169 100644 --- a/bridge/launchpad/launchpad.go +++ b/bridge/launchpad/launchpad.go @@ -5,10 +5,6 @@ import ( "github.com/MichaelMure/git-bug/bridge/core" ) -func init() { - core.Register(&Launchpad{}) -} - type Launchpad struct{} func (*Launchpad) Target() string { |