it compiles \o/

Michael Muré created

Change summary

bridge/bridges.go                       |  7 +++
bridge/core/bridge.go                   | 17 +++++++++
bridge/core/interfaces.go               |  5 ++
bridge/github/config.go                 |  9 ----
bridge/github/export.go                 |  2 
bridge/github/export_test.go            |  2 
bridge/github/github.go                 | 21 +++++++++++
bridge/github/import.go                 |  6 ---
bridge/github/import_test.go            |  2 
bridge/gitlab/gitlab.go                 |  6 +++
bridge/launchpad/config.go              |  7 ---
bridge/launchpad/import.go              |  5 --
bridge/launchpad/launchpad.go           | 19 ++++++++++
commands/bridge_auth.go                 | 26 ++++---------
commands/bridge_auth_addtoken.go        | 51 ++++++++++++++++++++++++--
commands/bridge_auth_show.go            | 32 ++++++----------
commands/user.go                        |  3 -
commands/user_adopt.go                  | 15 +------
commands/user_create.go                 |  8 ++--
doc/man/git-bug-bridge-auth-add-token.1 |  8 ++++
doc/md/git-bug_bridge_auth_add-token.md |  2 +
misc/bash_completion/git-bug            |  8 ++++
misc/powershell_completion/git-bug      |  4 ++
misc/zsh_completion/git-bug             |  4 +
24 files changed, 175 insertions(+), 94 deletions(-)

Detailed changes

bridge/bridges.go 🔗

@@ -21,6 +21,13 @@ func Targets() []string {
 	return core.Targets()
 }
 
