identity: implement the loading from git

Michael Muré created

Change summary

bug/bug.go            |  14 ++--
identity/bare.go      |   9 +++
identity/identity.go  | 109 ++++++++++++++++++++++++++++++++++++++++----
identity/interface.go |   6 +
identity/version.go   |   2 
5 files changed, 120 insertions(+), 20 deletions(-)

Detailed changes

bug/bug.go 🔗

@@ -113,13 +113,6 @@ func ReadRemoteBug(repo repository.ClockedRepo, remote string, id string) (*Bug,
 
 // readBug will read and parse a Bug from git
 func readBug(repo repository.ClockedRepo, ref string) (*Bug, error) {
-	hashes, err := repo.ListCommits(ref)
-
-	// TODO: this is not perfect, it might be a command invoke error
-	if err != nil {
-		return nil, ErrBugNotExist
-	}
-
 	refSplit := strings.Split(ref, "/")
 	id := refSplit[len(refSplit)-1]
 
@@ -127,6 +120,13 @@ func readBug(repo repository.ClockedRepo, ref string) (*Bug, error) {
 		return nil, fmt.Errorf("invalid ref length")
 	}
 
+	hashes, err := repo.ListCommits(ref)
+
+	// TODO: this is not perfect, it might be a command invoke error
+	if err != nil {
+		return nil, ErrBugNotExist
+	}
+
 	bug := Bug{
 		id: id,
 	}

identity/bare.go 🔗

@@ -9,6 +9,11 @@ import (
 	"github.com/MichaelMure/git-bug/util/text"
 )
 
+// Bare is a very minimal identity, designed to be fully embedded directly along
+// other data.
+//
+// in particular, this identity is designed to be compatible with the handling of
+// identities in the early version of git-bug.
 type Bare struct {
 	name      string
 	email     string
@@ -71,10 +76,12 @@ func (i Bare) AvatarUrl() string {
 	return i.avatarUrl
 }
 
+// Keys return the last version of the valid keys
 func (i Bare) Keys() []Key {
 	return []Key{}
 }
 
+// ValidKeysAtTime return the set of keys valid at a given lamport time
 func (i Bare) ValidKeysAtTime(time lamport.Time) []Key {
 	return []Key{}
 }
@@ -139,6 +146,8 @@ func (i Bare) Validate() error {
 	return nil
 }
 
+// IsProtected return true if the chain of git commits started to be signed.
+// If that's the case, only signed commit with a valid key for this identity can be added.
 func (i Bare) IsProtected() bool {
 	return false
 }

identity/identity.go 🔗

@@ -2,18 +2,22 @@
 package identity
 
 import (
+	"encoding/json"
 	"fmt"
 	"strings"
 
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/git"
 	"github.com/MichaelMure/git-bug/util/lamport"
+	"github.com/pkg/errors"
 )
 
 const identityRefPattern = "refs/identities/"
 const versionEntryName = "version"
 const identityConfigKey = "git-bug.identity"
 
+var ErrIdentityNotExist = errors.New("identity doesn't exist")
+
 type Identity struct {
 	id       string
 	Versions []Version
@@ -35,22 +39,106 @@ type identityJson struct {
 	Id string `json:"id"`
 }
 
-// TODO: marshal/unmarshal identity + load/write from OpBase
+// MarshalJSON will only serialize the id
+func (i *Identity) MarshalJSON() ([]byte, error) {
+	return json.Marshal(identityJson{
+		Id: i.Id(),
+	})
+}
+
+// UnmarshalJSON will only read the id
+// Users of this package are expected to run Load() to load
+// the remaining data from the identities data in git.
+func (i *Identity) UnmarshalJSON(data []byte) error {
+	aux := identityJson{}
+
+	if err := json.Unmarshal(data, &aux); err != nil {
+		return err
+	}
+
+	i.id = aux.Id
 
+	return nil
+}
+
+// TODO: load/write from OpBase
+
+// Read load an Identity from the identities data available in git
 func Read(repo repository.Repo, id string) (*Identity, error) {
-	// Todo
-	return &Identity{}, nil
+	i := &Identity{
+		id: id,
+	}
+
+	err := i.Load(repo)
+	if err != nil {
+		return nil, err
+	}
+
+	return i, nil
+}
+
+// Load will read the corresponding identity data from git and replace any
+// data already loaded if any.
+func (i *Identity) Load(repo repository.Repo) error {
+	ref := fmt.Sprintf("%s%s", identityRefPattern, i.Id())
+
+	hashes, err := repo.ListCommits(ref)
+
+	var versions []Version
+
+	// TODO: this is not perfect, it might be a command invoke error
+	if err != nil {
+		return ErrIdentityNotExist
+	}
+
+	for _, hash := range hashes {
+		entries, err := repo.ListEntries(hash)
+		if err != nil {
+			return errors.Wrap(err, "can't list git tree entries")
+		}
+
+		if len(entries) != 1 {
+			return fmt.Errorf("invalid identity data at hash %s", hash)
+		}
+
+		entry := entries[0]
+
+		if entry.Name != versionEntryName {
+			return fmt.Errorf("invalid identity data at hash %s", hash)
+		}
+
+		data, err := repo.ReadData(entry.Hash)
+		if err != nil {
+			return errors.Wrap(err, "failed to read git blob data")
+		}
+
+		var version Version
+		err = json.Unmarshal(data, &version)
+
+		if err != nil {
+			return errors.Wrapf(err, "failed to decode Identity version json %s", hash)
+		}
+
+		// tag the version with the commit hash
+		version.commitHash = hash
+
+		versions = append(versions, version)
+	}
+
+	i.Versions = versions
+
+	return nil
 }
 
 // NewFromGitUser will query the repository for user detail and
 // build the corresponding Identity
-/*func NewFromGitUser(repo repository.Repo) (*Identity, error) {
+func NewFromGitUser(repo repository.Repo) (*Identity, error) {
 	name, err := repo.GetUserName()
 	if err != nil {
 		return nil, err
 	}
 	if name == "" {
-		return nil, errors.New("User name is not configured in git yet. Please use `git config --global user.name \"John Doe\"`")
+		return nil, errors.New("user name is not configured in git yet. Please use `git config --global user.name \"John Doe\"`")
 	}
 
 	email, err := repo.GetUserEmail()
@@ -58,14 +146,15 @@ func Read(repo repository.Repo, id string) (*Identity, error) {
 		return nil, err
 	}
 	if email == "" {
-		return nil, errors.New("User name is not configured in git yet. Please use `git config --global user.email johndoe@example.com`")
+		return nil, errors.New("user name is not configured in git yet. Please use `git config --global user.email johndoe@example.com`")
 	}
 
 	return NewIdentity(name, email)
-}*/
+}
 
-//
-func BuildFromGit(repo repository.Repo) *Identity {
+// BuildFromGit will query the repository for user detail and
+// build the corresponding Identity
+/*func BuildFromGit(repo repository.Repo) *Identity {
 	version := Version{}
 
 	name, err := repo.GetUserName()
@@ -83,7 +172,7 @@ func BuildFromGit(repo repository.Repo) *Identity {
 			version,
 		},
 	}
-}
+}*/
 
 // SetIdentity store the user identity's id in the git config
 func SetIdentity(repo repository.RepoCommon, identity Identity) error {

identity/interface.go 🔗

@@ -1,6 +1,8 @@
 package identity
 
-import "github.com/MichaelMure/git-bug/util/lamport"
+import (
+	"github.com/MichaelMure/git-bug/util/lamport"
+)
 
 type Interface interface {
 	Name() string
@@ -8,7 +10,7 @@ type Interface interface {
 	Login() string
 	AvatarUrl() string
 
-	// Login return the last version of the valid keys
+	// Keys return the last version of the valid keys
 	Keys() []Key
 
 	// ValidKeysAtTime return the set of keys valid at a given lamport time

identity/version.go 🔗

@@ -8,11 +8,11 @@ import (
 
 	"github.com/MichaelMure/git-bug/repository"
 	"github.com/MichaelMure/git-bug/util/git"
-
 	"github.com/MichaelMure/git-bug/util/lamport"
 	"github.com/MichaelMure/git-bug/util/text"
 )
 
+// Version is a complete set of informations about an Identity at a point in time.
 type Version struct {
 	// Private field so not serialized
 	commitHash git.Hash