commands: add a super-fast "user ls" command

Michael Muré created

Change summary

cache/bug_excerpt.go         |  4 +++
cache/identity_excerpt.go    | 26 ++++++++++++++++++--
cache/repo_cache.go          | 12 +++++++++
commands/user_list.go        | 47 ++++++++++++++++++++++++++++++++++++++
doc/man/git-bug-user-ls.1    | 33 ++++++++++++++++++++++++++
doc/man/git-bug-user.1       |  2 
doc/md/git-bug_user.md       |  1 
doc/md/git-bug_user_ls.md    | 23 ++++++++++++++++++
identity/bare.go             |  5 ++++
identity/identity.go         | 17 +++++++++++++
identity/identity_stub.go    |  4 +++
identity/interface.go        |  3 ++
misc/bash_completion/git-bug | 24 +++++++++++++++++++
misc/zsh_completion/git-bug  |  2 
14 files changed, 198 insertions(+), 5 deletions(-)

Detailed changes

cache/bug_excerpt.go 🔗

@@ -68,6 +68,10 @@ func NewBugExcerpt(b bug.Interface, snap *bug.Snapshot) *BugExcerpt {
 	return e
 }
 
+func (b *BugExcerpt) HumanId() string {
+	return bug.FormatHumanID(b.Id)
+}
+
 /*
  * Sorting
  */

cache/identity_excerpt.go 🔗

@@ -2,10 +2,16 @@ package cache
 
 import (
 	"encoding/gob"
+	"fmt"
 
 	"github.com/MichaelMure/git-bug/identity"
 )
 
+// Package initialisation used to register the type for (de)serialization
+func init() {
+	gob.Register(IdentityExcerpt{})
+}
+
 // IdentityExcerpt hold a subset of the identity values to be able to sort and
 // filter identities efficiently without having to read and compile each raw
 // identity.
@@ -26,9 +32,23 @@ func NewIdentityExcerpt(i *identity.Identity) *IdentityExcerpt {
 	}
 }
 
