From f124df7e73f8613bf9cd2f83177eeb4110c7a639 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Fri, 24 May 2019 21:03:31 +0200 Subject: [PATCH 01/24] changes to the BridgeImpl Interface Add bridge params Change bridge.Configure signature --- bridge/core/bridge.go | 13 ++++++++++-- bridge/core/interfaces.go | 2 +- commands/bridge_configure.go | 39 +++++++++++++++++++++++++++--------- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/bridge/core/bridge.go b/bridge/core/bridge.go index aa02ceb5ea072f49d2924204a898e3be88ae8090..6bc14bc7f411345aabe6e37ae48dd20789d6b6e6 100644 --- a/bridge/core/bridge.go +++ b/bridge/core/bridge.go @@ -20,6 +20,15 @@ const bridgeConfigKeyPrefix = "git-bug.bridge" var bridgeImpl map[string]reflect.Type +// BridgeParams holds parameters to simplify the bridge configuration without +// having to make terminal prompts. +type BridgeParams struct { + Owner string + Project string + URL string + Token string +} + // Bridge is a wrapper around a BridgeImpl that will bind low-level // implementation with utility code to provide high-level functions. type Bridge struct { @@ -166,8 +175,8 @@ func RemoveBridge(repo repository.RepoCommon, fullName string) error { } // Configure run the target specific configuration process -func (b *Bridge) Configure() error { - conf, err := b.impl.Configure(b.repo) +func (b *Bridge) Configure(params BridgeParams) error { + conf, err := b.impl.Configure(b.repo, params) if err != nil { return err } diff --git a/bridge/core/interfaces.go b/bridge/core/interfaces.go index be5afa62a45b1f247660c50eeed959681375d747..37fdb3d7233d71b2348960c43401f2bd0240a3c8 100644 --- a/bridge/core/interfaces.go +++ b/bridge/core/interfaces.go @@ -15,7 +15,7 @@ type BridgeImpl interface { // Configure handle the user interaction and return a key/value configuration // for future use - Configure(repo repository.RepoCommon) (Configuration, error) + Configure(repo repository.RepoCommon, params BridgeParams) (Configuration, error) // ValidateConfig check the configuration for error ValidateConfig(conf Configuration) error diff --git a/commands/bridge_configure.go b/commands/bridge_configure.go index ce10d9af69a3bebd3dbb18c150332f3397775eff..b20b38a59ec5eaf7d4c4992ee3f0d571f15d05c2 100644 --- a/commands/bridge_configure.go +++ b/commands/bridge_configure.go @@ -7,12 +7,24 @@ import ( "strconv" "strings" + "github.com/MichaelMure/git-bug/bridge/core" + "github.com/MichaelMure/git-bug/bridge" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/util/interrupt" "github.com/spf13/cobra" ) +const ( + defaultName = "default" +) + +var ( + name string + target string + bridgeParams core.BridgeParams +) + func runBridgeConfigure(cmd *cobra.Command, args []string) error { backend, err := cache.NewRepoCache(repo) if err != nil { @@ -21,14 +33,18 @@ func runBridgeConfigure(cmd *cobra.Command, args []string) error { defer backend.Close() interrupt.RegisterCleaner(backend.Close) - target, err := promptTarget() - if err != nil { - return err + if target == "" { + target, err = promptTarget() + if err != nil { + return err + } } - name, err := promptName() - if err != nil { - return err + if name == "" { + name, err = promptName() + if err != nil { + return err + } } b, err := bridge.NewBridge(backend, target, name) @@ -36,11 +52,12 @@ func runBridgeConfigure(cmd *cobra.Command, args []string) error { return err } - err = b.Configure() + err = b.Configure(bridgeParams) if err != nil { return err } + fmt.Println("successfully configured bridge") return nil } @@ -71,8 +88,6 @@ func promptTarget() (string, error) { } func promptName() (string, error) { - defaultName := "default" - fmt.Printf("name [%s]: ", defaultName) line, err := bufio.NewReader(os.Stdin).ReadString('\n') @@ -98,4 +113,10 @@ var bridgeConfigureCmd = &cobra.Command{ func init() { bridgeCmd.AddCommand(bridgeConfigureCmd) + bridgeConfigureCmd.Flags().StringVarP(&name, "name", "n", "", "Bridge name") + bridgeConfigureCmd.Flags().StringVarP(&target, "target", "t", "", "Bridge target name. Valid values are [github,gitlab,gitea,launchpad]") + bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.URL, "url", "u", "", "Repository url") + bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Owner, "owner", "o", "", "Repository owner") + bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Token, "token", "T", "", "Authentication token") + bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Project, "project", "p", "", "Repository name") } From 6deb6ec469d4f10e668597a79d71afd11204a8b2 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Fri, 24 May 2019 21:04:25 +0200 Subject: [PATCH 02/24] Github bridge updates Use bridge params to configure the bridge --- bridge/github/config.go | 256 +++++++++++++++++++++++++++++----------- 1 file changed, 189 insertions(+), 67 deletions(-) diff --git a/bridge/github/config.go b/bridge/github/config.go index 2a3119a6ab0a36a7665b0c790eb3166747a19fb3..29e50b873c7b6cf481a9f4c48599f53354da60e7 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -11,91 +11,160 @@ import ( "net/http" "os" "regexp" + "strconv" "strings" "syscall" "time" + "golang.org/x/crypto/ssh/terminal" + "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" - keyUser = "user" + keyOwner = "owner" keyProject = "project" keyToken = "token" + + defaultTimeout = 5 * time.Second ) -func (*Github) Configure(repo repository.RepoCommon) (core.Configuration, error) { - conf := make(core.Configuration) +var ( + rxGithubSplit = regexp.MustCompile(`github\.com\/([^\/]*)\/([^\/]*)`) +) - fmt.Println() - fmt.Println("git-bug will now generate an access token in your Github profile. Your credential are not stored and are only used to generate the token. The token is stored in the repository git config.") - fmt.Println() - fmt.Println("The token will have the following scopes:") - fmt.Println(" - user:email: to be able to read public-only users email") - // 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() +func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) { + conf := make(core.Configuration) + var err error + var token string + var owner string + var project string + + // getting owner and project name: + // first use directly params if they are both provided, else try to parse + // them from params URL, and finaly try getting them from terminal prompt + if params.Owner != "" && params.Project != "" { + owner = params.Owner + project = params.Project + + } else if params.URL != "" { + owner, project, err = splitURL(params.URL) + if err != nil { + return nil, err + } - projectUser, projectName, err := promptURL() - if err != nil { - return nil, err + } else { + owner, project, err = promptURL() + if err != nil { + return nil, err + } } - conf[keyUser] = projectUser - conf[keyProject] = projectName - - username, err := promptUsername() + // validate project owner + ok, err := validateUsername(owner) if err != nil { return nil, err } - - password, err := promptPassword() - if err != nil { - return nil, err + if !ok { + return nil, fmt.Errorf("invalid parameter owner: %v", owner) } - // Attempt to authenticate and create a token + // try to get token from params if provided, else use terminal prompt + // to login and generate a token + if params.Token != "" { + token = params.Token - note := fmt.Sprintf("git-bug - %s/%s", projectUser, projectName) - - resp, err := requestToken(note, username, password) - if err != nil { - return nil, err - } + } else { + fmt.Println() + fmt.Println("git-bug will now generate an access token in your Github profile. Your credential are not stored and are only used to generate the token. The token is stored in the repository git config.") + fmt.Println() + fmt.Println("Depending on your configuration the token will have one of the following scopes:") + fmt.Println(" - 'user:email': to be able to read public-only users email") + fmt.Println(" - 'repo' : to be able to read private repositories") + // 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() - defer resp.Body.Close() + isPublic, err := promptProjectVisibility() + if err != nil { + return nil, err + } - // Handle 2FA is needed - OTPHeader := resp.Header.Get("X-GitHub-OTP") - if resp.StatusCode == http.StatusUnauthorized && OTPHeader != "" { - otpCode, err := prompt2FA() + username, err := promptUsername() if err != nil { return nil, err } - resp, err = requestTokenWith2FA(note, username, password, otpCode) + password, err := promptPassword() if err != nil { return nil, err } - defer resp.Body.Close() - } + var scope string + if isPublic { + // user:email is requested to be able to read public emails + // - a private email will stay private, even with this token + scope = "user:email" + } else { + // 'repo' is request to be able to read private repositories + // /!\ token will have read/write rights on every private repository you have access to + scope = "repo" + } + + // Attempt to authenticate and create a token + + note := fmt.Sprintf("git-bug - %s/%s", owner, project) - if resp.StatusCode == http.StatusCreated { - token, err := decodeBody(resp.Body) + resp, err := requestToken(note, username, password, scope) if err != nil { return nil, err } - conf[keyToken] = token - return conf, nil + + defer resp.Body.Close() + + // Handle 2FA is needed + OTPHeader := resp.Header.Get("X-GitHub-OTP") + if resp.StatusCode == http.StatusUnauthorized && OTPHeader != "" { + otpCode, err := prompt2FA() + if err != nil { + return nil, err + } + + resp, err = requestTokenWith2FA(note, username, password, otpCode, scope) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + } + + if resp.StatusCode == http.StatusCreated { + token, err = decodeBody(resp.Body) + if err != nil { + return nil, err + } + + } else { + b, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("error creating token %v: %v", resp.StatusCode, string(b)) + } } - b, _ := ioutil.ReadAll(resp.Body) - fmt.Printf("Error %v: %v\n", resp.StatusCode, string(b)) + // verifying access to project with token + ok, err = validateProject(owner, project, token) + if err != nil { + return nil, err + } + if !ok { + return nil, fmt.Errorf("project doesn't exist or authentication token has a wrong scope") + } + + conf[keyToken] = token + conf[keyOwner] = owner + conf[keyProject] = project - return nil, nil + return conf, nil } func (*Github) ValidateConfig(conf core.Configuration) error { @@ -103,8 +172,8 @@ func (*Github) ValidateConfig(conf core.Configuration) error { return fmt.Errorf("missing %s key", keyToken) } - if _, ok := conf[keyUser]; !ok { - return fmt.Errorf("missing %s key", keyUser) + if _, ok := conf[keyOwner]; !ok { + return fmt.Errorf("missing %s key", keyOwner) } if _, ok := conf[keyProject]; !ok { @@ -114,20 +183,18 @@ func (*Github) ValidateConfig(conf core.Configuration) error { return nil } -func requestToken(note, username, password string) (*http.Response, error) { - return requestTokenWith2FA(note, username, password, "") +func requestToken(note, username, password string, scope string) (*http.Response, error) { + return requestTokenWith2FA(note, username, password, "", scope) } -func requestTokenWith2FA(note, username, password, otpCode string) (*http.Response, error) { +func requestTokenWith2FA(note, username, password, otpCode string, scope string) (*http.Response, error) { url := fmt.Sprintf("%s/authorizations", githubV3Url) params := struct { Scopes []string `json:"scopes"` Note string `json:"note"` Fingerprint string `json:"fingerprint"` }{ - // user:email is requested to be able to read public emails - // - a private email will stay private, even with this token - Scopes: []string{"user:email"}, + Scopes: []string{scope}, Note: note, Fingerprint: randomFingerprint(), } @@ -149,7 +216,9 @@ func requestTokenWith2FA(note, username, password, otpCode string) (*http.Respon req.Header.Set("X-GitHub-OTP", otpCode) } - client := http.Client{} + client := &http.Client{ + Timeout: defaultTimeout, + } return client.Do(req) } @@ -217,31 +286,23 @@ func promptURL() (string, string, error) { } line = strings.TrimRight(line, "\n") - if line == "" { fmt.Println("URL is empty") continue } - projectUser, projectName, err := splitURL(line) - + projectOwner, projectName, err := splitURL(line) if err != nil { fmt.Println(err) continue } - return projectUser, projectName, nil + return projectOwner, projectName, nil } } func splitURL(url string) (string, string, error) { - re, err := regexp.Compile(`github\.com\/([^\/]*)\/([^\/]*)`) - if err != nil { - panic(err) - } - - res := re.FindStringSubmatch(url) - + res := rxGithubSplit.FindStringSubmatch(url) if res == nil { return "", "", fmt.Errorf("bad github project url") } @@ -265,6 +326,33 @@ func validateUsername(username string) (bool, error) { return resp.StatusCode == http.StatusOK, nil } +func validateProject(owner, project, token string) (bool, error) { + url := fmt.Sprintf("%s/repos/%s/%s", githubV3Url, owner, project) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return false, err + } + + req.Header.Set("Authorization", fmt.Sprintf("token %s", token)) + + client := &http.Client{ + Timeout: defaultTimeout, + } + + resp, err := client.Do(req) + if err != nil { + return false, err + } + + err = resp.Body.Close() + if err != nil { + return false, err + } + + return resp.StatusCode == http.StatusOK, nil +} + func promptPassword() (string, error) { for { fmt.Print("password: ") @@ -291,14 +379,48 @@ func prompt2FA() (string, error) { fmt.Print("two-factor authentication code: ") byte2fa, err := terminal.ReadPassword(int(syscall.Stdin)) + fmt.Println() if err != nil { return "", err } - if len(byte2fa) > 0 { - return string(byte2fa), nil + if len(byte2fa) != 6 { + fmt.Println("invalid 2FA code size") + continue + } + + str2fa := string(byte2fa) + _, err = strconv.Atoi(str2fa) + if err != nil { + fmt.Println("2fa code must be digits only") + continue + } + + return str2fa, nil + } +} + +func promptProjectVisibility() (bool, error) { + fmt.Println("[0]: public") + fmt.Println("[1]: private") + + for { + fmt.Print("repository visibility type: ") + + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + fmt.Println() + if err != nil { + return false, err + } + + line = strings.TrimRight(line, "\n") + + index, err := strconv.Atoi(line) + if err != nil || (index != 0 && index != 1) { + fmt.Println("invalid input") + continue } - fmt.Println("code is empty") + return index == 0, nil } } From 5f80f242fa2afc7c5844d685d94d70fba97b7a0c Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Fri, 24 May 2019 21:04:35 +0200 Subject: [PATCH 03/24] Launchpad changes Use bridge params to configure the bridge --- bridge/launchpad/config.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/bridge/launchpad/config.go b/bridge/launchpad/config.go index 11a465bea5d9e9d9cb12e8338983ffbe7c0439db..637ffd2c798ec125b30f4925de3d91e36229c401 100644 --- a/bridge/launchpad/config.go +++ b/bridge/launchpad/config.go @@ -12,15 +12,17 @@ import ( const keyProject = "project" -func (*Launchpad) Configure(repo repository.RepoCommon) (core.Configuration, error) { +func (*Launchpad) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) { conf := make(core.Configuration) - projectName, err := promptProjectName() - if err != nil { - return nil, err - } + if params.Project == "" { + projectName, err := promptProjectName() + if err != nil { + return nil, err + } - conf[keyProject] = projectName + conf[keyProject] = projectName + } return conf, nil } @@ -52,3 +54,7 @@ func promptProjectName() (string, error) { return line, nil } } + +func validateProject() (bool, error) { + return false, nil +} From 70268ff4ecda62bb9f8d7494d04a6941fa0dce51 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Fri, 24 May 2019 21:32:13 +0200 Subject: [PATCH 04/24] Change keyUser to keyOwner in Github bridge --- bridge/github/github.go | 3 ++- bridge/github/import.go | 5 +++-- bridge/github/import_test.go | 6 +++--- bridge/github/iterator.go | 14 +++++++------- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/bridge/github/github.go b/bridge/github/github.go index 5fee7487d97676c8b7b35a66f4bc72ae74012751..3e717ee977c8915c622c5fd543445ec60139278e 100644 --- a/bridge/github/github.go +++ b/bridge/github/github.go @@ -4,9 +4,10 @@ package github import ( "context" - "github.com/MichaelMure/git-bug/bridge/core" "github.com/shurcooL/githubv4" "golang.org/x/oauth2" + + "github.com/MichaelMure/git-bug/bridge/core" ) func init() { diff --git a/bridge/github/import.go b/bridge/github/import.go index 2b9e55619456bcc34cafd466044952c450c74ada..edb97c4fc014a8c529e7bd44e9fcc03c3e5746ab 100644 --- a/bridge/github/import.go +++ b/bridge/github/import.go @@ -5,13 +5,14 @@ import ( "fmt" "time" + "github.com/shurcooL/githubv4" + "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" "github.com/MichaelMure/git-bug/util/text" - "github.com/shurcooL/githubv4" ) const ( @@ -42,7 +43,7 @@ func (gi *githubImporter) Init(conf core.Configuration) error { // ImportAll iterate over all the configured repository issues and ensure the creation of the // missing issues / timeline items / edits / label events ... func (gi *githubImporter) ImportAll(repo *cache.RepoCache, since time.Time) error { - gi.iterator = NewIterator(gi.conf[keyUser], gi.conf[keyProject], gi.conf[keyToken], since) + gi.iterator = NewIterator(gi.conf[keyOwner], gi.conf[keyProject], gi.conf[keyToken], since) // Loop over all matching issues for gi.iterator.NextIssue() { diff --git a/bridge/github/import_test.go b/bridge/github/import_test.go index 1e31501b7ce8c81847db1678f3468ab7872ebc52..9704e56f1cf51bb0b1dae26d889e2b80cacbaff9 100644 --- a/bridge/github/import_test.go +++ b/bridge/github/import_test.go @@ -140,9 +140,9 @@ func Test_Importer(t *testing.T) { importer := &githubImporter{} err = importer.Init(core.Configuration{ - "user": "MichaelMure", - "project": "git-bug-test-github-bridge", - "token": token, + keyOwner: "MichaelMure", + keyProject: "git-bug-test-github-bridge", + keyToken: token, }) require.NoError(t, err) diff --git a/bridge/github/iterator.go b/bridge/github/iterator.go index 5935276af9a3ef47719ed8df8f5b91bcb867ac18..2bb197de086fe614aadf748645da6036bf5259a4 100644 --- a/bridge/github/iterator.go +++ b/bridge/github/iterator.go @@ -60,7 +60,7 @@ type iterator struct { } // NewIterator create and initalize a new iterator -func NewIterator(user, project, token string, since time.Time) *iterator { +func NewIterator(owner, project, token string, since time.Time) *iterator { i := &iterator{ gc: buildClient(token), since: since, @@ -70,22 +70,22 @@ func NewIterator(user, project, token string, since time.Time) *iterator { issueEdit: indexer{-1}, commentEdit: indexer{-1}, variables: map[string]interface{}{ - "owner": githubv4.String(user), - "name": githubv4.String(project), + keyOwner: githubv4.String(owner), + "name": githubv4.String(project), }, }, commentEdit: commentEditIterator{ index: -1, variables: map[string]interface{}{ - "owner": githubv4.String(user), - "name": githubv4.String(project), + keyOwner: githubv4.String(owner), + "name": githubv4.String(project), }, }, issueEdit: issueEditIterator{ index: -1, variables: map[string]interface{}{ - "owner": githubv4.String(user), - "name": githubv4.String(project), + keyOwner: githubv4.String(owner), + "name": githubv4.String(project), }, }, } From f01b9bca12cb51f1f8f46b5e58bb3cb4be8126de Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Fri, 24 May 2019 21:32:27 +0200 Subject: [PATCH 05/24] Fix typo in github bridge rm --- commands/bridge_rm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/bridge_rm.go b/commands/bridge_rm.go index 80a831ff3e626479345625f472adb4626333154d..1b67608b0952b9903813c5134544301a9ed83188 100644 --- a/commands/bridge_rm.go +++ b/commands/bridge_rm.go @@ -24,7 +24,7 @@ func runBridgeRm(cmd *cobra.Command, args []string) error { } var bridgeRmCmd = &cobra.Command{ - Use: "rm name ", + Use: "rm ", Short: "Delete a configured bridge.", PreRunE: loadRepo, RunE: runBridgeRm, From 2c4c0132bf8c9e9819cc9a76d4a04ed4daeedb3b Mon Sep 17 00:00:00 2001 From: Sladyn Date: Sat, 25 May 2019 15:13:40 +0200 Subject: [PATCH 06/24] Add GetRemotes functionalities --- cache/repo_cache.go | 7 ++++++- repository/git.go | 22 ++++++++++++++++++++++ repository/mock_repo.go | 7 +++++++ repository/repo.go | 3 +++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/cache/repo_cache.go b/cache/repo_cache.go index dc1889b2b707f89d091f225ca2e09b6e8fe7dee6..b3b2ea1e7062ecac9af2a7944f23ce142bce202c 100644 --- a/cache/repo_cache.go +++ b/cache/repo_cache.go @@ -104,11 +104,16 @@ func (c *RepoCache) GetPath() string { return c.repo.GetPath() } -// GetPath returns the path to the repo. +// GetCoreEditor returns the name of the editor that the user has used to configure git. func (c *RepoCache) GetCoreEditor() (string, error) { return c.repo.GetCoreEditor() } +// GetRemotes returns the configured remotes repositories. +func (c *RepoCache) GetRemotes() (map[string]string, error) { + return c.repo.GetRemotes() +} + // GetUserName returns the name the the user has used to configure git func (c *RepoCache) GetUserName() (string, error) { return c.repo.GetUserName() diff --git a/repository/git.go b/repository/git.go index 4d6ca19a04310246aa2c2d82fa6229cb55f0b13d..801504f2e14b07dba1756971cf95d88261e7b4b2 100644 --- a/repository/git.go +++ b/repository/git.go @@ -162,6 +162,28 @@ func (repo *GitRepo) GetCoreEditor() (string, error) { return repo.runGitCommand("var", "GIT_EDITOR") } +// GetRemotes returns the configured remotes repositories. +func (repo *GitRepo) GetRemotes() (map[string]string, error) { + stdout, err := repo.runGitCommand("remote", "--verbose") + if err != nil { + return nil, err + } + + lines := strings.Split(stdout, "\n") + remotes := make(map[string]string, len(lines)) + + for _, line := range lines { + elements := strings.Fields(line) + if len(elements) != 3 { + return nil, fmt.Errorf("unexpected output format: %s", line) + } + + remotes[elements[0]] = elements[1] + } + + return remotes, nil +} + // StoreConfig store a single key/value pair in the config of the repo func (repo *GitRepo) StoreConfig(key string, value string) error { _, err := repo.runGitCommand("config", "--replace-all", key, value) diff --git a/repository/mock_repo.go b/repository/mock_repo.go index 14f5e7b5b4b998fcb4f6987a44dfb1931b691072..2dc4868ec91882a755354e0a5c13192dee658fa6 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -59,6 +59,13 @@ func (r *mockRepoForTest) GetCoreEditor() (string, error) { return "vi", nil } +// GetRemotes returns the configured remotes repositories. +func (r *mockRepoForTest) GetRemotes() (map[string]string, error) { + return map[string]string{ + "origin": "git://github.com/MichaelMure/git-bug", + }, nil +} + func (r *mockRepoForTest) StoreConfig(key string, value string) error { r.config[key] = value return nil diff --git a/repository/repo.go b/repository/repo.go index f3c2de6db3d2f26f5c0ad89b2983c693b4c25fdc..4420449325bdd5508490660d1a25fc1c7f21e5ae 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -27,6 +27,9 @@ type RepoCommon interface { // GetCoreEditor returns the name of the editor that the user has used to configure git. GetCoreEditor() (string, error) + // GetRemotes returns the configured remotes repositories. + GetRemotes() (map[string]string, error) + // StoreConfig store a single key/value pair in the config of the repo StoreConfig(key string, value string) error From 46ce1059b6977d79b37477f02f2a5028ed4f09b0 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Sat, 25 May 2019 15:20:30 +0200 Subject: [PATCH 07/24] Update Github bridge configuration Use GetRemotes in `promptURL` to suggest repo urls Add `promptTokenOptions` to select token a configuration option --- bridge/github/config.go | 286 ++++++++++++++++++++++++++++------------ 1 file changed, 200 insertions(+), 86 deletions(-) diff --git a/bridge/github/config.go b/bridge/github/config.go index 29e50b873c7b6cf481a9f4c48599f53354da60e7..f2ac750e0c8a9c699daf73f02af484f128a51e53 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -32,7 +32,7 @@ const ( ) var ( - rxGithubSplit = regexp.MustCompile(`github\.com\/([^\/]*)\/([^\/]*)`) + rxGithubURL = regexp.MustCompile(`github\.com[\/:]([^\/]*[a-z]+)\/([^\/]*[a-z]+)`) ) func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) { @@ -50,13 +50,18 @@ func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) ( project = params.Project } else if params.URL != "" { - owner, project, err = splitURL(params.URL) + _, owner, project, err = splitURL(params.URL) if err != nil { return nil, err } } else { - owner, project, err = promptURL() + remotes, err := repo.GetRemotes() + if err != nil { + return nil, err + } + + owner, project, err = promptURL(remotes) if err != nil { return nil, err } @@ -71,87 +76,19 @@ func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) ( return nil, fmt.Errorf("invalid parameter owner: %v", owner) } - // try to get token from params if provided, else use terminal prompt - // to login and generate a token + // try to get token from params if provided, else use terminal prompt to either + // enter a token or login and generate a new one if params.Token != "" { token = params.Token } else { - fmt.Println() - fmt.Println("git-bug will now generate an access token in your Github profile. Your credential are not stored and are only used to generate the token. The token is stored in the repository git config.") - fmt.Println() - fmt.Println("Depending on your configuration the token will have one of the following scopes:") - fmt.Println(" - 'user:email': to be able to read public-only users email") - fmt.Println(" - 'repo' : to be able to read private repositories") - // 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() - - isPublic, err := promptProjectVisibility() - if err != nil { - return nil, err - } - - username, err := promptUsername() - if err != nil { - return nil, err - } - - password, err := promptPassword() - if err != nil { - return nil, err - } - - var scope string - if isPublic { - // user:email is requested to be able to read public emails - // - a private email will stay private, even with this token - scope = "user:email" - } else { - // 'repo' is request to be able to read private repositories - // /!\ token will have read/write rights on every private repository you have access to - scope = "repo" - } - - // Attempt to authenticate and create a token - - note := fmt.Sprintf("git-bug - %s/%s", owner, project) - - resp, err := requestToken(note, username, password, scope) + token, err = promptTokenOptions(owner, project) if err != nil { return nil, err } - - defer resp.Body.Close() - - // Handle 2FA is needed - OTPHeader := resp.Header.Get("X-GitHub-OTP") - if resp.StatusCode == http.StatusUnauthorized && OTPHeader != "" { - otpCode, err := prompt2FA() - if err != nil { - return nil, err - } - - resp, err = requestTokenWith2FA(note, username, password, otpCode, scope) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - } - - if resp.StatusCode == http.StatusCreated { - token, err = decodeBody(resp.Body) - if err != nil { - return nil, err - } - - } else { - b, _ := ioutil.ReadAll(resp.Body) - return nil, fmt.Errorf("error creating token %v: %v", resp.StatusCode, string(b)) - } } - // verifying access to project with token + // verify access to the repository with token ok, err = validateProject(owner, project, token) if err != nil { return nil, err @@ -253,6 +190,130 @@ func randomFingerprint() string { return string(b) } +func promptTokenOptions(owner, project string) (string, error) { + for { + fmt.Println() + fmt.Println("[0]: i have my own token") + fmt.Println("[1]: login and generate token") + fmt.Print("Select option: ") + + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + fmt.Println() + if err != nil { + return "", err + } + + line = strings.TrimRight(line, "\n") + + index, err := strconv.Atoi(line) + if err != nil || (index != 0 && index != 1) { + fmt.Println("invalid input") + continue + } + + if index == 0 { + return promptToken() + } + + return loginAndRequestToken(owner, project) + } +} + +func promptToken() (string, error) { + for { + fmt.Print("Enter token: ") + + byteToken, err := terminal.ReadPassword(int(syscall.Stdin)) + fmt.Println() + + if err != nil { + return "", err + } + + if len(byteToken) > 0 { + return string(byteToken), nil + } + + fmt.Println("token is empty") + } +} + +func loginAndRequestToken(owner, project string) (string, error) { + fmt.Println("git-bug will now generate an access token in your Github profile. Your credential are not stored and are only used to generate the token. The token is stored in the repository git config.") + fmt.Println() + fmt.Println("Depending on your configuration the token will have one of the following scopes:") + fmt.Println(" - 'user:email': to be able to read public-only users email") + fmt.Println(" - 'repo' : to be able to read private repositories") + // 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() + + // prompt project visibility to know the token scope needed for the repository + isPublic, err := promptProjectVisibility() + if err != nil { + return "", err + } + + username, err := promptUsername() + if err != nil { + return "", err + } + + password, err := promptPassword() + if err != nil { + return "", err + } + + var scope string + if isPublic { + // user:email is requested to be able to read public emails + // - a private email will stay private, even with this token + scope = "user:email" + } else { + // 'repo' is request to be able to read private repositories + // /!\ token will have read/write rights on every private repository you have access to + scope = "repo" + } + + // Attempt to authenticate and create a token + + note := fmt.Sprintf("git-bug - %s/%s", owner, project) + + resp, err := requestToken(note, username, password, scope) + if err != nil { + return "", err + } + + defer resp.Body.Close() + + // Handle 2FA is needed + OTPHeader := resp.Header.Get("X-GitHub-OTP") + if resp.StatusCode == http.StatusUnauthorized && OTPHeader != "" { + otpCode, err := prompt2FA() + if err != nil { + return "", err + } + + resp, err = requestTokenWith2FA(note, username, password, otpCode, scope) + if err != nil { + return "", err + } + + defer resp.Body.Close() + } + + if resp.StatusCode == http.StatusCreated { + token, err := decodeBody(resp.Body) + if err != nil { + return "", err + } + + return token, nil + } + + b, _ := ioutil.ReadAll(resp.Body) + return "", fmt.Errorf("error creating token %v: %v", resp.StatusCode, string(b)) +} + func promptUsername() (string, error) { for { fmt.Print("username: ") @@ -276,7 +337,45 @@ func promptUsername() (string, error) { } } -func promptURL() (string, string, error) { +func promptURL(remotes map[string]string) (string, string, error) { + validRemotes := valideGithubURLRemotes(remotes) + if len(validRemotes) > 0 { + for { + fmt.Println("\nDetected projects:") + + // print valid remote github urls + for i, remote := range validRemotes { + fmt.Printf("[%d]: %v\n", i+1, remote) + } + + fmt.Printf("\n[0]: Another project\n\n") + fmt.Printf("Select option: ") + + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + return "", "", err + } + + line = strings.TrimRight(line, "\n") + + index, err := strconv.Atoi(line) + if err != nil || (index < 0 && index >= len(validRemotes)) { + fmt.Println("invalid input") + continue + } + + // if user want to enter another project url break this loop + if index == 0 { + break + } + + // get owner and project with index + _, owner, project, _ := splitURL(validRemotes[index-1]) + return owner, project, nil + } + } + + // manually enter github url for { fmt.Print("Github project URL: ") @@ -291,23 +390,37 @@ func promptURL() (string, string, error) { continue } - projectOwner, projectName, err := splitURL(line) + // get owner and project from url + _, owner, project, err := splitURL(line) if err != nil { fmt.Println(err) continue } - return projectOwner, projectName, nil + return owner, project, nil } } -func splitURL(url string) (string, string, error) { - res := rxGithubSplit.FindStringSubmatch(url) +func splitURL(url string) (string, string, string, error) { + res := rxGithubURL.FindStringSubmatch(url) if res == nil { - return "", "", fmt.Errorf("bad github project url") + return "", "", "", fmt.Errorf("bad github project url") + } + + return res[0], res[1], res[2], nil +} + +func valideGithubURLRemotes(remotes map[string]string) []string { + urls := make([]string, 0, len(remotes)) + for _, url := range remotes { + // split url can work again with shortURL + shortURL, _, _, err := splitURL(url) + if err == nil { + urls = append(urls, shortURL) + } } - return res[1], res[2], nil + return urls } func validateUsername(username string) (bool, error) { @@ -334,6 +447,7 @@ func validateProject(owner, project, token string) (bool, error) { return false, err } + // need the token for private repositories req.Header.Set("Authorization", fmt.Sprintf("token %s", token)) client := &http.Client{ @@ -401,11 +515,10 @@ func prompt2FA() (string, error) { } func promptProjectVisibility() (bool, error) { - fmt.Println("[0]: public") - fmt.Println("[1]: private") - for { - fmt.Print("repository visibility type: ") + fmt.Println("[0]: public") + fmt.Println("[1]: private") + fmt.Print("repository visibility: ") line, err := bufio.NewReader(os.Stdin).ReadString('\n') fmt.Println() @@ -421,6 +534,7 @@ func promptProjectVisibility() (bool, error) { continue } + // return true for public repositories, false for private return index == 0, nil } } From 0de2bd92b034ec265dbc66751626b4db67983760 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Sat, 25 May 2019 15:56:52 +0200 Subject: [PATCH 08/24] Launchpad bridge configuration from `BridgeParams` Project and URL Improve Github config comments --- bridge/github/config.go | 8 +++--- bridge/launchpad/config.go | 53 ++++++++++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/bridge/github/config.go b/bridge/github/config.go index f2ac750e0c8a9c699daf73f02af484f128a51e53..e723fda684568e17e88a92779c898903bacb446a 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -42,25 +42,27 @@ func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) ( var owner string var project string - // getting owner and project name: - // first use directly params if they are both provided, else try to parse - // them from params URL, and finaly try getting them from terminal prompt + // getting owner and project name if params.Owner != "" && params.Project != "" { + // first try to use params if they are both provided owner = params.Owner project = params.Project } else if params.URL != "" { + // try to parse them from params URL _, owner, project, err = splitURL(params.URL) if err != nil { return nil, err } } else { + // remote suggestions remotes, err := repo.GetRemotes() if err != nil { return nil, err } + // try terminal prompt owner, project, err = promptURL(remotes) if err != nil { return nil, err diff --git a/bridge/launchpad/config.go b/bridge/launchpad/config.go index 637ffd2c798ec125b30f4925de3d91e36229c401..1c072ad3ef837537cbf99eabb93b58f7910f2233 100644 --- a/bridge/launchpad/config.go +++ b/bridge/launchpad/config.go @@ -3,7 +3,9 @@ package launchpad import ( "bufio" "fmt" + "net/http" "os" + "regexp" "strings" "github.com/MichaelMure/git-bug/bridge/core" @@ -12,18 +14,43 @@ import ( const keyProject = "project" +var ( + rxLaunchpadURL = regexp.MustCompile(`launchpad\.net[\/:]([^\/]*[a-z]+)`) +) + func (*Launchpad) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) { conf := make(core.Configuration) + var err error + var project string + + if params.Project != "" { + project = params.Project + + } else if params.URL != "" { + // get project name from url + _, project, err = splitURL(params.URL) + if err != nil { + return nil, err + } - if params.Project == "" { - projectName, err := promptProjectName() + } else { + // get project name from terminal prompt + project, err = promptProjectName() if err != nil { return nil, err } + } - conf[keyProject] = projectName + // verify project + ok, err := validateProject(project) + if err != nil { + return nil, err + } + if !ok { + return nil, fmt.Errorf("project doesn't exist") } + conf[keyProject] = project return conf, nil } @@ -55,6 +82,22 @@ func promptProjectName() (string, error) { } } -func validateProject() (bool, error) { - return false, nil +func validateProject(project string) (bool, error) { + url := fmt.Sprintf("%s/%s", apiRoot, project) + + resp, err := http.Get(url) + if err != nil { + return false, err + } + + return resp.StatusCode == http.StatusOK, nil +} + +func splitURL(url string) (string, string, error) { + res := rxLaunchpadURL.FindStringSubmatch(url) + if res == nil { + return "", "", fmt.Errorf("bad Launchpad project url") + } + + return res[0], res[1], nil } From 43758a14fdd42ec9b25f1fe291ec774ed67cf34e Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Sat, 25 May 2019 17:27:25 +0200 Subject: [PATCH 09/24] Add warning messages for launchpad-preview fix --target flag description improve comments --- bridge/github/config.go | 6 +++--- bridge/launchpad/config.go | 7 +++++++ commands/bridge_configure.go | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/bridge/github/config.go b/bridge/github/config.go index e723fda684568e17e88a92779c898903bacb446a..3baafadd1fc29be00e1bed11b99daaccb50edd66 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -44,12 +44,12 @@ func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) ( // getting owner and project name if params.Owner != "" && params.Project != "" { - // first try to use params if they are both provided + // first try to use params if both or project and owner are provided owner = params.Owner project = params.Project } else if params.URL != "" { - // try to parse them from params URL + // try to parse params URL and extract owner and project _, owner, project, err = splitURL(params.URL) if err != nil { return nil, err @@ -62,7 +62,7 @@ func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) ( return nil, err } - // try terminal prompt + // terminal prompt owner, project, err = promptURL(remotes) if err != nil { return nil, err diff --git a/bridge/launchpad/config.go b/bridge/launchpad/config.go index 1c072ad3ef837537cbf99eabb93b58f7910f2233..6b299a9aef02aea22dcd119bff081c8f3545f7bb 100644 --- a/bridge/launchpad/config.go +++ b/bridge/launchpad/config.go @@ -19,6 +19,13 @@ var ( ) func (*Launchpad) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) { + if params.Token != "" { + fmt.Println("warn: token is not needed to configure a launchpad-preview bridge") + } + if params.Owner != "" { + fmt.Println("warn: owner is not used when configuring a launchpad-preview bridge") + } + conf := make(core.Configuration) var err error var project string diff --git a/commands/bridge_configure.go b/commands/bridge_configure.go index b20b38a59ec5eaf7d4c4992ee3f0d571f15d05c2..12f287b8f978c2925a1fddad19a9f593d4a0be49 100644 --- a/commands/bridge_configure.go +++ b/commands/bridge_configure.go @@ -114,7 +114,7 @@ var bridgeConfigureCmd = &cobra.Command{ func init() { bridgeCmd.AddCommand(bridgeConfigureCmd) bridgeConfigureCmd.Flags().StringVarP(&name, "name", "n", "", "Bridge name") - bridgeConfigureCmd.Flags().StringVarP(&target, "target", "t", "", "Bridge target name. Valid values are [github,gitlab,gitea,launchpad]") + bridgeConfigureCmd.Flags().StringVarP(&target, "target", "t", "", "Bridge target name. Valid values are [github,gitlab,gitea,launchpad-preview]") bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.URL, "url", "u", "", "Repository url") bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Owner, "owner", "o", "", "Repository owner") bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Token, "token", "T", "", "Authentication token") From 1022b9e5364c7f4bb8e38363b3760a3e48813047 Mon Sep 17 00:00:00 2001 From: Amine Date: Sun, 26 May 2019 12:35:53 +0200 Subject: [PATCH 10/24] Update flags descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhance printing in prompt Co-Authored-By: Michael Muré --- bridge/github/config.go | 4 ++-- bridge/launchpad/config.go | 4 ++-- commands/bridge_configure.go | 13 +++++++------ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bridge/github/config.go b/bridge/github/config.go index 3baafadd1fc29be00e1bed11b99daaccb50edd66..7868156ed3d9a5ef279ec06841796d854ec412d6 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -195,8 +195,8 @@ func randomFingerprint() string { func promptTokenOptions(owner, project string) (string, error) { for { fmt.Println() - fmt.Println("[0]: i have my own token") - fmt.Println("[1]: login and generate token") + fmt.Println("[0]: user provided token") + fmt.Println("[1]: automated token creation") fmt.Print("Select option: ") line, err := bufio.NewReader(os.Stdin).ReadString('\n') diff --git a/bridge/launchpad/config.go b/bridge/launchpad/config.go index 6b299a9aef02aea22dcd119bff081c8f3545f7bb..c5f72c019daf498758dd9728b3ee17216dbb74db 100644 --- a/bridge/launchpad/config.go +++ b/bridge/launchpad/config.go @@ -20,10 +20,10 @@ var ( func (*Launchpad) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) { if params.Token != "" { - fmt.Println("warn: token is not needed to configure a launchpad-preview bridge") + fmt.Println("warning: --token is ineffective for a Launchpad bridge") } if params.Owner != "" { - fmt.Println("warn: owner is not used when configuring a launchpad-preview bridge") + fmt.Println("warning: --owner is ineffective for a Launchpad bridge") } conf := make(core.Configuration) diff --git a/commands/bridge_configure.go b/commands/bridge_configure.go index 12f287b8f978c2925a1fddad19a9f593d4a0be49..0ad602f4b39f517b6ba84f0832bf2fa9f8ea3d43 100644 --- a/commands/bridge_configure.go +++ b/commands/bridge_configure.go @@ -113,10 +113,11 @@ var bridgeConfigureCmd = &cobra.Command{ func init() { bridgeCmd.AddCommand(bridgeConfigureCmd) - bridgeConfigureCmd.Flags().StringVarP(&name, "name", "n", "", "Bridge name") - bridgeConfigureCmd.Flags().StringVarP(&target, "target", "t", "", "Bridge target name. Valid values are [github,gitlab,gitea,launchpad-preview]") - bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.URL, "url", "u", "", "Repository url") - bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Owner, "owner", "o", "", "Repository owner") - bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Token, "token", "T", "", "Authentication token") - bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Project, "project", "p", "", "Repository name") + bridgeConfigureCmd.Flags().StringVarP(&name, "name", "n", "", "A distinctive name to identify the bridge") + bridgeConfigureCmd.Flags().StringVarP(&target, "target", "t", "", + fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ","))) + bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.URL, "url", "u", "", "The URL of the target repository") + bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Owner, "owner", "o", "", "The owner of the target repository") + bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Token, "token", "T", "", "The authentication token for the API") + bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Project, "project", "p", "", "The name of the target repository") } From 1c146a1b678d11ef86840955a9195685d18e4a52 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Sun, 26 May 2019 12:47:01 +0200 Subject: [PATCH 11/24] Change client default timeout value to 60 seconds add named return values for easier validation for 2FA codes --- bridge/github/config.go | 29 +++++++++-------------------- bridge/github/iterator.go | 12 ++++++------ 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/bridge/github/config.go b/bridge/github/config.go index 7868156ed3d9a5ef279ec06841796d854ec412d6..dc1185b5cb52b60dab0923090fd26d05dc068ea6 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -28,7 +28,7 @@ const ( keyProject = "project" keyToken = "token" - defaultTimeout = 5 * time.Second + defaultTimeout = 60 * time.Second ) var ( @@ -243,8 +243,10 @@ func promptToken() (string, error) { func loginAndRequestToken(owner, project string) (string, error) { fmt.Println("git-bug will now generate an access token in your Github profile. Your credential are not stored and are only used to generate the token. The token is stored in the repository git config.") fmt.Println() - fmt.Println("Depending on your configuration the token will have one of the following scopes:") + fmt.Println("The access scope depend on the type of repository.") + fmt.Println("Public:") fmt.Println(" - 'user:email': to be able to read public-only users email") + fmt.Println("Private:") fmt.Println(" - 'repo' : to be able to read private repositories") // 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() @@ -304,12 +306,7 @@ func loginAndRequestToken(owner, project string) (string, error) { } if resp.StatusCode == http.StatusCreated { - token, err := decodeBody(resp.Body) - if err != nil { - return "", err - } - - return token, nil + return decodeBody(resp.Body) } b, _ := ioutil.ReadAll(resp.Body) @@ -403,7 +400,7 @@ func promptURL(remotes map[string]string) (string, string, error) { } } -func splitURL(url string) (string, string, string, error) { +func splitURL(url string) (shortURL string, owner string, project string, err error) { res := rxGithubURL.FindStringSubmatch(url) if res == nil { return "", "", "", fmt.Errorf("bad github project url") @@ -500,19 +497,11 @@ func prompt2FA() (string, error) { return "", err } - if len(byte2fa) != 6 { - fmt.Println("invalid 2FA code size") - continue - } - - str2fa := string(byte2fa) - _, err = strconv.Atoi(str2fa) - if err != nil { - fmt.Println("2fa code must be digits only") - continue + if len(byte2fa) > 0 { + return string(byte2fa), nil } - return str2fa, nil + fmt.Println("code is empty") } } diff --git a/bridge/github/iterator.go b/bridge/github/iterator.go index 2bb197de086fe614aadf748645da6036bf5259a4..fcf72b8f3fb258121f9721003c9416dcd1daabaf 100644 --- a/bridge/github/iterator.go +++ b/bridge/github/iterator.go @@ -70,22 +70,22 @@ func NewIterator(owner, project, token string, since time.Time) *iterator { issueEdit: indexer{-1}, commentEdit: indexer{-1}, variables: map[string]interface{}{ - keyOwner: githubv4.String(owner), - "name": githubv4.String(project), + "owner": githubv4.String(owner), + "name": githubv4.String(project), }, }, commentEdit: commentEditIterator{ index: -1, variables: map[string]interface{}{ - keyOwner: githubv4.String(owner), - "name": githubv4.String(project), + "owner": githubv4.String(owner), + "name": githubv4.String(project), }, }, issueEdit: issueEditIterator{ index: -1, variables: map[string]interface{}{ - keyOwner: githubv4.String(owner), - "name": githubv4.String(project), + "owner": githubv4.String(owner), + "name": githubv4.String(project), }, }, } From 99b6107487f25bccf91d78fa44d9fe9ab2e3ef01 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Sun, 26 May 2019 13:10:47 +0200 Subject: [PATCH 12/24] Disable `bridgeConfigure` sort flags option rename `validateGithubURLRemotes` to `getValideGithubRemoteURLs` --- bridge/github/config.go | 4 ++-- commands/bridge_configure.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bridge/github/config.go b/bridge/github/config.go index dc1185b5cb52b60dab0923090fd26d05dc068ea6..80fc9152d13de098e990c38cdde94c5bcd1c3fd1 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -337,7 +337,7 @@ func promptUsername() (string, error) { } func promptURL(remotes map[string]string) (string, string, error) { - validRemotes := valideGithubURLRemotes(remotes) + validRemotes := getValideGithubRemoteURLs(remotes) if len(validRemotes) > 0 { for { fmt.Println("\nDetected projects:") @@ -409,7 +409,7 @@ func splitURL(url string) (shortURL string, owner string, project string, err er return res[0], res[1], res[2], nil } -func valideGithubURLRemotes(remotes map[string]string) []string { +func getValideGithubRemoteURLs(remotes map[string]string) []string { urls := make([]string, 0, len(remotes)) for _, url := range remotes { // split url can work again with shortURL diff --git a/commands/bridge_configure.go b/commands/bridge_configure.go index 0ad602f4b39f517b6ba84f0832bf2fa9f8ea3d43..c1bea08bd3f4139f130f11261b785395107c75f6 100644 --- a/commands/bridge_configure.go +++ b/commands/bridge_configure.go @@ -120,4 +120,5 @@ func init() { bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Owner, "owner", "o", "", "The owner of the target repository") bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Token, "token", "T", "", "The authentication token for the API") bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Project, "project", "p", "", "The name of the target repository") + bridgeConfigureCmd.Flags().SortFlags = false } From 5911cb96c02fdec57638fe4d49cac10b3f19e3ce Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Sun, 26 May 2019 17:35:39 +0200 Subject: [PATCH 13/24] Change github regex for better matching Remove '.git' suffixes from URLs Change token scope for public repositories to `repo:public_repo` --- bridge/github/config.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/bridge/github/config.go b/bridge/github/config.go index 80fc9152d13de098e990c38cdde94c5bcd1c3fd1..2430de9c0f6395a3bfb3c67bf6610385266c728c 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -32,7 +32,7 @@ const ( ) var ( - rxGithubURL = regexp.MustCompile(`github\.com[\/:]([^\/]*[a-z]+)\/([^\/]*[a-z]+)`) + rxGithubURL = regexp.MustCompile(`github\.com[\/:]([a-zA-Z0-9\-\_]+)\/([a-zA-Z0-9\-\_\.]+)`) ) func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) { @@ -222,6 +222,16 @@ func promptTokenOptions(owner, project string) (string, error) { } func promptToken() (string, error) { + fmt.Println("You can generate a new token by visiting https://github.com/settings/tokens.") + fmt.Println("Choose 'Generate new token' and set the necessary access scope for your repository.") + fmt.Println() + fmt.Println("The access scope depend on the type of repository.") + fmt.Println("Public:") + fmt.Println(" - 'repo:public_repo': to be able to read public repositories") + fmt.Println("Private:") + fmt.Println(" - 'repo' : to be able to read private repositories") + fmt.Println() + for { fmt.Print("Enter token: ") @@ -245,9 +255,9 @@ func loginAndRequestToken(owner, project string) (string, error) { fmt.Println() fmt.Println("The access scope depend on the type of repository.") fmt.Println("Public:") - fmt.Println(" - 'user:email': to be able to read public-only users email") + fmt.Println(" - 'repo:public_repo': to be able to read public repositories") fmt.Println("Private:") - fmt.Println(" - 'repo' : to be able to read private repositories") + fmt.Println(" - 'repo' : to be able to read private repositories") // 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() @@ -269,9 +279,8 @@ func loginAndRequestToken(owner, project string) (string, error) { var scope string if isPublic { - // user:email is requested to be able to read public emails - // - a private email will stay private, even with this token - scope = "user:email" + // repo:public_repo is requested to be able to read public repositories + scope = "repo:public_repo" } else { // 'repo' is request to be able to read private repositories // /!\ token will have read/write rights on every private repository you have access to @@ -401,7 +410,8 @@ func promptURL(remotes map[string]string) (string, string, error) { } func splitURL(url string) (shortURL string, owner string, project string, err error) { - res := rxGithubURL.FindStringSubmatch(url) + cleanURL := strings.TrimSuffix(url, ".git") + res := rxGithubURL.FindStringSubmatch(cleanURL) if res == nil { return "", "", "", fmt.Errorf("bad github project url") } From aa439fae06df64883455039accfc81fa479f1a8d Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Mon, 27 May 2019 19:15:53 +0200 Subject: [PATCH 14/24] Add bridge configure documentation and examples --- commands/bridge_configure.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/commands/bridge_configure.go b/commands/bridge_configure.go index c1bea08bd3f4139f130f11261b785395107c75f6..f3f7f66fc070a552948e3baab1ca5badf443d40b 100644 --- a/commands/bridge_configure.go +++ b/commands/bridge_configure.go @@ -57,7 +57,7 @@ func runBridgeConfigure(cmd *cobra.Command, args []string) error { return err } - fmt.Println("successfully configured bridge") + fmt.Printf("Successfully configured bridge: %s\n", name) return nil } @@ -105,8 +105,26 @@ func promptName() (string, error) { } var bridgeConfigureCmd = &cobra.Command{ - Use: "configure", - Short: "Configure a new bridge.", + Use: "configure", + Short: "Configure a new bridge.", + Long: ` Configure a new bridge by passing flags or/and using interactive terminal prompts. You can avoid all the terminal prompts by passing all the necessary flags to configure your bridge. + Repository configuration can be made by passing or the --url flag or the --project and/or --owner flags. If the three flags are provided git-bug will use --project and --owner flags. + Token configuration can be made by passing it in the --token flag or in the terminal prompt. If you don't already have one you can use terminal prompt to login and generate it directly. + For Github and Gitlab bridges, git-bug need a token to export and import issues, comments and editions for public and private repositories. + For Launchpad bridges, git-bug for now supports only public repositories and you only need --project or --url flag to configure it.`, + Example: `# For Github +git bug bridge configure \ + --name=default \ + --target=github \ + --owner=$(OWNER) \ + --project=$(PROJECT) \ + --token=$(TOKEN) + +# For Launchpad +git bug bridge configure \ + --name=default \ + --target=launchpad-preview \ + --url=https://bugs.launchpad.net/ubuntu/`, PreRunE: loadRepo, RunE: runBridgeConfigure, } From 5ea0cb138bd4152724afa73909db508e73452fd1 Mon Sep 17 00:00:00 2001 From: Amine Date: Mon, 27 May 2019 20:26:36 +0200 Subject: [PATCH 15/24] Update documentation and function naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Michael Muré --- bridge/github/config.go | 7 +++---- bridge/launchpad/config.go | 5 ++++- commands/bridge_configure.go | 6 ++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bridge/github/config.go b/bridge/github/config.go index 2430de9c0f6395a3bfb3c67bf6610385266c728c..f44be47251291bcf0c5bb16b5144b7a7e4824efd 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -196,7 +196,7 @@ func promptTokenOptions(owner, project string) (string, error) { for { fmt.Println() fmt.Println("[0]: user provided token") - fmt.Println("[1]: automated token creation") + fmt.Println("[1]: interactive token creation") fmt.Print("Select option: ") line, err := bufio.NewReader(os.Stdin).ReadString('\n') @@ -258,7 +258,6 @@ func loginAndRequestToken(owner, project string) (string, error) { fmt.Println(" - 'repo:public_repo': to be able to read public repositories") fmt.Println("Private:") fmt.Println(" - 'repo' : to be able to read private repositories") - // 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() // prompt project visibility to know the token scope needed for the repository @@ -346,7 +345,7 @@ func promptUsername() (string, error) { } func promptURL(remotes map[string]string) (string, string, error) { - validRemotes := getValideGithubRemoteURLs(remotes) + validRemotes := getValidGithubRemoteURLs(remotes) if len(validRemotes) > 0 { for { fmt.Println("\nDetected projects:") @@ -419,7 +418,7 @@ func splitURL(url string) (shortURL string, owner string, project string, err er return res[0], res[1], res[2], nil } -func getValideGithubRemoteURLs(remotes map[string]string) []string { +func getValidGithubRemoteURLs(remotes map[string]string) []string { urls := make([]string, 0, len(remotes)) for _, url := range remotes { // split url can work again with shortURL diff --git a/bridge/launchpad/config.go b/bridge/launchpad/config.go index c5f72c019daf498758dd9728b3ee17216dbb74db..ef206fd5b108408c44b0edd74ce398fd023e0487 100644 --- a/bridge/launchpad/config.go +++ b/bridge/launchpad/config.go @@ -92,7 +92,10 @@ func promptProjectName() (string, error) { func validateProject(project string) (bool, error) { url := fmt.Sprintf("%s/%s", apiRoot, project) - resp, err := http.Get(url) + client := := &http.Client{ + Timeout: defaultTimeout, + } + resp, err := client.Get(url) if err != nil { return false, err } diff --git a/commands/bridge_configure.go b/commands/bridge_configure.go index f3f7f66fc070a552948e3baab1ca5badf443d40b..711ceb9c21f0318eef0044bca1c3972ba0215968 100644 --- a/commands/bridge_configure.go +++ b/commands/bridge_configure.go @@ -108,10 +108,8 @@ var bridgeConfigureCmd = &cobra.Command{ Use: "configure", Short: "Configure a new bridge.", Long: ` Configure a new bridge by passing flags or/and using interactive terminal prompts. You can avoid all the terminal prompts by passing all the necessary flags to configure your bridge. - Repository configuration can be made by passing or the --url flag or the --project and/or --owner flags. If the three flags are provided git-bug will use --project and --owner flags. - Token configuration can be made by passing it in the --token flag or in the terminal prompt. If you don't already have one you can use terminal prompt to login and generate it directly. - For Github and Gitlab bridges, git-bug need a token to export and import issues, comments and editions for public and private repositories. - For Launchpad bridges, git-bug for now supports only public repositories and you only need --project or --url flag to configure it.`, + Repository configuration can be made by passing either the --url flag or the --project and --owner flags. If the three flags are provided git-bug will use --project and --owner flags. + Token configuration can be directly passed with the --token flag or in the terminal prompt. If you don't already have one you can use the interactive procedure to generate one. Example: `# For Github git bug bridge configure \ --name=default \ From 630bbc6753de501158e72600da1af292a5125603 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Wed, 29 May 2019 20:48:51 +0200 Subject: [PATCH 16/24] Update bridge configure long description and examples --- commands/bridge_configure.go | 56 +++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/commands/bridge_configure.go b/commands/bridge_configure.go index 711ceb9c21f0318eef0044bca1c3972ba0215968..c9ac168025362fc1a29bfabdc1e2128be5f04a49 100644 --- a/commands/bridge_configure.go +++ b/commands/bridge_configure.go @@ -20,9 +20,9 @@ const ( ) var ( - name string - target string - bridgeParams core.BridgeParams + bridgeConfigureName string + bridgeConfigureTarget string + bridgeParams core.BridgeParams ) func runBridgeConfigure(cmd *cobra.Command, args []string) error { @@ -33,21 +33,21 @@ func runBridgeConfigure(cmd *cobra.Command, args []string) error { defer backend.Close() interrupt.RegisterCleaner(backend.Close) - if target == "" { - target, err = promptTarget() + if bridgeConfigureTarget == "" { + bridgeConfigureTarget, err = promptTarget() if err != nil { return err } } - if name == "" { - name, err = promptName() + if bridgeConfigureName == "" { + bridgeConfigureName, err = promptName() if err != nil { return err } } - b, err := bridge.NewBridge(backend, target, name) + b, err := bridge.NewBridge(backend, bridgeConfigureTarget, bridgeConfigureName) if err != nil { return err } @@ -57,7 +57,7 @@ func runBridgeConfigure(cmd *cobra.Command, args []string) error { return err } - fmt.Printf("Successfully configured bridge: %s\n", name) + fmt.Printf("Successfully configured bridge: %s\n", bridgeConfigureName) return nil } @@ -109,8 +109,38 @@ var bridgeConfigureCmd = &cobra.Command{ Short: "Configure a new bridge.", Long: ` Configure a new bridge by passing flags or/and using interactive terminal prompts. You can avoid all the terminal prompts by passing all the necessary flags to configure your bridge. Repository configuration can be made by passing either the --url flag or the --project and --owner flags. If the three flags are provided git-bug will use --project and --owner flags. - Token configuration can be directly passed with the --token flag or in the terminal prompt. If you don't already have one you can use the interactive procedure to generate one. - Example: `# For Github + Token configuration can be directly passed with the --token flag or in the terminal prompt. If you don't already have one you can use the interactive procedure to generate one.`, + Example: `# Interactive example +[1]: github +[2]: launchpad-preview +target: 1 +name [default]: default + +Detected projects: +[1]: github.com/a-hilaly/git-bug +[2]: github.com/MichaelMure/git-bug + +[0]: Another project + +Select option: 1 + +[0]: user provided token +[1]: interactive token creation +Select option: 0 + +You can generate a new token by visiting https://github.com/settings/tokens. +Choose 'Generate new token' and set the necessary access scope for your repository. + +The access scope depend on the type of repository. +Public: + - 'public_repo': to be able to read public repositories +Private: + - 'repo' : to be able to read private repositories + +Enter token: +Successfully configured bridge: default + +# For Github git bug bridge configure \ --name=default \ --target=github \ @@ -129,8 +159,8 @@ git bug bridge configure \ func init() { bridgeCmd.AddCommand(bridgeConfigureCmd) - bridgeConfigureCmd.Flags().StringVarP(&name, "name", "n", "", "A distinctive name to identify the bridge") - bridgeConfigureCmd.Flags().StringVarP(&target, "target", "t", "", + bridgeConfigureCmd.Flags().StringVarP(&bridgeConfigureName, "name", "n", "", "A distinctive name to identify the bridge") + bridgeConfigureCmd.Flags().StringVarP(&bridgeConfigureTarget, "target", "t", "", fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ","))) bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.URL, "url", "u", "", "The URL of the target repository") bridgeConfigureCmd.Flags().StringVarP(&bridgeParams.Owner, "owner", "o", "", "The owner of the target repository") From 45d47a0966410840dfe4e90584ba0147eb334634 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Wed, 29 May 2019 20:49:55 +0200 Subject: [PATCH 17/24] Update configuration process and add unit tests Update launchpad bridge --- bridge/github/config.go | 50 ++++++++---- bridge/github/config_test.go | 146 +++++++++++++++++++++++++++++++++++ bridge/launchpad/config.go | 9 ++- 3 files changed, 187 insertions(+), 18 deletions(-) create mode 100644 bridge/github/config_test.go diff --git a/bridge/github/config.go b/bridge/github/config.go index f44be47251291bcf0c5bb16b5144b7a7e4824efd..c48c11f6b3e5856728bca60d93f5ee018bbc5775 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -16,6 +16,8 @@ import ( "syscall" "time" + "github.com/pkg/errors" + "golang.org/x/crypto/ssh/terminal" "github.com/MichaelMure/git-bug/bridge/core" @@ -32,7 +34,7 @@ const ( ) var ( - rxGithubURL = regexp.MustCompile(`github\.com[\/:]([a-zA-Z0-9\-\_]+)\/([a-zA-Z0-9\-\_\.]+)`) + ErrBadProjectURL = errors.New("bad project url") ) func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) { @@ -50,7 +52,7 @@ func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) ( } else if params.URL != "" { // try to parse params URL and extract owner and project - _, owner, project, err = splitURL(params.URL) + owner, project, err = splitURL(params.URL) if err != nil { return nil, err } @@ -227,9 +229,9 @@ func promptToken() (string, error) { fmt.Println() fmt.Println("The access scope depend on the type of repository.") fmt.Println("Public:") - fmt.Println(" - 'repo:public_repo': to be able to read public repositories") + fmt.Println(" - 'public_repo': to be able to read public repositories") fmt.Println("Private:") - fmt.Println(" - 'repo' : to be able to read private repositories") + fmt.Println(" - 'repo' : to be able to read private repositories") fmt.Println() for { @@ -255,9 +257,9 @@ func loginAndRequestToken(owner, project string) (string, error) { fmt.Println() fmt.Println("The access scope depend on the type of repository.") fmt.Println("Public:") - fmt.Println(" - 'repo:public_repo': to be able to read public repositories") + fmt.Println(" - 'public_repo': to be able to read public repositories") fmt.Println("Private:") - fmt.Println(" - 'repo' : to be able to read private repositories") + fmt.Println(" - 'repo' : to be able to read private repositories") fmt.Println() // prompt project visibility to know the token scope needed for the repository @@ -278,8 +280,8 @@ func loginAndRequestToken(owner, project string) (string, error) { var scope string if isPublic { - // repo:public_repo is requested to be able to read public repositories - scope = "repo:public_repo" + // public_repo is requested to be able to read public repositories + scope = "public_repo" } else { // 'repo' is request to be able to read private repositories // /!\ token will have read/write rights on every private repository you have access to @@ -377,7 +379,7 @@ func promptURL(remotes map[string]string) (string, string, error) { } // get owner and project with index - _, owner, project, _ := splitURL(validRemotes[index-1]) + owner, project, _ := splitURL(validRemotes[index-1]) return owner, project, nil } } @@ -398,7 +400,7 @@ func promptURL(remotes map[string]string) (string, string, error) { } // get owner and project from url - _, owner, project, err := splitURL(line) + owner, project, err := splitURL(line) if err != nil { fmt.Println(err) continue @@ -408,22 +410,34 @@ func promptURL(remotes map[string]string) (string, string, error) { } } -func splitURL(url string) (shortURL string, owner string, project string, err error) { +// splitURL extract the owner and project from a github repository URL. It will remove the +// '.git' extension from the URL before parsing it. +// Note that Github removes the '.git' extension from projects names at their creation +func splitURL(url string) (owner string, project string, err error) { cleanURL := strings.TrimSuffix(url, ".git") - res := rxGithubURL.FindStringSubmatch(cleanURL) + + re, err := regexp.Compile(`github\.com[/:]([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_.]+)`) + if err != nil { + return "", "", err + } + + res := re.FindStringSubmatch(cleanURL) if res == nil { - return "", "", "", fmt.Errorf("bad github project url") + return "", "", ErrBadProjectURL } - return res[0], res[1], res[2], nil + owner = res[1] + project = res[2] + return owner, project, nil } func getValidGithubRemoteURLs(remotes map[string]string) []string { urls := make([]string, 0, len(remotes)) for _, url := range remotes { // split url can work again with shortURL - shortURL, _, _, err := splitURL(url) + owner, project, err := splitURL(url) if err == nil { + shortURL := fmt.Sprintf("%s/%s/%s", "github.com", owner, project) urls = append(urls, shortURL) } } @@ -434,7 +448,11 @@ func getValidGithubRemoteURLs(remotes map[string]string) []string { func validateUsername(username string) (bool, error) { url := fmt.Sprintf("%s/users/%s", githubV3Url, username) - resp, err := http.Get(url) + client := &http.Client{ + Timeout: defaultTimeout, + } + + resp, err := client.Get(url) if err != nil { return false, err } diff --git a/bridge/github/config_test.go b/bridge/github/config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6c84046a7a1be2a524d1e7bb7a3448ceba5181fa --- /dev/null +++ b/bridge/github/config_test.go @@ -0,0 +1,146 @@ +package github + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSplitURL(t *testing.T) { + type args struct { + url string + } + type want struct { + owner string + project string + err error + } + tests := []struct { + name string + args args + want want + }{ + { + name: "default url", + args: args{ + url: "https://github.com/MichaelMure/git-bug", + }, + want: want{ + owner: "MichaelMure", + project: "git-bug", + err: nil, + }, + }, + + { + name: "default url with git extension", + args: args{ + url: "https://github.com/MichaelMure/git-bug.git", + }, + want: want{ + owner: "MichaelMure", + project: "git-bug", + err: nil, + }, + }, + { + name: "url with git protocol", + args: args{ + url: "git://github.com/MichaelMure/git-bug.git", + }, + want: want{ + owner: "MichaelMure", + project: "git-bug", + err: nil, + }, + }, + { + name: "ssh url", + args: args{ + url: "git@github.com:MichaelMure/git-bug.git", + }, + want: want{ + owner: "MichaelMure", + project: "git-bug", + err: nil, + }, + }, + { + name: "bad url", + args: args{ + url: "https://githb.com/MichaelMure/git-bug.git", + }, + want: want{ + err: ErrBadProjectURL, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + owner, project, err := splitURL(tt.args.url) + assert.Equal(t, tt.want.err, err) + assert.Equal(t, tt.want.owner, owner) + assert.Equal(t, tt.want.project, project) + }) + } +} + +func TestValidateProject(t *testing.T) { + tokenPrivateScope := os.Getenv("GITHUB_TOKEN_PRIVATE") + if tokenPrivateScope == "" { + t.Skip("Env var GITHUB_TOKEN_PRIVATE missing") + } + + tokenPublicScope := os.Getenv("GITHUB_TOKEN_PUBLIC") + if tokenPublicScope == "" { + t.Skip("Env var GITHUB_TOKEN_PUBLIC missing") + } + + type args struct { + owner string + project string + token string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "public repository and token with scope 'public_repo", + args: args{ + project: "git-bug", + owner: "MichaelMure", + token: tokenPublicScope, + }, + want: true, + }, + { + name: "private repository and token with scope 'repo", + args: args{ + project: "git-bug-test-github-bridge", + owner: "MichaelMure", + token: tokenPrivateScope, + }, + want: true, + }, + { + name: "private repository and token with scope 'public_repo'", + args: args{ + project: "git-bug-test-github-bridge", + owner: "MichaelMure", + token: tokenPublicScope, + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ok, _ := validateProject(tt.args.owner, tt.args.project, tt.args.token) + assert.Equal(t, tt.want, ok) + }) + } +} diff --git a/bridge/launchpad/config.go b/bridge/launchpad/config.go index ef206fd5b108408c44b0edd74ce398fd023e0487..1514505f32205599d7d741716f08902830895703 100644 --- a/bridge/launchpad/config.go +++ b/bridge/launchpad/config.go @@ -7,12 +7,16 @@ import ( "os" "regexp" "strings" + "time" "github.com/MichaelMure/git-bug/bridge/core" "github.com/MichaelMure/git-bug/repository" ) -const keyProject = "project" +const ( + keyProject = "project" + defaultTimeout = 60 * time.Second +) var ( rxLaunchpadURL = regexp.MustCompile(`launchpad\.net[\/:]([^\/]*[a-z]+)`) @@ -92,9 +96,10 @@ func promptProjectName() (string, error) { func validateProject(project string) (bool, error) { url := fmt.Sprintf("%s/%s", apiRoot, project) - client := := &http.Client{ + client := &http.Client{ Timeout: defaultTimeout, } + resp, err := client.Get(url) if err != nil { return false, err From d064a7127ec612a804e08143c7098bbc60332e83 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Wed, 29 May 2019 20:50:30 +0200 Subject: [PATCH 18/24] Update man pages and bash_completion --- doc/man/git-bug-bridge-configure.1 | 87 +++++++++++++++++++++++++++++- doc/man/git-bug-bridge-rm.1 | 2 +- doc/md/git-bug_bridge_configure.md | 60 ++++++++++++++++++++- doc/md/git-bug_bridge_rm.md | 2 +- misc/bash_completion/git-bug | 24 +++++++++ 5 files changed, 170 insertions(+), 5 deletions(-) diff --git a/doc/man/git-bug-bridge-configure.1 b/doc/man/git-bug-bridge-configure.1 index 27dfeac0e48bfd9082b2706769efb8f830ebee71..21ea7df1cb1e775e14f0ddc643c35b68159b9421 100644 --- a/doc/man/git-bug-bridge-configure.1 +++ b/doc/man/git-bug-bridge-configure.1 @@ -15,15 +15,100 @@ git\-bug\-bridge\-configure \- Configure a new bridge. .SH DESCRIPTION .PP -Configure a new bridge. +.RS + +.nf +Configure a new bridge by passing flags or/and using interactive terminal prompts. You can avoid all the terminal prompts by passing all the necessary flags to configure your bridge. +Repository configuration can be made by passing either the \-\-url flag or the \-\-project and \-\-owner flags. If the three flags are provided git\-bug will use \-\-project and \-\-owner flags. +Token configuration can be directly passed with the \-\-token flag or in the terminal prompt. If you don't already have one you can use the interactive procedure to generate one. + +.fi +.RE .SH OPTIONS +.PP +\fB\-n\fP, \fB\-\-name\fP="" + A distinctive name to identify the bridge + +.PP +\fB\-t\fP, \fB\-\-target\fP="" + The target of the bridge. Valid values are [github,launchpad\-preview] + +.PP +\fB\-u\fP, \fB\-\-url\fP="" + The URL of the target repository + +.PP +\fB\-o\fP, \fB\-\-owner\fP="" + The owner of the target repository + +.PP +\fB\-T\fP, \fB\-\-token\fP="" + The authentication token for the API + +.PP +\fB\-p\fP, \fB\-\-project\fP="" + The name of the target repository + .PP \fB\-h\fP, \fB\-\-help\fP[=false] help for configure +.SH EXAMPLE +.PP +.RS + +.nf +# Interactive example +[1]: github +[2]: launchpad\-preview +target: 1 +name [default]: default + +Detected projects: +[1]: github.com/a\-hilaly/git\-bug +[2]: github.com/MichaelMure/git\-bug + +[0]: Another project + +Select option: 1 + +[0]: user provided token +[1]: interactive token creation +Select option: 0 + +You can generate a new token by visiting https://github.com/settings/tokens. +Choose 'Generate new token' and set the necessary access scope for your repository. + +The access scope depend on the type of repository. +Public: + \- 'public\_repo': to be able to read public repositories +Private: + \- 'repo' : to be able to read private repositories + +Enter token: +Successfully configured bridge: default + +# For Github +git bug bridge configure \\ + \-\-name=default \\ + \-\-target=github \\ + \-\-owner=$(OWNER) \\ + \-\-project=$(PROJECT) \\ + \-\-token=$(TOKEN) + +# For Launchpad +git bug bridge configure \\ + \-\-name=default \\ + \-\-target=launchpad\-preview \\ + \-\-url=https://bugs.launchpad.net/ubuntu/ + +.fi +.RE + + .SH SEE ALSO .PP \fBgit\-bug\-bridge(1)\fP diff --git a/doc/man/git-bug-bridge-rm.1 b/doc/man/git-bug-bridge-rm.1 index 630eba93c25b9877007651f879bee42d48585179..324d4237d56fe8c3561eb5680c88429373d85900 100644 --- a/doc/man/git-bug-bridge-rm.1 +++ b/doc/man/git-bug-bridge-rm.1 @@ -10,7 +10,7 @@ git\-bug\-bridge\-rm \- Delete a configured bridge. .SH SYNOPSIS .PP -\fBgit\-bug bridge rm name [flags]\fP +\fBgit\-bug bridge rm [flags]\fP .SH DESCRIPTION diff --git a/doc/md/git-bug_bridge_configure.md b/doc/md/git-bug_bridge_configure.md index 63fbbbca1ca6cdc6ab282841c7f27fda10b7dea0..db603b4853eeb247ca6e1173142c075c54ddead8 100644 --- a/doc/md/git-bug_bridge_configure.md +++ b/doc/md/git-bug_bridge_configure.md @@ -4,16 +4,72 @@ Configure a new bridge. ### Synopsis -Configure a new bridge. + Configure a new bridge by passing flags or/and using interactive terminal prompts. You can avoid all the terminal prompts by passing all the necessary flags to configure your bridge. + Repository configuration can be made by passing either the --url flag or the --project and --owner flags. If the three flags are provided git-bug will use --project and --owner flags. + Token configuration can be directly passed with the --token flag or in the terminal prompt. If you don't already have one you can use the interactive procedure to generate one. ``` git-bug bridge configure [flags] ``` +### Examples + +``` +# Interactive example +[1]: github +[2]: launchpad-preview +target: 1 +name [default]: default + +Detected projects: +[1]: github.com/a-hilaly/git-bug +[2]: github.com/MichaelMure/git-bug + +[0]: Another project + +Select option: 1 + +[0]: user provided token +[1]: interactive token creation +Select option: 0 + +You can generate a new token by visiting https://github.com/settings/tokens. +Choose 'Generate new token' and set the necessary access scope for your repository. + +The access scope depend on the type of repository. +Public: + - 'public_repo': to be able to read public repositories +Private: + - 'repo' : to be able to read private repositories + +Enter token: +Successfully configured bridge: default + +# For Github +git bug bridge configure \ + --name=default \ + --target=github \ + --owner=$(OWNER) \ + --project=$(PROJECT) \ + --token=$(TOKEN) + +# For Launchpad +git bug bridge configure \ + --name=default \ + --target=launchpad-preview \ + --url=https://bugs.launchpad.net/ubuntu/ +``` + ### Options ``` - -h, --help help for configure + -n, --name string A distinctive name to identify the bridge + -t, --target string The target of the bridge. Valid values are [github,launchpad-preview] + -u, --url string The URL of the target repository + -o, --owner string The owner of the target repository + -T, --token string The authentication token for the API + -p, --project string The name of the target repository + -h, --help help for configure ``` ### SEE ALSO diff --git a/doc/md/git-bug_bridge_rm.md b/doc/md/git-bug_bridge_rm.md index ab58e96940e341b8e31e878892146d61fd918b2f..54941b2f8fa69cb5bae71f23aabf48ecbeb06b84 100644 --- a/doc/md/git-bug_bridge_rm.md +++ b/doc/md/git-bug_bridge_rm.md @@ -7,7 +7,7 @@ Delete a configured bridge. Delete a configured bridge. ``` -git-bug bridge rm name [flags] +git-bug bridge rm [flags] ``` ### Options diff --git a/misc/bash_completion/git-bug b/misc/bash_completion/git-bug index 51e30da0cd89fd038471bd200a9b34939a58b0b0..741dcc49d203d627010e2707d9696eadf8f7dd02 100644 --- a/misc/bash_completion/git-bug +++ b/misc/bash_completion/git-bug @@ -301,6 +301,30 @@ _git-bug_bridge_configure() flags_with_completion=() flags_completion=() + flags+=("--name=") + two_word_flags+=("--name") + two_word_flags+=("-n") + local_nonpersistent_flags+=("--name=") + flags+=("--target=") + two_word_flags+=("--target") + two_word_flags+=("-t") + local_nonpersistent_flags+=("--target=") + flags+=("--url=") + two_word_flags+=("--url") + two_word_flags+=("-u") + local_nonpersistent_flags+=("--url=") + flags+=("--owner=") + two_word_flags+=("--owner") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--owner=") + flags+=("--token=") + two_word_flags+=("--token") + two_word_flags+=("-T") + local_nonpersistent_flags+=("--token=") + flags+=("--project=") + two_word_flags+=("--project") + two_word_flags+=("-p") + local_nonpersistent_flags+=("--project=") must_have_one_flag=() must_have_one_noun=() From ebebdfdf35b29b37b5a164d73bc00ca1c2b8e895 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Thu, 30 May 2019 12:50:21 +0200 Subject: [PATCH 19/24] add unit tests for launchpad bridge configuration add tests for validateUsername in Github bridge panic when compile regex fail --- bridge/github/config.go | 2 +- bridge/github/config_test.go | 65 +++++++++++++++++++++-- bridge/launchpad/config.go | 23 ++++---- bridge/launchpad/config_test.go | 93 +++++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 13 deletions(-) create mode 100644 bridge/launchpad/config_test.go diff --git a/bridge/github/config.go b/bridge/github/config.go index c48c11f6b3e5856728bca60d93f5ee018bbc5775..4a3bddfb884c21f6fed9223d7a37be0520981768 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -418,7 +418,7 @@ func splitURL(url string) (owner string, project string, err error) { re, err := regexp.Compile(`github\.com[/:]([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_.]+)`) if err != nil { - return "", "", err + panic("regexp compile:" + err.Error()) } res := re.FindStringSubmatch(cleanURL) diff --git a/bridge/github/config_test.go b/bridge/github/config_test.go index 6c84046a7a1be2a524d1e7bb7a3448ceba5181fa..e04f327c79708730ed10a7d1f1c7e7caf847b4b5 100644 --- a/bridge/github/config_test.go +++ b/bridge/github/config_test.go @@ -32,7 +32,17 @@ func TestSplitURL(t *testing.T) { err: nil, }, }, - + { + name: "default issues url", + args: args{ + url: "https://github.com/MichaelMure/git-bug/issues", + }, + want: want{ + owner: "MichaelMure", + project: "git-bug", + err: nil, + }, + }, { name: "default url with git extension", args: args{ @@ -87,6 +97,46 @@ func TestSplitURL(t *testing.T) { } } +func TestValidateUsername(t *testing.T) { + type args struct { + username string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "existing username", + args: args{ + username: "MichaelMure", + }, + want: true, + }, + { + name: "existing organisation name", + args: args{ + username: "ipfs", + }, + want: true, + }, + { + name: "non existing username", + args: args{ + username: "cant-find-this", + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ok, _ := validateUsername(tt.args.username) + assert.Equal(t, tt.want, ok) + }) + } +} + func TestValidateProject(t *testing.T) { tokenPrivateScope := os.Getenv("GITHUB_TOKEN_PRIVATE") if tokenPrivateScope == "" { @@ -109,7 +159,7 @@ func TestValidateProject(t *testing.T) { want bool }{ { - name: "public repository and token with scope 'public_repo", + name: "public repository and token with scope 'public_repo'", args: args{ project: "git-bug", owner: "MichaelMure", @@ -118,7 +168,7 @@ func TestValidateProject(t *testing.T) { want: true, }, { - name: "private repository and token with scope 'repo", + name: "private repository and token with scope 'repo'", args: args{ project: "git-bug-test-github-bridge", owner: "MichaelMure", @@ -135,6 +185,15 @@ func TestValidateProject(t *testing.T) { }, want: false, }, + { + name: "project not existing", + args: args{ + project: "cant-find-this", + owner: "organisation-not-found", + token: tokenPublicScope, + }, + want: false, + }, } for _, tt := range tests { diff --git a/bridge/launchpad/config.go b/bridge/launchpad/config.go index 1514505f32205599d7d741716f08902830895703..d8efea46662ddf9afbb32dea1122d4b1f3af52e9 100644 --- a/bridge/launchpad/config.go +++ b/bridge/launchpad/config.go @@ -2,6 +2,7 @@ package launchpad import ( "bufio" + "errors" "fmt" "net/http" "os" @@ -13,15 +14,13 @@ import ( "github.com/MichaelMure/git-bug/repository" ) +var ErrBadProjectURL = errors.New("bad Launchpad project URL") + const ( keyProject = "project" defaultTimeout = 60 * time.Second ) -var ( - rxLaunchpadURL = regexp.MustCompile(`launchpad\.net[\/:]([^\/]*[a-z]+)`) -) - func (*Launchpad) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) { if params.Token != "" { fmt.Println("warning: --token is ineffective for a Launchpad bridge") @@ -39,7 +38,7 @@ func (*Launchpad) Configure(repo repository.RepoCommon, params core.BridgeParams } else if params.URL != "" { // get project name from url - _, project, err = splitURL(params.URL) + project, err = splitURL(params.URL) if err != nil { return nil, err } @@ -108,11 +107,17 @@ func validateProject(project string) (bool, error) { return resp.StatusCode == http.StatusOK, nil } -func splitURL(url string) (string, string, error) { - res := rxLaunchpadURL.FindStringSubmatch(url) +// extract project name from url +func splitURL(url string) (string, error) { + re, err := regexp.Compile(`launchpad\.net[\/:]([^\/]*[a-z]+)`) + if err != nil { + panic("regexp compile:" + err.Error()) + } + + res := re.FindStringSubmatch(url) if res == nil { - return "", "", fmt.Errorf("bad Launchpad project url") + return "", ErrBadProjectURL } - return res[0], res[1], nil + return res[1], nil } diff --git a/bridge/launchpad/config_test.go b/bridge/launchpad/config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..275c0d242a00d37a78079ea24501995ad011f491 --- /dev/null +++ b/bridge/launchpad/config_test.go @@ -0,0 +1,93 @@ +package launchpad + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSplitURL(t *testing.T) { + type args struct { + url string + } + type want struct { + project string + err error + } + tests := []struct { + name string + args args + want want + }{ + { + name: "default project url", + args: args{ + url: "https://launchpad.net/ubuntu", + }, + want: want{ + project: "ubuntu", + err: nil, + }, + }, + { + name: "project bugs url", + args: args{ + url: "https://bugs.launchpad.net/ubuntu", + }, + want: want{ + project: "ubuntu", + err: nil, + }, + }, + { + name: "bad url", + args: args{ + url: "https://launchpa.net/ubuntu", + }, + want: want{ + err: ErrBadProjectURL, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + project, err := splitURL(tt.args.url) + assert.Equal(t, tt.want.err, err) + assert.Equal(t, tt.want.project, project) + }) + } +} + +func TestValidateProject(t *testing.T) { + type args struct { + project string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "public project", + args: args{ + project: "ubuntu", + }, + want: true, + }, + { + name: "non existing project", + args: args{ + project: "cant-find-this", + }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ok, _ := validateProject(tt.args.project) + assert.Equal(t, tt.want, ok) + }) + } +} From 7923d6c942114b710089c2b2e7f49bc7ac374b44 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Fri, 31 May 2019 00:33:30 +0200 Subject: [PATCH 20/24] Skip test validateUsername in travis environment --- bridge/github/config_test.go | 4 ++++ misc/zsh_completion/git-bug | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bridge/github/config_test.go b/bridge/github/config_test.go index e04f327c79708730ed10a7d1f1c7e7caf847b4b5..4feeaa7408cfe6983de6ff3688081b94ebaf01eb 100644 --- a/bridge/github/config_test.go +++ b/bridge/github/config_test.go @@ -98,6 +98,10 @@ func TestSplitURL(t *testing.T) { } func TestValidateUsername(t *testing.T) { + if env := os.Getenv("TRAVIS"); env == "true" { + t.Skip("Travis environment: avoiding non authenticated requests") + } + type args struct { username string } diff --git a/misc/zsh_completion/git-bug b/misc/zsh_completion/git-bug index 52c242dfbaef7ab86f380f6f580eed300f8ad8f7..c2ed9872aae98af3cce28fd78516939abc6c33bd 100644 --- a/misc/zsh_completion/git-bug +++ b/misc/zsh_completion/git-bug @@ -8,7 +8,7 @@ case $state in level1) case $words[1] in git-bug) - _arguments '1: :(add bridge commands comment deselect export label ls ls-id ls-label pull push select show status termui title user version webui)' + _arguments '1: :(add bridge commands comment deselect label ls ls-id ls-label pull push select show status termui title user version webui)' ;; *) _arguments '*: :_files' From c52a467302608012c4fd7d496a7cd60ad153b5cd Mon Sep 17 00:00:00 2001 From: Amine Date: Wed, 5 Jun 2019 00:45:34 +0200 Subject: [PATCH 21/24] Allow cancelling configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit correct error message Co-Authored-By: Michael Muré --- bridge/github/config.go | 4 ++-- commands/bridge_configure.go | 7 +++++++ commands/bridge_rm.go | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/bridge/github/config.go b/bridge/github/config.go index 4a3bddfb884c21f6fed9223d7a37be0520981768..b54688466a6bdc9ca7c784acacdf14fc05619a10 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -98,7 +98,7 @@ func (*Github) Configure(repo repository.RepoCommon, params core.BridgeParams) ( return nil, err } if !ok { - return nil, fmt.Errorf("project doesn't exist or authentication token has a wrong scope") + return nil, fmt.Errorf("project doesn't exist or authentication token has an incorrect scope") } conf[keyToken] = token @@ -428,7 +428,7 @@ func splitURL(url string) (owner string, project string, err error) { owner = res[1] project = res[2] - return owner, project, nil + return } func getValidGithubRemoteURLs(remotes map[string]string) []string { diff --git a/commands/bridge_configure.go b/commands/bridge_configure.go index c9ac168025362fc1a29bfabdc1e2128be5f04a49..169919f64ef812caa80641fbd38080d70cb1f42b 100644 --- a/commands/bridge_configure.go +++ b/commands/bridge_configure.go @@ -33,6 +33,13 @@ func runBridgeConfigure(cmd *cobra.Command, args []string) error { defer backend.Close() interrupt.RegisterCleaner(backend.Close) + termState, err := terminal.GetState(int(syscall.Stdin)) + if err != nil { + return err + } + interrupt.RegisterCleaner(func() error { + return terminal.Restore(int(syscall.Stdin), termState) + }) if bridgeConfigureTarget == "" { bridgeConfigureTarget, err = promptTarget() if err != nil { diff --git a/commands/bridge_rm.go b/commands/bridge_rm.go index 1b67608b0952b9903813c5134544301a9ed83188..80a831ff3e626479345625f472adb4626333154d 100644 --- a/commands/bridge_rm.go +++ b/commands/bridge_rm.go @@ -24,7 +24,7 @@ func runBridgeRm(cmd *cobra.Command, args []string) error { } var bridgeRmCmd = &cobra.Command{ - Use: "rm ", + Use: "rm name ", Short: "Delete a configured bridge.", PreRunE: loadRepo, RunE: runBridgeRm, From a6c8b6b78dcf9b31c73fe35545e3fb754e06851a Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Wed, 5 Jun 2019 01:42:36 +0200 Subject: [PATCH 22/24] make token visible in configuration process validate token global fixes --- bridge/github/config.go | 28 ++++++++++++++++------------ commands/bridge_configure.go | 19 ++++++++++++------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/bridge/github/config.go b/bridge/github/config.go index b54688466a6bdc9ca7c784acacdf14fc05619a10..707b3e2fdb5a3d2b5b4bb2c428a41c56f7184ef2 100644 --- a/bridge/github/config.go +++ b/bridge/github/config.go @@ -197,8 +197,8 @@ func randomFingerprint() string { func promptTokenOptions(owner, project string) (string, error) { for { fmt.Println() - fmt.Println("[0]: user provided token") - fmt.Println("[1]: interactive token creation") + fmt.Println("[1]: user provided token") + fmt.Println("[2]: interactive token creation") fmt.Print("Select option: ") line, err := bufio.NewReader(os.Stdin).ReadString('\n') @@ -210,12 +210,12 @@ func promptTokenOptions(owner, project string) (string, error) { line = strings.TrimRight(line, "\n") index, err := strconv.Atoi(line) - if err != nil || (index != 0 && index != 1) { + if err != nil || (index != 1 && index != 2) { fmt.Println("invalid input") continue } - if index == 0 { + if index == 1 { return promptToken() } @@ -234,21 +234,25 @@ func promptToken() (string, error) { fmt.Println(" - 'repo' : to be able to read private repositories") fmt.Println() + re, err := regexp.Compile(`^[a-zA-Z0-9]{40}`) + if err != nil { + panic("regexp compile:" + err.Error()) + } + for { fmt.Print("Enter token: ") - byteToken, err := terminal.ReadPassword(int(syscall.Stdin)) - fmt.Println() - + line, err := bufio.NewReader(os.Stdin).ReadString('\n') if err != nil { return "", err } - if len(byteToken) > 0 { - return string(byteToken), nil + token := strings.TrimRight(line, "\n") + if re.MatchString(token) { + return token, nil } - fmt.Println("token is empty") + fmt.Println("token is invalid") } } @@ -534,8 +538,8 @@ func prompt2FA() (string, error) { func promptProjectVisibility() (bool, error) { for { - fmt.Println("[0]: public") - fmt.Println("[1]: private") + fmt.Println("[1]: public") + fmt.Println("[2]: private") fmt.Print("repository visibility: ") line, err := bufio.NewReader(os.Stdin).ReadString('\n') diff --git a/commands/bridge_configure.go b/commands/bridge_configure.go index 169919f64ef812caa80641fbd38080d70cb1f42b..cecfe139272aa1128729ac05cf3d588389bbffb4 100644 --- a/commands/bridge_configure.go +++ b/commands/bridge_configure.go @@ -6,13 +6,15 @@ import ( "os" "strconv" "strings" + "syscall" - "github.com/MichaelMure/git-bug/bridge/core" + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" "github.com/MichaelMure/git-bug/bridge" + "github.com/MichaelMure/git-bug/bridge/core" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) const ( @@ -37,9 +39,11 @@ func runBridgeConfigure(cmd *cobra.Command, args []string) error { if err != nil { return err } + interrupt.RegisterCleaner(func() error { return terminal.Restore(int(syscall.Stdin), termState) }) + if bridgeConfigureTarget == "" { bridgeConfigureTarget, err = promptTarget() if err != nil { @@ -78,8 +82,9 @@ func promptTarget() (string, error) { fmt.Printf("target: ") line, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { - return "", err + return "", fmt.Errorf("got err: '%v' '%v'", line, err) } line = strings.TrimRight(line, "\n") @@ -131,9 +136,9 @@ Detected projects: Select option: 1 -[0]: user provided token -[1]: interactive token creation -Select option: 0 +[1]: user provided token +[2]: interactive token creation +Select option: 1 You can generate a new token by visiting https://github.com/settings/tokens. Choose 'Generate new token' and set the necessary access scope for your repository. @@ -144,7 +149,7 @@ Public: Private: - 'repo' : to be able to read private repositories -Enter token: +Enter token: 87cf5c03b64029f18ea5f9ca5679daa08ccbd700 Successfully configured bridge: default # For Github From 8d6bdb65074f9e75c014b86dd744b7bba72b349b Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Fri, 7 Jun 2019 01:28:52 +0200 Subject: [PATCH 23/24] rename GITHUB_TOKEN to GITHUB_PRIVATE_TOKEN --- bridge/github/import_test.go | 4 ++-- commands/bridge_configure.go | 2 +- doc/man/git-bug-bridge-configure.1 | 8 ++++---- doc/man/git-bug-bridge-rm.1 | 2 +- doc/md/git-bug_bridge_configure.md | 8 ++++---- doc/md/git-bug_bridge_rm.md | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bridge/github/import_test.go b/bridge/github/import_test.go index 9704e56f1cf51bb0b1dae26d889e2b80cacbaff9..24356f344496a1ca9f246e4071a711851c2a9db2 100644 --- a/bridge/github/import_test.go +++ b/bridge/github/import_test.go @@ -133,9 +133,9 @@ func Test_Importer(t *testing.T) { defer backend.Close() interrupt.RegisterCleaner(backend.Close) - token := os.Getenv("GITHUB_TOKEN") + token := os.Getenv("GITHUB_TOKEN_PRIVATE") if token == "" { - t.Skip("Env var GITHUB_TOKEN missing") + t.Skip("Env var GITHUB_TOKEN_PRIVATE missing") } importer := &githubImporter{} diff --git a/commands/bridge_configure.go b/commands/bridge_configure.go index cecfe139272aa1128729ac05cf3d588389bbffb4..61d969d1d9d50de8199a70efb221c02769cbca48 100644 --- a/commands/bridge_configure.go +++ b/commands/bridge_configure.go @@ -84,7 +84,7 @@ func promptTarget() (string, error) { line, err := bufio.NewReader(os.Stdin).ReadString('\n') if err != nil { - return "", fmt.Errorf("got err: '%v' '%v'", line, err) + return "", err } line = strings.TrimRight(line, "\n") diff --git a/doc/man/git-bug-bridge-configure.1 b/doc/man/git-bug-bridge-configure.1 index 21ea7df1cb1e775e14f0ddc643c35b68159b9421..fa6feed5bdef029cce656d16b2a3713fa82c8afd 100644 --- a/doc/man/git-bug-bridge-configure.1 +++ b/doc/man/git-bug-bridge-configure.1 @@ -75,9 +75,9 @@ Detected projects: Select option: 1 -[0]: user provided token -[1]: interactive token creation -Select option: 0 +[1]: user provided token +[2]: interactive token creation +Select option: 1 You can generate a new token by visiting https://github.com/settings/tokens. Choose 'Generate new token' and set the necessary access scope for your repository. @@ -88,7 +88,7 @@ Public: Private: \- 'repo' : to be able to read private repositories -Enter token: +Enter token: 87cf5c03b64029f18ea5f9ca5679daa08ccbd700 Successfully configured bridge: default # For Github diff --git a/doc/man/git-bug-bridge-rm.1 b/doc/man/git-bug-bridge-rm.1 index 324d4237d56fe8c3561eb5680c88429373d85900..630eba93c25b9877007651f879bee42d48585179 100644 --- a/doc/man/git-bug-bridge-rm.1 +++ b/doc/man/git-bug-bridge-rm.1 @@ -10,7 +10,7 @@ git\-bug\-bridge\-rm \- Delete a configured bridge. .SH SYNOPSIS .PP -\fBgit\-bug bridge rm [flags]\fP +\fBgit\-bug bridge rm name [flags]\fP .SH DESCRIPTION diff --git a/doc/md/git-bug_bridge_configure.md b/doc/md/git-bug_bridge_configure.md index db603b4853eeb247ca6e1173142c075c54ddead8..788b39862091ce9be9585878894a0f9794609af2 100644 --- a/doc/md/git-bug_bridge_configure.md +++ b/doc/md/git-bug_bridge_configure.md @@ -29,9 +29,9 @@ Detected projects: Select option: 1 -[0]: user provided token -[1]: interactive token creation -Select option: 0 +[1]: user provided token +[2]: interactive token creation +Select option: 1 You can generate a new token by visiting https://github.com/settings/tokens. Choose 'Generate new token' and set the necessary access scope for your repository. @@ -42,7 +42,7 @@ Public: Private: - 'repo' : to be able to read private repositories -Enter token: +Enter token: 87cf5c03b64029f18ea5f9ca5679daa08ccbd700 Successfully configured bridge: default # For Github diff --git a/doc/md/git-bug_bridge_rm.md b/doc/md/git-bug_bridge_rm.md index 54941b2f8fa69cb5bae71f23aabf48ecbeb06b84..ab58e96940e341b8e31e878892146d61fd918b2f 100644 --- a/doc/md/git-bug_bridge_rm.md +++ b/doc/md/git-bug_bridge_rm.md @@ -7,7 +7,7 @@ Delete a configured bridge. Delete a configured bridge. ``` -git-bug bridge rm [flags] +git-bug bridge rm name [flags] ``` ### Options From 1c2ad95960c09d029e6306ac5a5ea76c58e8b5c9 Mon Sep 17 00:00:00 2001 From: Amine Hilaly Date: Fri, 7 Jun 2019 02:35:40 +0200 Subject: [PATCH 24/24] add verbose flag to go test --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 94a820d5c6e6f3061275ae1e433361f6d8acaa87..f4e76f4cb577f1b2e92724f6eacf9d859ee1c617 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ install: go install -ldflags "$(LDFLAGS)" . test: - go test -bench=. ./... + go test -v -bench=. ./... pack-webui: npm run --prefix webui build