+// LoginMetaKey return the metadata key used to store the remote bug-tracker login
+// on the user identity. The corresponding value is used to match identities and
+// credentials.
+func LoginMetaKey(target string) (string, error) {
+	return core.LoginMetaKey(target)
+}
+
 // Instantiate a new Bridge for a repo, from the given target and name
 func NewBridge(repo *cache.RepoCache, target string, name string) (*core.Bridge, error) {
 	return core.NewBridge(repo, target, name)

bridge/core/bridge.go 🔗

@@ -28,6 +28,7 @@ const (
 )
 
 var bridgeImpl map[string]reflect.Type
+var bridgeLoginMetaKey map[string]string
 
 // BridgeParams holds parameters to simplify the bridge configuration without
 // having to make terminal prompts.
@@ -59,7 +60,11 @@ func Register(impl BridgeImpl) {
 	if bridgeImpl == nil {
 		bridgeImpl = make(map[string]reflect.Type)
 	}
+	if bridgeLoginMetaKey == nil {
+		bridgeLoginMetaKey = make(map[string]string)
+	}
 	bridgeImpl[impl.Target()] = reflect.TypeOf(impl)
+	bridgeLoginMetaKey[impl.Target()] = impl.LoginMetaKey()
 }
 
 // Targets return all known bridge implementation target
@@ -81,6 +86,18 @@ func TargetExist(target string) bool {
 	return ok
 }
 
+// LoginMetaKey return the metadata key used to store the remote bug-tracker login
+// on the user identity. The corresponding value is used to match identities and
+// credentials.
+func LoginMetaKey(target string) (string, error) {
+	metaKey, ok := bridgeLoginMetaKey[target]
+	if !ok {
+		return "", fmt.Errorf("unknown bridge target %v", target)
+	}
+
+	return metaKey, nil
+}
+
 // Instantiate a new Bridge for a repo, from the given target and name
 func NewBridge(repo *cache.RepoCache, target string, name string) (*Bridge, error) {
 	implType, ok := bridgeImpl[target]

bridge/core/interfaces.go 🔗

@@ -13,6 +13,11 @@ type BridgeImpl interface {
 	// Target return the target of the bridge (e.g.: "github")
 	Target() string
 
+	// LoginMetaKey return the metadata key used to store the remote bug-tracker login
+	// on the user identity. The corresponding value is used to match identities and
+	// credentials.
+	LoginMetaKey() string
+
 	// Configure handle the user interaction and return a key/value configuration
 	// for future use
 	Configure(repo *cache.RepoCache, params BridgeParams) (Configuration, error)

bridge/github/config.go 🔗

@@ -27,15 +27,6 @@ import (
 	"github.com/MichaelMure/git-bug/util/colors"
 )
 
-const (
-	target      = "github"
-	githubV3Url = "https://api.github.com"
-	keyOwner    = "owner"
-	keyProject  = "project"
-
-	defaultTimeout = 60 * time.Second
-)
-
 var (
 	ErrBadProjectURL = errors.New("bad project url")
 )

bridge/github/export.go 🔗

@@ -76,7 +76,7 @@ func (ge *githubExporter) Init(repo *cache.RepoCache, conf core.Configuration) e
 	}
 
 	login := user.ImmutableMetadata()[metaKeyGithubLogin]
-	creds, err := auth.List(repo, auth.WithMeta(metaKeyGithubLogin, login), auth.WithTarget(target), auth.WithKind(auth.KindToken))
+	creds, err := auth.List(repo, auth.WithMeta(auth.MetaKeyLogin, login), auth.WithTarget(target), auth.WithKind(auth.KindToken))
 	if err != nil {
 		return err
 	}

bridge/github/export_test.go 🔗

@@ -179,7 +179,7 @@ func TestPushPull(t *testing.T) {
 	})
 
 	token := auth.NewToken(envToken, target)
-	token.SetMetadata(metaKeyGithubLogin, login)
+	token.SetMetadata(auth.MetaKeyLogin, login)
 	err = auth.Store(repo, token)
 	require.NoError(t, err)
 

bridge/github/github.go 🔗

@@ -3,6 +3,7 @@ package github
 
 import (
 	"context"
+	"time"
 
 	"github.com/shurcooL/githubv4"
 	"golang.org/x/oauth2"
@@ -11,12 +12,32 @@ import (
 	"github.com/MichaelMure/git-bug/bridge/core/auth"
 )
 
+const (
+	target = "github"
+
+	metaKeyGithubId    = "github-id"
+	metaKeyGithubUrl   = "github-url"
+	metaKeyGithubLogin = "github-login"
+
+	keyOwner   = "owner"
+	keyProject = "project"
+
+	githubV3Url    = "https://api.github.com"
+	defaultTimeout = 60 * time.Second
+)
+
+var _ core.BridgeImpl = &Github{}
+
 type Github struct{}
 
 func (*Github) Target() string {
 	return target
 }
 
+func (g *Github) LoginMetaKey() string {
+	return metaKeyGithubLogin
+}
+
 func (*Github) NewImporter() core.Importer {
 	return &githubImporter{}
 }

bridge/github/import.go 🔗

@@ -15,12 +15,6 @@ import (
 	"github.com/MichaelMure/git-bug/util/text"
 )
 
-const (
-	metaKeyGithubId    = "github-id"
-	metaKeyGithubUrl   = "github-url"
-	metaKeyGithubLogin = "github-login"
-)
-
 // githubImporter implement the Importer interface
 type githubImporter struct {
 	conf core.Configuration

bridge/github/import_test.go 🔗

@@ -145,7 +145,7 @@ func Test_Importer(t *testing.T) {
 	author.SetMetadata(metaKeyGithubLogin, login)
 
 	token := auth.NewToken(envToken, target)
-	token.SetMetadata(metaKeyGithubLogin, login)
+	token.SetMetadata(auth.MetaKeyLogin, login)
 	err = auth.Store(repo, token)
 	require.NoError(t, err)
 

bridge/gitlab/gitlab.go 🔗

@@ -26,12 +26,18 @@ const (
 	defaultTimeout = 60 * time.Second
 )
 
+var _ core.BridgeImpl = &Gitlab{}
+
 type Gitlab struct{}
 
 func (*Gitlab) Target() string {
 	return target
 }
 
+func (g *Gitlab) LoginMetaKey() string {
+	return metaKeyGitlabLogin
+}
+
 func (*Gitlab) NewImporter() core.Importer {
 	return &gitlabImporter{}
 }

bridge/launchpad/config.go 🔗

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"net/http"
 	"regexp"
-	"time"
 
 	"github.com/MichaelMure/git-bug/bridge/core"
 	"github.com/MichaelMure/git-bug/cache"
@@ -14,12 +13,6 @@ import (
 
 var ErrBadProjectURL = errors.New("bad Launchpad project URL")
 
-const (
-	target         = "launchpad-preview"
-	keyProject     = "project"
-	defaultTimeout = 60 * time.Second
-)
-
 func (l *Launchpad) Configure(repo *cache.RepoCache, params core.BridgeParams) (core.Configuration, error) {
 	if params.TokenRaw != "" {
 		fmt.Println("warning: token params are ineffective for a Launchpad bridge")

bridge/launchpad/import.go 🔗

@@ -20,11 +20,6 @@ func (li *launchpadImporter) Init(repo *cache.RepoCache, conf core.Configuration
 	return nil
 }
 
-const (
-	metaKeyLaunchpadID    = "launchpad-id"
-	metaKeyLaunchpadLogin = "launchpad-login"
-)
-
 func (li *launchpadImporter) ensurePerson(repo *cache.RepoCache, owner LPPerson) (*cache.IdentityCache, error) {
 	// Look first in the cache
 	i, err := repo.ResolveIdentityImmutableMetadata(metaKeyLaunchpadLogin, owner.Login)

bridge/launchpad/launchpad.go 🔗

@@ -2,15 +2,34 @@
 package launchpad
 
 import (
+	"time"
+
 	"github.com/MichaelMure/git-bug/bridge/core"
 )
 
+const (
+	target = "launchpad-preview"
+
+	metaKeyLaunchpadID    = "launchpad-id"
+	metaKeyLaunchpadLogin = "launchpad-login"
+
+	keyProject = "project"
+
+	defaultTimeout = 60 * time.Second
+)
+
+var _ core.BridgeImpl = &Launchpad{}
+
 type Launchpad struct{}
 
 func (*Launchpad) Target() string {
 	return "launchpad-preview"
 }
 
+func (l *Launchpad) LoginMetaKey() string {
+	return metaKeyLaunchpadLogin
+}
+
 func (*Launchpad) NewImporter() core.Importer {
 	return &launchpadImporter{}
 }

commands/bridge_auth.go 🔗

@@ -2,6 +2,8 @@ package commands
 
 import (
 	"fmt"
+	"sort"
+	"strings"
 
 	"github.com/spf13/cobra"
 
@@ -26,8 +28,6 @@ func runBridgeAuth(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	defaultUser, _ := backend.GetUserIdentity()
-
 	for _, cred := range creds {
 		targetFmt := text.LeftPadMaxLine(cred.Target(), 10, 0)
 
@@ -37,29 +37,19 @@ func runBridgeAuth(cmd *cobra.Command, args []string) error {
 			value = cred.Value
 		}
 
-		var userFmt string
-
-		switch cred.UserId() {
-		case auth.DefaultUserId:
-			userFmt = colors.Red("default user")
-		default:
-			user, err := backend.ResolveIdentity(cred.UserId())
-			if err != nil {
-				return err
-			}
-			userFmt = user.DisplayName()
-
-			if cred.UserId() == defaultUser.Id() {
-				userFmt = colors.Red(userFmt)
-			}
+		meta := make([]string, 0, len(cred.Metadata()))
+		for k, v := range cred.Metadata() {
+			meta = append(meta, k+":"+v)
 		}
+		sort.Strings(meta)
+		metaFmt := strings.Join(meta, ",")
 
 		fmt.Printf("%s %s %s %s %s\n",
 			colors.Cyan(cred.ID().Human()),
 			colors.Yellow(targetFmt),
 			colors.Magenta(cred.Kind()),
-			userFmt,
 			value,
+			metaFmt,
 		)
 	}
 

commands/bridge_auth_addtoken.go 🔗

@@ -13,24 +13,37 @@ import (
 	"github.com/MichaelMure/git-bug/bridge"
 	"github.com/MichaelMure/git-bug/bridge/core"
 	"github.com/MichaelMure/git-bug/bridge/core/auth"
-	"github.com/MichaelMure/git-bug/identity"
+	"github.com/MichaelMure/git-bug/cache"
+	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
 var (
 	bridgeAuthAddTokenTarget string
+	bridgeAuthAddTokenLogin  string
+	bridgeAuthAddTokenUser   string
 )
 
 func runBridgeTokenAdd(cmd *cobra.Command, args []string) error {
-	var value string
-
 	if bridgeAuthAddTokenTarget == "" {
 		return fmt.Errorf("flag --target is required")
 	}
+	if bridgeAuthAddTokenLogin == "" {
+		return fmt.Errorf("flag --login is required")
+	}
+
+	backend, err := cache.NewRepoCache(repo)
+	if err != nil {
+		return err
+	}
+	defer backend.Close()
+	interrupt.RegisterCleaner(backend.Close)
 
 	if !core.TargetExist(bridgeAuthAddTokenTarget) {
 		return fmt.Errorf("unknown target")
 	}
 
+	var value string
+
 	if len(args) == 1 {
 		value = args[0]
 	} else {
@@ -46,12 +59,36 @@ func runBridgeTokenAdd(cmd *cobra.Command, args []string) error {
 		value = strings.TrimSuffix(raw, "\n")
 	}
 
-	user, err := identity.GetUserIdentity(repo)
+	var user *cache.IdentityCache
+
+	if bridgeAuthAddTokenUser == "" {
+		user, err = backend.GetUserIdentity()
+	} else {
+		user, err = backend.ResolveIdentityPrefix(bridgeAuthAddTokenUser)
+	}
 	if err != nil {
 		return err
 	}
 
-	token := auth.NewToken(user.Id(), value, bridgeAuthAddTokenTarget)
+	metaKey, _ := bridge.LoginMetaKey(bridgeAuthAddTokenTarget)
+	login, ok := user.ImmutableMetadata()[metaKey]
+
+	switch {
+	case ok && login == bridgeAuthAddTokenLogin:
+		// nothing to do
+	case ok && login != bridgeAuthAddTokenLogin:
+		return fmt.Errorf("this user is already tagged with a different %s login", bridgeAuthAddTokenTarget)
+	default:
+		user.SetMetadata(metaKey, bridgeAuthAddTokenLogin)
+		err = user.Commit()
+		if err != nil {
+			return err
+		}
+	}
+
+	token := auth.NewToken(value, bridgeAuthAddTokenTarget)
+	token.SetMetadata(auth.MetaKeyLogin, bridgeAuthAddTokenLogin)
+
 	if err := token.Validate(); err != nil {
 		return errors.Wrap(err, "invalid token")
 	}
@@ -77,5 +114,9 @@ func init() {
 	bridgeAuthCmd.AddCommand(bridgeAuthAddTokenCmd)
 	bridgeAuthAddTokenCmd.Flags().StringVarP(&bridgeAuthAddTokenTarget, "target", "t", "",
 		fmt.Sprintf("The target of the bridge. Valid values are [%s]", strings.Join(bridge.Targets(), ",")))
+	bridgeAuthAddTokenCmd.Flags().StringVarP(&bridgeAuthAddTokenLogin,
+		"login", "l", "", "The login in the remote bug-tracker")
+	bridgeAuthAddTokenCmd.Flags().StringVarP(&bridgeAuthAddTokenUser,
+		"user", "u", "", "The user to add the token to. Default is the current user")
 	bridgeAuthAddTokenCmd.Flags().SortFlags = false
 }

commands/bridge_auth_show.go 🔗

@@ -2,13 +2,14 @@ package commands
 
 import (
 	"fmt"
+	"sort"
+	"strings"
 	"time"
 
 	"github.com/spf13/cobra"
 
 	"github.com/MichaelMure/git-bug/bridge/core/auth"
 	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/util/colors"
 	"github.com/MichaelMure/git-bug/util/interrupt"
 )
 
@@ -25,28 +26,9 @@ func runBridgeAuthShow(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	var userFmt string
-
-	switch cred.UserId() {
-	case auth.DefaultUserId:
-		userFmt = colors.Red("default user")
-	default:
-		user, err := backend.ResolveIdentity(cred.UserId())
-		if err != nil {
-			return err
-		}
-		userFmt = user.DisplayName()
-
-		defaultUser, _ := backend.GetUserIdentity()
-		if cred.UserId() == defaultUser.Id() {
-			userFmt = colors.Red(userFmt)
-		}
-	}
-
 	fmt.Printf("Id: %s\n", cred.ID())
 	fmt.Printf("Target: %s\n", cred.Target())
 	fmt.Printf("Kind: %s\n", cred.Kind())
-	fmt.Printf("User: %s\n", userFmt)
 	fmt.Printf("Creation: %s\n", cred.CreateTime().Format(time.RFC822))
 
 	switch cred := cred.(type) {
@@ -54,6 +36,16 @@ func runBridgeAuthShow(cmd *cobra.Command, args []string) error {
 		fmt.Printf("Value: %s\n", cred.Value)
 	}
 
+	fmt.Println("Metadata:")
+
+	meta := make([]string, 0, len(cred.Metadata()))
+	for key, value := range cred.Metadata() {
+		meta = append(meta, fmt.Sprintf("    %s --> %s\n", key, value))
+	}
+	sort.Strings(meta)
+
+	fmt.Print(strings.Join(meta, ""))
+
 	return nil
 }
 

commands/user.go 🔗

@@ -50,8 +50,6 @@ func runUser(cmd *cobra.Command, args []string) error {
 				Time().Format("Mon Jan 2 15:04:05 2006 +0200"))
 		case "lastModificationLamport":
 			fmt.Printf("%d\n", id.LastModificationLamport())
-		case "login":
-			fmt.Printf("%s\n", id.Login())
 		case "metadata":
 			for key, value := range id.ImmutableMetadata() {
 				fmt.Printf("%s\n%s\n", key, value)
@@ -68,7 +66,6 @@ func runUser(cmd *cobra.Command, args []string) error {
 
 	fmt.Printf("Id: %s\n", id.Id())
 	fmt.Printf("Name: %s\n", id.Name())
-	fmt.Printf("Login: %s\n", id.Login())
 	fmt.Printf("Email: %s\n", id.Email())
 	fmt.Printf("Last modification: %s (lamport %d)\n",
 		id.LastModification().Time().Format("Mon Jan 2 15:04:05 2006 +0200"),

commands/user_adopt.go 🔗

@@ -4,11 +4,10 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/MichaelMure/git-bug/bridge/core/auth"
+	"github.com/spf13/cobra"
+
 	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/identity"
 	"github.com/MichaelMure/git-bug/util/interrupt"
-	"github.com/spf13/cobra"
 )
 
 func runUserAdopt(cmd *cobra.Command, args []string) error {
@@ -26,16 +25,6 @@ func runUserAdopt(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	_, err = backend.GetUserIdentity()
-	if err == identity.ErrNoIdentitySet {
-		err = auth.ReplaceDefaultUser(repo, i.Id())
-		if err != nil {
-			return err
-		}
-	} else if err != nil {
-		return err
-	}
-
 	err = backend.SetUserIdentity(i)
 	if err != nil {
 		return err

commands/user_create.go 🔗

@@ -23,7 +23,7 @@ func runUserCreate(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	name, err := input.Prompt("Name", "name", preName, input.Required)
+	name, err := input.PromptDefault("Name", "name", preName, input.Required)
 	if err != nil {
 		return err
 	}
@@ -33,17 +33,17 @@ func runUserCreate(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	email, err := input.Prompt("Email", "email", preEmail, input.Required)
+	email, err := input.PromptDefault("Email", "email", preEmail, input.Required)
 	if err != nil {
 		return err
 	}
 
-	login, err := input.Prompt("Avatar URL", "avatar", "")
+	avatarURL, err := input.Prompt("Avatar URL", "avatar")
 	if err != nil {
 		return err
 	}
 
-	id, err := backend.NewIdentityRaw(name, email, "", login, nil)
+	id, err := backend.NewIdentityRaw(name, email, avatarURL, nil)
 	if err != nil {
 		return err
 	}

doc/man/git-bug-bridge-auth-add-token.1 🔗

@@ -23,6 +23,14 @@ Store a new token
 \fB\-t\fP, \fB\-\-target\fP=""
     The target of the bridge. Valid values are [github,gitlab,launchpad\-preview]
 
+.PP
+\fB\-l\fP, \fB\-\-login\fP=""
+    The login in the remote bug\-tracker
+
+.PP
+\fB\-u\fP, \fB\-\-user\fP=""
+    The user to add the token to. Default is the current user
+
 .PP
 \fB\-h\fP, \fB\-\-help\fP[=false]
     help for add\-token

doc/md/git-bug_bridge_auth_add-token.md 🔗

@@ -14,6 +14,8 @@ git-bug bridge auth add-token [<token>] [flags]
 
 ```
   -t, --target string   The target of the bridge. Valid values are [github,gitlab,launchpad-preview]
+  -l, --login string    The login in the remote bug-tracker
+  -u, --user string     The user to add the token to. Default is the current user
   -h, --help            help for add-token
 ```
 

misc/bash_completion/git-bug 🔗

@@ -305,6 +305,14 @@ _git-bug_bridge_auth_add-token()
     two_word_flags+=("--target")
     two_word_flags+=("-t")
     local_nonpersistent_flags+=("--target=")
+    flags+=("--login=")
+    two_word_flags+=("--login")
+    two_word_flags+=("-l")
+    local_nonpersistent_flags+=("--login=")
+    flags+=("--user=")
+    two_word_flags+=("--user")
+    two_word_flags+=("-u")
+    local_nonpersistent_flags+=("--user=")
 
     must_have_one_flag=()
     must_have_one_noun=()

misc/powershell_completion/git-bug 🔗

@@ -64,6 +64,10 @@ Register-ArgumentCompleter -Native -CommandName 'git-bug' -ScriptBlock {
         'git-bug;bridge;auth;add-token' {
             [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'The target of the bridge. Valid values are [github,gitlab,launchpad-preview]')
             [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'The target of the bridge. Valid values are [github,gitlab,launchpad-preview]')
+            [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'The login in the remote bug-tracker')
+            [CompletionResult]::new('--login', 'login', [CompletionResultType]::ParameterName, 'The login in the remote bug-tracker')
+            [CompletionResult]::new('-u', 'u', [CompletionResultType]::ParameterName, 'The user to add the token to. Default is the current user')
+            [CompletionResult]::new('--user', 'user', [CompletionResultType]::ParameterName, 'The user to add the token to. Default is the current user')
             break
         }
         'git-bug;bridge;auth;rm' {

misc/zsh_completion/git-bug 🔗

@@ -177,7 +177,9 @@ function _git-bug_bridge_auth {
 
 function _git-bug_bridge_auth_add-token {
   _arguments \
-    '(-t --target)'{-t,--target}'[The target of the bridge. Valid values are [github,gitlab,launchpad-preview]]:'
+    '(-t --target)'{-t,--target}'[The target of the bridge. Valid values are [github,gitlab,launchpad-preview]]:' \
+    '(-l --login)'{-l,--login}'[The login in the remote bug-tracker]:' \
+    '(-u --user)'{-u,--user}'[The user to add the token to. Default is the current user]:'
 }
 
 function _git-bug_bridge_auth_rm {