-// Package initialisation used to register the type for (de)serialization
-func init() {
-	gob.Register(IdentityExcerpt{})
+func (i *IdentityExcerpt) HumanId() string {
+	return identity.FormatHumanID(i.Id)
+}
+
+// DisplayName return a non-empty string to display, representing the
+// identity, based on the non-empty values.
+func (i *IdentityExcerpt) DisplayName() string {
+	switch {
+	case i.Name == "" && i.Login != "":
+		return i.Login
+	case i.Name != "" && i.Login == "":
+		return i.Name
+	case i.Name != "" && i.Login != "":
+		return fmt.Sprintf("%s (%s)", i.Name, i.Login)
+	}
+
+	panic("invalid person data")
 }
 
 /*

cache/repo_cache.go 🔗

@@ -489,6 +489,12 @@ func (c *RepoCache) AllBugsIds() []string {
 	return result
 }
 
+// AllBugExcerpt return all known bug excerpt.
+// This maps is read-only.
+func (c *RepoCache) AllBugExcerpt() map[string]*BugExcerpt {
+	return c.bugExcerpts
+}
+
 // ValidLabels list valid labels
 //
 // Note: in the future, a proper label policy could be implemented where valid
@@ -765,6 +771,12 @@ func (c *RepoCache) AllIdentityIds() []string {
 	return result
 }
 
+// AllIdentityExcerpt return all known identities excerpt.
+// This maps is read-only.
+func (c *RepoCache) AllIdentityExcerpt() map[string]*IdentityExcerpt {
+	return c.identitiesExcerpts
+}
+
 func (c *RepoCache) SetUserIdentity(i *IdentityCache) error {
 	err := identity.SetUserIdentity(c.repo, i.Identity)
 	if err != nil {

commands/user_list.go 🔗

@@ -0,0 +1,47 @@
+package commands
+
+import (
+	"fmt"
+
+	"github.com/MichaelMure/git-bug/cache"
+	"github.com/MichaelMure/git-bug/util/colors"
+	"github.com/MichaelMure/git-bug/util/interrupt"
+	"github.com/spf13/cobra"
+)
+
+var (
+	userLsVerbose bool
+)
+
+func runUserLs(cmd *cobra.Command, args []string) error {
+	backend, err := cache.NewRepoCache(repo)
+	if err != nil {
+		return err
+	}
+	defer backend.Close()
+	interrupt.RegisterCleaner(backend.Close)
+
+	for _, i := range backend.AllIdentityExcerpt() {
+		fmt.Printf("%s %s\n",
+			colors.Cyan(i.HumanId()),
+			i.DisplayName(),
+		)
+	}
+
+	return nil
+}
+
+var userLsCmd = &cobra.Command{
+	Use:     "ls",
+	Short:   "List identities",
+	PreRunE: loadRepo,
+	RunE:    runUserLs,
+}
+
+func init() {
+	userCmd.AddCommand(userLsCmd)
+	userLsCmd.Flags().SortFlags = false
+
+	userLsCmd.Flags().BoolVarP(&userLsVerbose, "verbose", "v", false,
+		"Print extra information")
+}

doc/man/git-bug-user-ls.1 🔗

@@ -0,0 +1,33 @@
+.TH "GIT-BUG" "1" "Feb 2019" "Generated from git-bug's source code" "" 
+.nh
+.ad l
+
+
+.SH NAME
+.PP
+git\-bug\-user\-ls \- List identities
+
+
+.SH SYNOPSIS
+.PP
+\fBgit\-bug user ls [flags]\fP
+
+
+.SH DESCRIPTION
+.PP
+List identities
+
+
+.SH OPTIONS
+.PP
+\fB\-v\fP, \fB\-\-verbose\fP[=false]
+    Print extra information
+
+.PP
+\fB\-h\fP, \fB\-\-help\fP[=false]
+    help for ls
+
+
+.SH SEE ALSO
+.PP
+\fBgit\-bug\-user(1)\fP

doc/man/git-bug-user.1 🔗

@@ -26,4 +26,4 @@ Display or change the user identity
 
 .SH SEE ALSO
 .PP
-\fBgit\-bug(1)\fP, \fBgit\-bug\-user\-create(1)\fP
+\fBgit\-bug(1)\fP, \fBgit\-bug\-user\-create(1)\fP, \fBgit\-bug\-user\-ls(1)\fP

doc/md/git-bug_user.md 🔗

@@ -20,4 +20,5 @@ git-bug user [<id>] [flags]
 
 * [git-bug](git-bug.md)	 - A bug tracker embedded in Git
 * [git-bug user create](git-bug_user_create.md)	 - Create a new identity
+* [git-bug user ls](git-bug_user_ls.md)	 - List identities
 

doc/md/git-bug_user_ls.md 🔗

@@ -0,0 +1,23 @@
+## git-bug user ls
+
+List identities
+
+### Synopsis
+
+List identities
+
+```
+git-bug user ls [flags]
+```
+
+### Options
+
+```
+  -v, --verbose   Print extra information
+  -h, --help      help for ls
+```
+
+### SEE ALSO
+
+* [git-bug user](git-bug_user.md)	 - Display or change the user identity
+

identity/bare.go 🔗

@@ -85,6 +85,11 @@ func (i *Bare) Id() string {
 	return i.id
 }
 
+// HumanId return the Identity identifier truncated for human consumption
+func (i *Bare) HumanId() string {
+	return FormatHumanID(i.Id())
+}
+
 // Name return the last version of the name
 func (i *Bare) Name() string {
 	return i.name

identity/identity.go 🔗

@@ -20,6 +20,9 @@ const identityRemoteRefPattern = "refs/remotes/%s/identities/"
 const versionEntryName = "version"
 const identityConfigKey = "git-bug.identity"
 
+const idLength = 40
+const humanIdLength = 7
+
 var ErrNonFastForwardMerge = errors.New("non fast-forward identity merge")
 var ErrNoIdentitySet = errors.New("user identity first needs to be created using \"git bug user create\"")
 var ErrMultipleIdentitiesSet = errors.New("multiple user identities set")
@@ -93,6 +96,10 @@ func read(repo repository.Repo, ref string) (*Identity, error) {
 	refSplit := strings.Split(ref, "/")
 	id := refSplit[len(refSplit)-1]
 
+	if len(id) != idLength {
+		return nil, fmt.Errorf("invalid ref length")
+	}
+
 	hashes, err := repo.ListCommits(ref)
 
 	// TODO: this is not perfect, it might be a command invoke error
@@ -461,6 +468,16 @@ func (i *Identity) Id() string {
 	return i.id
 }
 
+// HumanId return the Identity identifier truncated for human consumption
+func (i *Identity) HumanId() string {
+	return FormatHumanID(i.Id())
+}
+
+func FormatHumanID(id string) string {
+	format := fmt.Sprintf("%%.%ds", humanIdLength)
+	return fmt.Sprintf(format, id)
+}
+
 // Name return the last version of the name
 func (i *Identity) Name() string {
 	return i.lastVersion().name

identity/identity_stub.go 🔗

@@ -44,6 +44,10 @@ func (i *IdentityStub) Id() string {
 	return i.id
 }
 
+func (i *IdentityStub) HumanId() string {
+	return FormatHumanID(i.Id())
+}
+
 func (IdentityStub) Name() string {
 	panic("identities needs to be properly loaded with identity.ReadLocal()")
 }

identity/interface.go 🔗

@@ -9,6 +9,9 @@ type Interface interface {
 	// Id return the Identity identifier
 	Id() string
 
+	// HumanId return the Identity identifier truncated for human consumption
+	HumanId() string
+
 	// Name return the last version of the name
 	Name() string
 	// Email return the last version of the email

misc/bash_completion/git-bug 🔗

@@ -819,6 +819,29 @@ _git-bug_user_create()
     noun_aliases=()
 }
 
+_git-bug_user_ls()
+{
+    last_command="git-bug_user_ls"
+
+    command_aliases=()
+
+    commands=()
+
+    flags=()
+    two_word_flags=()
+    local_nonpersistent_flags=()
+    flags_with_completion=()
+    flags_completion=()
+
+    flags+=("--verbose")
+    flags+=("-v")
+    local_nonpersistent_flags+=("--verbose")
+
+    must_have_one_flag=()
+    must_have_one_noun=()
+    noun_aliases=()
+}
+
 _git-bug_user()
 {
     last_command="git-bug_user"
@@ -827,6 +850,7 @@ _git-bug_user()
 
     commands=()
     commands+=("create")
+    commands+=("ls")
 
     flags=()
     two_word_flags=()

misc/zsh_completion/git-bug 🔗

@@ -33,7 +33,7 @@ case $state in
         _arguments '2: :(edit)'
       ;;
       user)
-        _arguments '2: :(create)'
+        _arguments '2: :(create ls)'
       ;;
       *)
         _arguments '*: :_files'