gitlab also compile

Michael Muré created

Change summary

bridge/github/config.go      |   2 
bridge/github/export.go      |   5 
bridge/github/export_test.go |   3 
bridge/github/import_test.go |   5 +
bridge/gitlab/config.go      | 159 ++++++++++++++++---------------------
bridge/gitlab/export.go      |  24 ++++-
bridge/gitlab/export_test.go |   5 
bridge/gitlab/import.go      |  17 ---
bridge/gitlab/import_test.go |  11 +-
bridge/launchpad/config.go   |  26 -----
10 files changed, 113 insertions(+), 144 deletions(-)

Detailed changes

bridge/github/config.go 🔗

@@ -316,7 +316,7 @@ 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}`)
+	re, err := regexp.Compile(`^[a-zA-Z0-9]{40}$`)
 	if err != nil {
 		panic("regexp compile:" + err.Error())
 	}

bridge/github/export.go 🔗

@@ -99,7 +99,7 @@ func (ge *githubExporter) cacheAllClient(repo *cache.RepoCache) error {
 	for _, cred := range creds {
 		login, ok := cred.GetMetadata(auth.MetaKeyLogin)
 		if !ok {
-			_, _ = fmt.Fprintf(os.Stderr, "credential %s is not tagged with Github login\n", cred.ID().Human())
+			_, _ = fmt.Fprintf(os.Stderr, "credential %s is not tagged with a Github login\n", cred.ID().Human())
 			continue
 		}
 
@@ -107,6 +107,9 @@ func (ge *githubExporter) cacheAllClient(repo *cache.RepoCache) error {
 		if err == identity.ErrIdentityNotExist {
 			continue
 		}
+		if err != nil {
+			return nil
+		}
 
 		if _, ok := ge.identityClient[user.Id()]; !ok {
 			client := buildClient(creds[0].(*auth.Token))

bridge/github/export_test.go 🔗

@@ -144,8 +144,10 @@ func TestPushPull(t *testing.T) {
 	require.NoError(t, err)
 
 	// set author identity
+	login := "identity-test"
 	author, err := backend.NewIdentity("test identity", "test@test.org")
 	require.NoError(t, err)
+	author.SetMetadata(metaKeyGithubLogin, login)
 
 	err = backend.SetUserIdentity(author)
 	require.NoError(t, err)
@@ -177,6 +179,7 @@ func TestPushPull(t *testing.T) {
 	})
 
 	token := auth.NewToken(envToken, target)
+	token.SetMetadata(metaKeyGithubLogin, login)
 	err = auth.Store(repo, token)
 	require.NoError(t, err)
 

bridge/github/import_test.go 🔗

@@ -21,6 +21,7 @@ import (
 
 func Test_Importer(t *testing.T) {
 	author := identity.NewIdentity("Michael Muré", "batolettre@gmail.com")
+
 	tests := []struct {
 		name string
 		url  string
@@ -140,7 +141,11 @@ func Test_Importer(t *testing.T) {
 		t.Skip("Env var GITHUB_TOKEN_PRIVATE missing")
 	}
 
+	login := "test-identity"
+	author.SetMetadata(metaKeyGithubLogin, login)
+
 	token := auth.NewToken(envToken, target)
+	token.SetMetadata(metaKeyGithubLogin, login)
 	err = auth.Store(repo, token)
 	require.NoError(t, err)
 

bridge/gitlab/config.go 🔗

@@ -19,8 +19,7 @@ import (
 	"github.com/MichaelMure/git-bug/bridge/core"
 	"github.com/MichaelMure/git-bug/bridge/core/auth"
 	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/entity"
-	"github.com/MichaelMure/git-bug/identity"
+	"github.com/MichaelMure/git-bug/input"
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/colors"
 )
@@ -36,14 +35,12 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor
 	if params.Owner != "" {
 		fmt.Println("warning: --owner is ineffective for a gitlab bridge")
 	}
+	if params.Login != "" {
+		fmt.Println("warning: --login is ineffective for a gitlab bridge")
+	}
 
 	conf := make(core.Configuration)
 	var err error
-
-	if (params.CredPrefix != "" || params.TokenRaw != "") && params.URL == "" {
-		return nil, fmt.Errorf("you must provide a project URL to configure this bridge with a token")
-	}
-
 	var baseUrl string
 
 	switch {
@@ -74,17 +71,6 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor
 		return nil, fmt.Errorf("base URL (%s) doesn't match the project URL (%s)", params.BaseURL, url)
 	}
 
-	user, err := repo.GetUserIdentity()
-	if err != nil && err != identity.ErrNoIdentitySet {
-		return nil, err
-	}
-
-	// default to a "to be filled" user Id if we don't have a valid one yet
-	userId := auth.DefaultUserId
-	if user != nil {
-		userId = user.Id()
-	}
-
 	var cred auth.Credential
 
 	switch {
@@ -93,13 +79,16 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor
 		if err != nil {
 			return nil, err
 		}
-		if user != nil && cred.UserId() != user.Id() {
-			return nil, fmt.Errorf("selected credential don't match the user")
-		}
 	case params.TokenRaw != "":
-		cred = auth.NewToken(userId, params.TokenRaw, target)
+		token := auth.NewToken(params.TokenRaw, target)
+		login, err := getLoginFromToken(baseUrl, token)
+		if err != nil {
+			return nil, err
+		}
+		token.SetMetadata(auth.MetaKeyLogin, login)
+		cred = token
 	default:
-		cred, err = promptTokenOptions(repo, userId, baseUrl)
+		cred, err = promptTokenOptions(repo, baseUrl)
 		if err != nil {
 			return nil, err
 		}
@@ -153,64 +142,41 @@ func (g *Gitlab) ValidateConfig(conf core.Configuration) error {
 }
 
 func promptBaseUrlOptions() (string, error) {
-	for {
-		fmt.Printf("Gitlab base url:\n")
-		fmt.Printf("[0]: https://gitlab.com\n")
-		fmt.Printf("[1]: enter your own base url\n")
-		fmt.Printf("Select option: ")
-
-		line, err := bufio.NewReader(os.Stdin).ReadString('\n')
-		if err != nil {
-			return "", err
-		}
-
-		line = strings.TrimSpace(line)
+	index, err := input.PromptChoice("Gitlab base url", []string{
+		"https://gitlab.com",
+		"enter your own base url",
+	})
 
-		index, err := strconv.Atoi(line)
-		if err != nil || index < 0 || index > 1 {
-			fmt.Println("invalid input")
-			continue
-		}
+	if err != nil {
+		return "", err
+	}
 
-		switch index {
-		case 0:
-			return defaultBaseURL, nil
-		case 1:
-			return promptBaseUrl()
-		}
+	if index == 0 {
+		return defaultBaseURL, nil
+	} else {
+		return promptBaseUrl()
 	}
 }
 
 func promptBaseUrl() (string, error) {
-	for {
-		fmt.Print("Base url: ")
-
-		line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+	validator := func(name string, value string) (string, error) {
+		u, err := url.Parse(value)
 		if err != nil {
-			return "", err
+			return err.Error(), nil
 		}
-
-		line = strings.TrimSpace(line)
-
-		ok, err := validateBaseUrl(line)
-		if err != nil {
-			return "", err
+		if u.Scheme == "" {
+			return "missing scheme", nil
 		}
-		if ok {
-			return line, nil
+		if u.Host == "" {
+			return "missing host", nil
 		}
+		return "", nil
 	}
-}
 
-func validateBaseUrl(baseUrl string) (bool, error) {
-	u, err := url.Parse(baseUrl)
-	if err != nil {
-		return false, err
-	}
-	return u.Scheme != "" && u.Host != "", nil
+	return input.Prompt("Base url", "url", input.Required, validator)
 }
 
-func promptTokenOptions(repo repository.RepoConfig, userId entity.Id, baseUrl string) (auth.Credential, error) {
+func promptTokenOptions(repo repository.RepoConfig, baseUrl string) (auth.Credential, error) {
 	for {
 		creds, err := auth.List(repo, auth.WithTarget(target), auth.WithKind(auth.KindToken))
 		if err != nil {
@@ -219,11 +185,7 @@ func promptTokenOptions(repo repository.RepoConfig, userId entity.Id, baseUrl st
 
 		// if we don't have existing token, fast-track to the token prompt
 		if len(creds) == 0 {
-			value, err := promptToken(baseUrl)
-			if err != nil {
-				return nil, err
-			}
-			return auth.NewToken(userId, value, target), nil
+			return promptToken(baseUrl)
 		}
 
 		fmt.Println()
@@ -261,44 +223,44 @@ func promptTokenOptions(repo repository.RepoConfig, userId entity.Id, baseUrl st
 
 		switch index {
 		case 1:
-			value, err := promptToken(baseUrl)
-			if err != nil {
-				return nil, err
-			}
-			return auth.NewToken(userId, value, target), nil
+			return promptToken(baseUrl)
 		default:
 			return creds[index-2], nil
 		}
 	}
 }
 
-func promptToken(baseUrl string) (string, error) {
+func promptToken(baseUrl string) (*auth.Token, error) {
 	fmt.Printf("You can generate a new token by visiting %s.\n", path.Join(baseUrl, "profile/personal_access_tokens"))
 	fmt.Println("Choose 'Create personal access token' and set the necessary access scope for your repository.")
 	fmt.Println()
 	fmt.Println("'api' access scope: to be able to make api calls")
 	fmt.Println()
 
-	re, err := regexp.Compile(`^[a-zA-Z0-9\-\_]{20}`)
+	re, err := regexp.Compile(`^[a-zA-Z0-9\-\_]{20}$`)
 	if err != nil {
 		panic("regexp compile:" + err.Error())
 	}
 
-	for {
-		fmt.Print("Enter token: ")
+	var login string
 
-		line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+	validator := func(name string, value string) (complaint string, err error) {
+		if !re.MatchString(value) {
+			return "token has incorrect format", nil
+		}
+		login, err = getLoginFromToken(baseUrl, auth.NewToken(value, target))
 		if err != nil {
-			return "", err
+			return fmt.Sprintf("token is invalid: %v", err), nil
 		}
+		return "", nil
+	}
 
-		token := strings.TrimSpace(line)
-		if re.MatchString(token) {
-			return token, nil
-		}
+	rawToken, err := input.Prompt("Enter token", "token", input.Required, validator)
 
-		fmt.Println("token has incorrect format")
-	}
+	token := auth.NewToken(rawToken, target)
+	token.SetMetadata(auth.MetaKeyLogin, login)
+
+	return token, nil
 }
 
 func promptURL(repo repository.RepoCommon, baseUrl string) (string, error) {
@@ -408,8 +370,25 @@ func validateProjectURL(baseUrl, url string, token *auth.Token) (int, error) {
 
 	project, _, err := client.Projects.GetProject(projectPath, &gitlab.GetProjectOptions{})
 	if err != nil {
-		return 0, errors.Wrap(err, "wrong token scope ou inexistent project")
+		return 0, errors.Wrap(err, "wrong token scope ou non-existent project")
 	}
 
 	return project.ID, nil
 }
+
+func getLoginFromToken(baseUrl string, token *auth.Token) (string, error) {
+	client, err := buildClient(baseUrl, token)
+	if err != nil {
+		return "", err
+	}
+
+	user, _, err := client.Users.CurrentUser()
+	if err != nil {
+		return "", err
+	}
+	if user.Username == "" {
+		return "", fmt.Errorf("gitlab say username is empty")
+	}
+
+	return user.Username, nil
+}

bridge/gitlab/export.go 🔗

@@ -3,6 +3,7 @@ package gitlab
 import (
 	"context"
 	"fmt"
+	"os"
 	"strconv"
 	"time"
 
@@ -14,7 +15,7 @@ import (
 	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/entity"
-	"github.com/MichaelMure/git-bug/repository"
+	"github.com/MichaelMure/git-bug/identity"
 )
 
 var (
@@ -54,20 +55,33 @@ func (ge *gitlabExporter) Init(repo *cache.RepoCache, conf core.Configuration) e
 	return nil
 }
 
-func (ge *gitlabExporter) cacheAllClient(repo repository.RepoConfig) error {
+func (ge *gitlabExporter) cacheAllClient(repo *cache.RepoCache) error {
 	creds, err := auth.List(repo, auth.WithTarget(target), auth.WithKind(auth.KindToken))
 	if err != nil {
 		return err
 	}
 
 	for _, cred := range creds {
-		if _, ok := ge.identityClient[cred.UserId()]; !ok {
+		login, ok := cred.GetMetadata(auth.MetaKeyLogin)
+		if !ok {
+			_, _ = fmt.Fprintf(os.Stderr, "credential %s is not tagged with a Gitlab login\n", cred.ID().Human())
+			continue
+		}
+
+		user, err := repo.ResolveIdentityImmutableMetadata(metaKeyGitlabLogin, login)
+		if err == identity.ErrIdentityNotExist {
+			continue
+		}
+		if err != nil {
+			return nil
+		}
+
+		if _, ok := ge.identityClient[user.Id()]; !ok {
 			client, err := buildClient(ge.conf[keyGitlabBaseUrl], creds[0].(*auth.Token))
 			if err != nil {
 				return err
 			}
-
-			ge.identityClient[cred.UserId()] = client
+			ge.identityClient[user.Id()] = client
 		}
 	}
 

bridge/gitlab/export_test.go 🔗

@@ -149,8 +149,10 @@ func TestPushPull(t *testing.T) {
 	require.NoError(t, err)
 
 	// set author identity
+	login := "test-identity"
 	author, err := backend.NewIdentity("test identity", "test@test.org")
 	require.NoError(t, err)
+	author.SetMetadata(metaKeyGitlabLogin, login)
 
 	err = backend.SetUserIdentity(author)
 	require.NoError(t, err)
@@ -160,7 +162,8 @@ func TestPushPull(t *testing.T) {
 
 	tests := testCases(t, backend)
 
-	token := auth.NewToken(author.Id(), envToken, target)
+	token := auth.NewToken(envToken, target)
+	token.SetMetadata(metaKeyGitlabLogin, login)
 	err = auth.Store(repo, token)
 	require.NoError(t, err)
 

bridge/gitlab/import.go 🔗

@@ -13,7 +13,6 @@ import (
 	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/entity"
-	"github.com/MichaelMure/git-bug/identity"
 	"github.com/MichaelMure/git-bug/util/text"
 )
 
@@ -34,20 +33,7 @@ type gitlabImporter struct {
 func (gi *gitlabImporter) Init(repo *cache.RepoCache, conf core.Configuration) error {
 	gi.conf = conf
 
-	opts := []auth.Option{
-		auth.WithTarget(target),
-		auth.WithKind(auth.KindToken),
-	}
-
-	user, err := repo.GetUserIdentity()
-	if err == nil {
-		opts = append(opts, auth.WithUserId(user.Id()))
-	}
-	if err == identity.ErrNoIdentitySet {
-		opts = append(opts, auth.WithUserId(auth.DefaultUserId))
-	}
-
-	creds, err := auth.List(repo, opts...)
+	creds, err := auth.List(repo, auth.WithTarget(target), auth.WithKind(auth.KindToken))
 	if err != nil {
 		return err
 	}
@@ -403,7 +389,6 @@ func (gi *gitlabImporter) ensurePerson(repo *cache.RepoCache, id int) (*cache.Id
 	i, err = repo.NewIdentityRaw(
 		user.Name,
 		user.PublicEmail,
-		user.Username,
 		user.AvatarURL,
 		map[string]string{
 			// because Gitlab

bridge/gitlab/import_test.go 🔗

@@ -21,6 +21,7 @@ import (
 
 func TestImport(t *testing.T) {
 	author := identity.NewIdentity("Amine Hilaly", "hilalyamine@gmail.com")
+
 	tests := []struct {
 		name string
 		url  string
@@ -94,13 +95,11 @@ func TestImport(t *testing.T) {
 		t.Skip("Env var GITLAB_PROJECT_ID missing")
 	}
 
-	err = author.Commit(repo)
-	require.NoError(t, err)
-
-	err = identity.SetUserIdentity(repo, author)
-	require.NoError(t, err)
+	login := "test-identity"
+	author.SetMetadata(metaKeyGitlabLogin, login)
 
-	token := auth.NewToken(author.Id(), envToken, target)
+	token := auth.NewToken(envToken, target)
+	token.SetMetadata(metaKeyGitlabLogin, login)
 	err = auth.Store(repo, token)
 	require.NoError(t, err)
 

bridge/launchpad/config.go 🔗

@@ -1,17 +1,15 @@
 package launchpad
 
 import (
-	"bufio"
 	"errors"
 	"fmt"
 	"net/http"
-	"os"
 	"regexp"
-	"strings"
 	"time"
 
 	"github.com/MichaelMure/git-bug/bridge/core"
 	"github.com/MichaelMure/git-bug/cache"
+	"github.com/MichaelMure/git-bug/input"
 )
 
 var ErrBadProjectURL = errors.New("bad Launchpad project URL")
@@ -45,7 +43,7 @@ func (l *Launchpad) Configure(repo *cache.RepoCache, params core.BridgeParams) (
 		project, err = splitURL(params.URL)
 	default:
 		// get project name from terminal prompt
-		project, err = promptProjectName()
+		project, err = input.Prompt("Launchpad project name", "project name", input.Required)
 	}
 
 	if err != nil {
@@ -86,26 +84,6 @@ func (*Launchpad) ValidateConfig(conf core.Configuration) error {
 	return nil
 }
 
-func promptProjectName() (string, error) {
-	for {
-		fmt.Print("Launchpad project name: ")
-
-		line, err := bufio.NewReader(os.Stdin).ReadString('\n')
-		if err != nil {
-			return "", err
-		}
-
-		line = strings.TrimRight(line, "\n")
-
-		if line == "" {
-			fmt.Println("Project name is empty")
-			continue
-		}
-
-		return line, nil
-	}
-}
-
 func validateProject(project string) (bool, error) {
 	url := fmt.Sprintf("%s/%s", apiRoot, project)