aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bridge/bridges.go13
-rw-r--r--bridge/core/bridge.go155
-rw-r--r--bridge/core/interfaces.go29
-rw-r--r--bridge/github/config.go (renamed from bridge/github/auth.go)92
-rw-r--r--bridge/github/github.go28
-rw-r--r--bridge/main.go16
6 files changed, 273 insertions, 60 deletions
diff --git a/bridge/bridges.go b/bridge/bridges.go
new file mode 100644
index 00000000..c542903c
--- /dev/null
+++ b/bridge/bridges.go
@@ -0,0 +1,13 @@
+package bridge
+
+import (
+ "github.com/MichaelMure/git-bug/bridge/core"
+ "github.com/MichaelMure/git-bug/bridge/github"
+)
+
+// Bridges return all known bridges
+func Bridges() []*core.Bridge {
+ return []*core.Bridge{
+ core.NewBridge(&github.Github{}),
+ }
+}
diff --git a/bridge/core/bridge.go b/bridge/core/bridge.go
index 3ff8404b..3e3b935e 100644
--- a/bridge/core/bridge.go
+++ b/bridge/core/bridge.go
@@ -1,26 +1,149 @@
package core
-import "github.com/MichaelMure/git-bug/cache"
+import (
+ "fmt"
+ "os/exec"
+ "strings"
-type Common interface {
- // Configure handle the user interaction and return a key/value configuration
- // for future use
- Configure() (map[string]string, error)
+ "github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/repository"
+ "github.com/pkg/errors"
+)
+
+var ErrImportNorSupported = errors.New("import is not supported")
+var ErrExportNorSupported = errors.New("export is not supported")
+
+// Bridge is a wrapper around a BridgeImpl that will bind low-level
+// implementation with utility code to provide high-level functions.
+type Bridge struct {
+ impl BridgeImpl
+ conf Configuration
}
-type Importer interface {
- Common
- ImportAll(repo *cache.RepoCache) error
- Import(repo *cache.RepoCache, id string) error
+func NewBridge(impl BridgeImpl) *Bridge {
+ return &Bridge{
+ impl: impl,
+ }
}
-type Exporter interface {
- Common
- ExportAll(repo *cache.RepoCache) error
- Export(repo *cache.RepoCache, id string) error
+func (b *Bridge) Configure(repo repository.RepoCommon) error {
+ conf, err := b.impl.Configure(repo)
+ if err != nil {
+ return err
+ }
+
+ return b.storeConfig(repo, conf)
}
-type NotSupportedImporter struct{}
-type NotSupportedExporter struct{}
+func (b *Bridge) storeConfig(repo repository.RepoCommon, conf Configuration) error {
+ for key, val := range conf {
+ storeKey := fmt.Sprintf("git-bug.%s.%s", b.impl.Name(), key)
+
+ cmd := exec.Command("git", "config", "--replace-all", storeKey, val)
+ cmd.Dir = repo.GetPath()
+
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("error while storing bridge configuration: %s", out)
+ }
+ }
-// persist
+ return nil
+}
+
+func (b Bridge) getConfig(repo repository.RepoCommon) (Configuration, error) {
+ var err error
+ if b.conf == nil {
+ b.conf, err = b.loadConfig(repo)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return b.conf, nil
+}
+
+func (b Bridge) loadConfig(repo repository.RepoCommon) (Configuration, error) {
+ key := fmt.Sprintf("git-bug.%s", b.impl.Name())
+ cmd := exec.Command("git", "config", "--get-regexp", key)
+ cmd.Dir = repo.GetPath()
+
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return nil, fmt.Errorf("error while reading bridge configuration: %s", out)
+ }
+
+ lines := strings.Split(string(out), "\n")
+
+ result := make(Configuration, len(lines))
+ for _, line := range lines {
+ if strings.TrimSpace(line) == "" {
+ continue
+ }
+
+ parts := strings.Fields(line)
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("bad bridge configuration: %s", line)
+ }
+
+ result[parts[0]] = parts[1]
+ }
+
+ return result, nil
+}
+
+func (b Bridge) ImportAll(repo *cache.RepoCache) error {
+ importer := b.impl.Importer()
+ if importer == nil {
+ return ErrImportNorSupported
+ }
+
+ conf, err := b.getConfig(repo)
+ if err != nil {
+ return err
+ }
+
+ return b.impl.Importer().ImportAll(repo, conf)
+}
+
+func (b Bridge) Import(repo *cache.RepoCache, id string) error {
+ importer := b.impl.Importer()
+ if importer == nil {
+ return ErrImportNorSupported
+ }
+
+ conf, err := b.getConfig(repo)
+ if err != nil {
+ return err
+ }
+
+ return b.impl.Importer().Import(repo, conf, id)
+}
+
+func (b Bridge) ExportAll(repo *cache.RepoCache) error {
+ exporter := b.impl.Exporter()
+ if exporter == nil {
+ return ErrExportNorSupported
+ }
+
+ conf, err := b.getConfig(repo)
+ if err != nil {
+ return err
+ }
+
+ return b.impl.Exporter().ExportAll(repo, conf)
+}
+
+func (b Bridge) Export(repo *cache.RepoCache, id string) error {
+ exporter := b.impl.Exporter()
+ if exporter == nil {
+ return ErrExportNorSupported
+ }
+
+ conf, err := b.getConfig(repo)
+ if err != nil {
+ return err
+ }
+
+ return b.impl.Exporter().Export(repo, conf, id)
+}
diff --git a/bridge/core/interfaces.go b/bridge/core/interfaces.go
new file mode 100644
index 00000000..5336e7a8
--- /dev/null
+++ b/bridge/core/interfaces.go
@@ -0,0 +1,29 @@
+package core
+
+import (
+ "github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/repository"
+)
+
+type Configuration map[string]string
+
+type Importer interface {
+ ImportAll(repo *cache.RepoCache, conf Configuration) error
+ Import(repo *cache.RepoCache, conf Configuration, id string) error
+}
+
+type Exporter interface {
+ ExportAll(repo *cache.RepoCache, conf Configuration) error
+ Export(repo *cache.RepoCache, conf Configuration, id string) error
+}
+
+type BridgeImpl interface {
+ Name() string
+
+ // Configure handle the user interaction and return a key/value configuration
+ // for future use
+ Configure(repo repository.RepoCommon) (Configuration, error)
+
+ Importer() Importer
+ Exporter() Exporter
+}
diff --git a/bridge/github/auth.go b/bridge/github/config.go
index b721df7f..3b12d3f9 100644
--- a/bridge/github/auth.go
+++ b/bridge/github/config.go
@@ -10,25 +10,36 @@ import (
"math/rand"
"net/http"
"os"
+ "regexp"
"strings"
"syscall"
"time"
+ "github.com/MichaelMure/git-bug/bridge/core"
+ "github.com/MichaelMure/git-bug/repository"
"golang.org/x/crypto/ssh/terminal"
)
-const githubV3Url = "https://api.github.com"
+const githubV3Url = "https://api.Github.com"
+const keyUser = "user"
+const keyProject = "project"
+const keyToken = "token"
+
+func (*Github) Configure(repo repository.RepoCommon) (core.Configuration, error) {
+ conf := make(core.Configuration)
-func Configure() (map[string]string, error) {
fmt.Println("git-bug will generate an access token in your Github profile.")
// fmt.Println("The token will have the \"repo\" permission, giving it read/write access to your repositories and issues. There is no narrower scope available, sorry :-|")
fmt.Println()
- tokenName, err := promptTokenName()
+ projectUser, projectName, err := promptURL()
if err != nil {
return nil, err
}
+ conf[keyUser] = projectUser
+ conf[keyProject] = projectName
+
fmt.Println()
username, err := promptUsername()
@@ -47,12 +58,7 @@ func Configure() (map[string]string, error) {
// Attempt to authenticate and create a token
- var note string
- if tokenName == "" {
- note = "git-bug"
- } else {
- note = fmt.Sprintf("git-bug - %s", tokenName)
- }
+ note := fmt.Sprintf("git-bug - %s/%s", projectUser, projectName)
resp, err := requestToken(note, username, password)
if err != nil {
@@ -61,10 +67,6 @@ func Configure() (map[string]string, error) {
defer resp.Body.Close()
- if resp.StatusCode == http.StatusCreated {
- return decodeBody(resp.Body)
- }
-
// Handle 2FA is needed
OTPHeader := resp.Header.Get("X-GitHub-OTP")
if resp.StatusCode == http.StatusUnauthorized && OTPHeader != "" {
@@ -79,10 +81,15 @@ func Configure() (map[string]string, error) {
}
defer resp.Body.Close()
+ }
- if resp.StatusCode == http.StatusCreated {
- return decodeBody(resp.Body)
+ if resp.StatusCode == http.StatusCreated {
+ token, err := decodeBody(resp.Body)
+ if err != nil {
+ return nil, err
}
+ conf[keyToken] = token
+ return conf, nil
}
b, _ := ioutil.ReadAll(resp.Body)
@@ -129,7 +136,7 @@ func requestTokenWith2FA(note, username, password, otpCode string) (*http.Respon
return client.Do(req)
}
-func decodeBody(body io.ReadCloser) (map[string]string, error) {
+func decodeBody(body io.ReadCloser) (string, error) {
data, _ := ioutil.ReadAll(body)
aux := struct {
@@ -138,12 +145,14 @@ func decodeBody(body io.ReadCloser) (map[string]string, error) {
err := json.Unmarshal(data, &aux)
if err != nil {
- return nil, err
+ return "", err
+ }
+
+ if aux.Token == "" {
+ return "", fmt.Errorf("no token found in response: %s", string(data))
}
- return map[string]string{
- "token": aux.Token,
- }, nil
+ return aux.Token, nil
}
func randomFingerprint() string {
@@ -180,17 +189,46 @@ func promptUsername() (string, error) {
}
}
-func promptTokenName() (string, error) {
- fmt.Println("To help distinguish the token, you can optionally provide a description")
- fmt.Println("The token will be named \"git-bug - <description>\"")
- fmt.Println("description:")
+func promptURL() (string, string, error) {
+ for {
+ fmt.Println("Github project URL:")
+
+ line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+ if err != nil {
+ return "", "", err
+ }
+
+ line = strings.TrimRight(line, "\n")
- line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+ if line == "" {
+ fmt.Println("URL is empty")
+ continue
+ }
+
+ projectUser, projectName, err := splitURL(line)
+
+ if err != nil {
+ fmt.Println(err)
+ continue
+ }
+
+ return projectUser, projectName, nil
+ }
+}
+
+func splitURL(url string) (string, string, error) {
+ re, err := regexp.Compile(`github\.com\/([^\/]*)\/([^\/]*)`)
if err != nil {
- return "", err
+ return "", "", err
+ }
+
+ res := re.FindStringSubmatch(url)
+
+ if res == nil {
+ return "", "", fmt.Errorf("bad github project url")
}
- return strings.TrimRight(line, "\n"), nil
+ return res[1], res[2], nil
}
func validateUsername(username string) (bool, error) {
diff --git a/bridge/github/github.go b/bridge/github/github.go
index 0238b4bf..45954e23 100644
--- a/bridge/github/github.go
+++ b/bridge/github/github.go
@@ -1,4 +1,30 @@
package github
-type github struct {
+import (
+ "github.com/MichaelMure/git-bug/bridge/core"
+ "github.com/MichaelMure/git-bug/cache"
+)
+
+type Github struct{}
+
+func (*Github) Name() string {
+ return "github"
+}
+
+func (*Github) Importer() core.Importer {
+ return &githubImporter{}
+}
+
+func (*Github) Exporter() core.Exporter {
+ return nil
+}
+
+type githubImporter struct{}
+
+func (*githubImporter) ImportAll(repo *cache.RepoCache, conf core.Configuration) error {
+ panic("implement me")
+}
+
+func (*githubImporter) Import(repo *cache.RepoCache, conf core.Configuration, id string) error {
+ panic("implement me")
}
diff --git a/bridge/main.go b/bridge/main.go
deleted file mode 100644
index e2472691..00000000
--- a/bridge/main.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package main
-
-import (
- "fmt"
-
- "github.com/MichaelMure/git-bug/bridge/github"
-)
-
-func main() {
- conf, err := github.Configure()
- if err != nil {
- fmt.Println(err)
- }
-
- fmt.Println(conf)
-}