From feab9412dffe5772048aad29893c4cb01d566387 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Wed, 21 Nov 2018 18:56:12 +0100 Subject: WIP identity in git --- identity/identity.go | 285 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 identity/identity.go (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go new file mode 100644 index 00000000..f65e2a86 --- /dev/null +++ b/identity/identity.go @@ -0,0 +1,285 @@ +// Package identity contains the identity data model and low-level related functions +package identity + +import ( + "fmt" + "strings" + + "github.com/MichaelMure/git-bug/repository" + "github.com/MichaelMure/git-bug/util/git" + "github.com/MichaelMure/git-bug/util/lamport" +) + +const identityRefPattern = "refs/identities/" +const versionEntryName = "version" +const identityConfigKey = "git-bug.identity" + +type Identity struct { + id string + Versions []Version +} + +func NewIdentity(name string, email string) (*Identity, error) { + return &Identity{ + Versions: []Version{ + { + Name: name, + Email: email, + Nonce: makeNonce(20), + }, + }, + }, nil +} + +type identityJson struct { + Id string `json:"id"` +} + +// TODO: marshal/unmarshal identity + load/write from OpBase + +func Read(repo repository.Repo, id string) (*Identity, error) { + // Todo + return &Identity{}, nil +} + +// NewFromGitUser will query the repository for user detail and +// build the corresponding Identity +/*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\"`") + } + + email, err := repo.GetUserEmail() + if err != nil { + 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 NewIdentity(name, email) +}*/ + +// +func BuildFromGit(repo repository.Repo) *Identity { + version := Version{} + + name, err := repo.GetUserName() + if err == nil { + version.Name = name + } + + email, err := repo.GetUserEmail() + if err == nil { + version.Email = email + } + + return &Identity{ + Versions: []Version{ + version, + }, + } +} + +// SetIdentity store the user identity's id in the git config +func SetIdentity(repo repository.RepoCommon, identity Identity) error { + return repo.StoreConfig(identityConfigKey, identity.Id()) +} + +// GetIdentity read the current user identity, set with a git config entry +func GetIdentity(repo repository.Repo) (*Identity, error) { + configs, err := repo.ReadConfigs(identityConfigKey) + if err != nil { + return nil, err + } + + if len(configs) == 0 { + return nil, fmt.Errorf("no identity set") + } + + if len(configs) > 1 { + return nil, fmt.Errorf("multiple identity config exist") + } + + var id string + for _, val := range configs { + id = val + } + + return Read(repo, id) +} + +func (i *Identity) AddVersion(version Version) { + i.Versions = append(i.Versions, version) +} + +func (i *Identity) Commit(repo repository.ClockedRepo) error { + // Todo: check for mismatch between memory and commited data + + var lastCommit git.Hash = "" + + for _, v := range i.Versions { + if v.commitHash != "" { + lastCommit = v.commitHash + // ignore already commited versions + continue + } + + blobHash, err := v.Write(repo) + if err != nil { + return err + } + + // Make a git tree referencing the blob + tree := []repository.TreeEntry{ + {ObjectType: repository.Blob, Hash: blobHash, Name: versionEntryName}, + } + + treeHash, err := repo.StoreTree(tree) + if err != nil { + return err + } + + var commitHash git.Hash + if lastCommit != "" { + commitHash, err = repo.StoreCommitWithParent(treeHash, lastCommit) + } else { + commitHash, err = repo.StoreCommit(treeHash) + } + + if err != nil { + return err + } + + lastCommit = commitHash + + // if it was the first commit, use the commit hash as the Identity id + if i.id == "" { + i.id = string(commitHash) + } + } + + if i.id == "" { + panic("identity with no id") + } + + ref := fmt.Sprintf("%s%s", identityRefPattern, i.id) + err := repo.UpdateRef(ref, lastCommit) + + if err != nil { + return err + } + + return nil +} + +// Validate check if the Identity data is valid +func (i *Identity) Validate() error { + lastTime := lamport.Time(0) + + for _, v := range i.Versions { + if err := v.Validate(); err != nil { + return err + } + + if v.Time < lastTime { + return fmt.Errorf("non-chronological version (%d --> %d)", lastTime, v.Time) + } + + lastTime = v.Time + } + + return nil +} + +func (i *Identity) LastVersion() Version { + if len(i.Versions) <= 0 { + panic("no version at all") + } + + return i.Versions[len(i.Versions)-1] +} + +// Id return the Identity identifier +func (i *Identity) Id() string { + if i.id == "" { + // simply panic as it would be a coding error + // (using an id of an identity not stored yet) + panic("no id yet") + } + return i.id +} + +// Name return the last version of the name +func (i *Identity) Name() string { + return i.LastVersion().Name +} + +// Email return the last version of the email +func (i *Identity) Email() string { + return i.LastVersion().Email +} + +// Login return the last version of the login +func (i *Identity) Login() string { + return i.LastVersion().Login +} + +// Login return the last version of the Avatar URL +func (i *Identity) AvatarUrl() string { + return i.LastVersion().AvatarUrl +} + +// Login return the last version of the valid keys +func (i *Identity) Keys() []Key { + return i.LastVersion().Keys +} + +// 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 *Identity) IsProtected() bool { + // Todo + return false +} + +// ValidKeysAtTime return the set of keys valid at a given lamport time +func (i *Identity) ValidKeysAtTime(time lamport.Time) []Key { + var result []Key + + for _, v := range i.Versions { + if v.Time > time { + return result + } + + result = v.Keys + } + + return result +} + +// Match tell is the Identity match the given query string +func (i *Identity) Match(query string) bool { + query = strings.ToLower(query) + + return strings.Contains(strings.ToLower(i.Name()), query) || + strings.Contains(strings.ToLower(i.Login()), query) +} + +// DisplayName return a non-empty string to display, representing the +// identity, based on the non-empty values. +func (i *Identity) 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") +} -- cgit From 06d9c6872655b85f1a47599add92d49d570e7b2e Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Wed, 16 Jan 2019 21:23:49 +0100 Subject: identity: implement the loading from git --- identity/identity.go | 109 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 99 insertions(+), 10 deletions(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index f65e2a86..0fe13d21 100644 --- a/identity/identity.go +++ b/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 { -- cgit From 3df4f46c71650c9d23b267c44afec16f1b759e92 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Thu, 17 Jan 2019 02:05:50 +0100 Subject: identity: add metadata support --- identity/identity.go | 86 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 14 deletions(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 0fe13d21..3d523d38 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -20,19 +20,33 @@ var ErrIdentityNotExist = errors.New("identity doesn't exist") type Identity struct { id string - Versions []Version + Versions []*Version } -func NewIdentity(name string, email string) (*Identity, error) { +func NewIdentity(name string, email string) *Identity { return &Identity{ - Versions: []Version{ + Versions: []*Version{ { Name: name, Email: email, Nonce: makeNonce(20), }, }, - }, nil + } +} + +func NewIdentityFull(name string, email string, login string, avatarUrl string) *Identity { + return &Identity{ + Versions: []*Version{ + { + Name: name, + Email: email, + Login: login, + AvatarUrl: avatarUrl, + Nonce: makeNonce(20), + }, + }, + } } type identityJson struct { @@ -84,7 +98,7 @@ func (i *Identity) Load(repo repository.Repo) error { hashes, err := repo.ListCommits(ref) - var versions []Version + var versions []*Version // TODO: this is not perfect, it might be a command invoke error if err != nil { @@ -122,7 +136,7 @@ func (i *Identity) Load(repo repository.Repo) error { // tag the version with the commit hash version.commitHash = hash - versions = append(versions, version) + versions = append(versions, &version) } i.Versions = versions @@ -149,7 +163,7 @@ func NewFromGitUser(repo repository.Repo) (*Identity, error) { 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) + return NewIdentity(name, email), nil } // BuildFromGit will query the repository for user detail and @@ -202,7 +216,7 @@ func GetIdentity(repo repository.Repo) (*Identity, error) { return Read(repo, id) } -func (i *Identity) AddVersion(version Version) { +func (i *Identity) AddVersion(version *Version) { i.Versions = append(i.Versions, version) } @@ -285,7 +299,15 @@ func (i *Identity) Validate() error { return nil } -func (i *Identity) LastVersion() Version { +func (i *Identity) firstVersion() *Version { + if len(i.Versions) <= 0 { + panic("no version at all") + } + + return i.Versions[0] +} + +func (i *Identity) lastVersion() *Version { if len(i.Versions) <= 0 { panic("no version at all") } @@ -305,27 +327,27 @@ func (i *Identity) Id() string { // Name return the last version of the name func (i *Identity) Name() string { - return i.LastVersion().Name + return i.lastVersion().Name } // Email return the last version of the email func (i *Identity) Email() string { - return i.LastVersion().Email + return i.lastVersion().Email } // Login return the last version of the login func (i *Identity) Login() string { - return i.LastVersion().Login + return i.lastVersion().Login } // Login return the last version of the Avatar URL func (i *Identity) AvatarUrl() string { - return i.LastVersion().AvatarUrl + return i.lastVersion().AvatarUrl } // Login return the last version of the valid keys func (i *Identity) Keys() []Key { - return i.LastVersion().Keys + return i.lastVersion().Keys } // IsProtected return true if the chain of git commits started to be signed. @@ -372,3 +394,39 @@ func (i *Identity) DisplayName() string { panic("invalid person data") } + +// SetMetadata store arbitrary metadata along the last defined Version. +// If the Version has been commit to git already, it won't be overwritten. +func (i *Identity) SetMetadata(key string, value string) { + i.lastVersion().SetMetadata(key, value) +} + +// ImmutableMetadata return all metadata for this Identity, accumulated from each Version. +// If multiple value are found, the first defined takes precedence. +func (i *Identity) ImmutableMetadata() map[string]string { + metadata := make(map[string]string) + + for _, version := range i.Versions { + for key, value := range version.Metadata { + if _, has := metadata[key]; !has { + metadata[key] = value + } + } + } + + return metadata +} + +// MutableMetadata return all metadata for this Identity, accumulated from each Version. +// If multiple value are found, the last defined takes precedence. +func (i *Identity) MutableMetadata() map[string]string { + metadata := make(map[string]string) + + for _, version := range i.Versions { + for key, value := range version.Metadata { + metadata[key] = value + } + } + + return metadata +} -- cgit From 844616baf8dc628360942d57fd69f24e298e08da Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sat, 19 Jan 2019 16:01:06 +0100 Subject: identity: more progress and fixes --- identity/identity.go | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 3d523d38..313e3fd7 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -18,6 +18,16 @@ const identityConfigKey = "git-bug.identity" var ErrIdentityNotExist = errors.New("identity doesn't exist") +type ErrMultipleMatch struct { + Matching []string +} + +func (e ErrMultipleMatch) Error() string { + return fmt.Sprintf("Multiple matching identities found:\n%s", strings.Join(e.Matching, "\n")) +} + +var _ Interface = &Identity{} + type Identity struct { id string Versions []*Version -- cgit From d10c76469d40f13e27739fd363145e89bf74c3e0 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sat, 19 Jan 2019 19:23:31 +0100 Subject: identity: somewhat getting closer ! --- identity/identity.go | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 313e3fd7..38729e37 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -16,16 +16,6 @@ const identityRefPattern = "refs/identities/" const versionEntryName = "version" const identityConfigKey = "git-bug.identity" -var ErrIdentityNotExist = errors.New("identity doesn't exist") - -type ErrMultipleMatch struct { - Matching []string -} - -func (e ErrMultipleMatch) Error() string { - return fmt.Sprintf("Multiple matching identities found:\n%s", strings.Join(e.Matching, "\n")) -} - var _ Interface = &Identity{} type Identity struct { @@ -85,8 +75,6 @@ func (i *Identity) UnmarshalJSON(data []byte) error { 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) { i := &Identity{ @@ -230,7 +218,9 @@ func (i *Identity) AddVersion(version *Version) { i.Versions = append(i.Versions, version) } -func (i *Identity) Commit(repo repository.ClockedRepo) error { +// Write the identity into the Repository. In particular, this ensure that +// the Id is properly set. +func (i *Identity) Commit(repo repository.Repo) error { // Todo: check for mismatch between memory and commited data var lastCommit git.Hash = "" -- cgit From 14b240af8fef269d2c1d5dde2fff192b656c50f3 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sun, 20 Jan 2019 15:41:27 +0100 Subject: identity: more cleaning and fixes after a code review --- identity/identity.go | 53 +++++++--------------------------------------------- 1 file changed, 7 insertions(+), 46 deletions(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 38729e37..2a422789 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -4,7 +4,6 @@ package identity import ( "encoding/json" "fmt" - "strings" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/git" @@ -49,13 +48,13 @@ func NewIdentityFull(name string, email string, login string, avatarUrl string) } } -type identityJson struct { +type identityJSON struct { Id string `json:"id"` } // MarshalJSON will only serialize the id func (i *Identity) MarshalJSON() ([]byte, error) { - return json.Marshal(identityJson{ + return json.Marshal(identityJSON{ Id: i.Id(), }) } @@ -64,7 +63,7 @@ func (i *Identity) MarshalJSON() ([]byte, error) { // 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{} + aux := identityJSON{} if err := json.Unmarshal(data, &aux); err != nil { return err @@ -164,35 +163,13 @@ func NewFromGitUser(repo repository.Repo) (*Identity, error) { return NewIdentity(name, email), nil } -// 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() - if err == nil { - version.Name = name - } - - email, err := repo.GetUserEmail() - if err == nil { - version.Email = email - } - - return &Identity{ - Versions: []Version{ - version, - }, - } -}*/ - -// SetIdentity store the user identity's id in the git config -func SetIdentity(repo repository.RepoCommon, identity Identity) error { +// SetUserIdentity store the user identity's id in the git config +func SetUserIdentity(repo repository.RepoCommon, identity Identity) error { return repo.StoreConfig(identityConfigKey, identity.Id()) } -// GetIdentity read the current user identity, set with a git config entry -func GetIdentity(repo repository.Repo) (*Identity, error) { +// GetUserIdentity read the current user identity, set with a git config entry +func GetUserIdentity(repo repository.Repo) (*Identity, error) { configs, err := repo.ReadConfigs(identityConfigKey) if err != nil { return nil, err @@ -299,14 +276,6 @@ func (i *Identity) Validate() error { return nil } -func (i *Identity) firstVersion() *Version { - if len(i.Versions) <= 0 { - panic("no version at all") - } - - return i.Versions[0] -} - func (i *Identity) lastVersion() *Version { if len(i.Versions) <= 0 { panic("no version at all") @@ -372,14 +341,6 @@ func (i *Identity) ValidKeysAtTime(time lamport.Time) []Key { return result } -// Match tell is the Identity match the given query string -func (i *Identity) Match(query string) bool { - query = strings.ToLower(query) - - return strings.Contains(strings.ToLower(i.Name()), query) || - strings.Contains(strings.ToLower(i.Login()), query) -} - // DisplayName return a non-empty string to display, representing the // identity, based on the non-empty values. func (i *Identity) DisplayName() string { -- cgit From 56c6147eb6012252cf0b723b9eb6d1e841fc2f94 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Fri, 1 Feb 2019 12:22:00 +0100 Subject: identity: more refactoring progress --- identity/identity.go | 54 ++++++++++++++-------------------------------------- 1 file changed, 14 insertions(+), 40 deletions(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 2a422789..2dafb353 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -48,14 +48,10 @@ func NewIdentityFull(name string, email string, login string, avatarUrl string) } } -type identityJSON struct { - Id string `json:"id"` -} - // MarshalJSON will only serialize the id func (i *Identity) MarshalJSON() ([]byte, error) { - return json.Marshal(identityJSON{ - Id: i.Id(), + return json.Marshal(&IdentityStub{ + id: i.Id(), }) } @@ -63,35 +59,12 @@ func (i *Identity) MarshalJSON() ([]byte, error) { // 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 + panic("identity should be loaded with identity.UnmarshalJSON") } // Read load an Identity from the identities data available in git func Read(repo repository.Repo, id string) (*Identity, error) { - 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()) + ref := fmt.Sprintf("%s%s", identityRefPattern, id) hashes, err := repo.ListCommits(ref) @@ -99,35 +72,35 @@ func (i *Identity) Load(repo repository.Repo) error { // TODO: this is not perfect, it might be a command invoke error if err != nil { - return ErrIdentityNotExist + return nil, ErrIdentityNotExist } for _, hash := range hashes { entries, err := repo.ListEntries(hash) if err != nil { - return errors.Wrap(err, "can't list git tree entries") + return nil, errors.Wrap(err, "can't list git tree entries") } if len(entries) != 1 { - return fmt.Errorf("invalid identity data at hash %s", hash) + return nil, 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) + return nil, 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") + return nil, 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) + return nil, errors.Wrapf(err, "failed to decode Identity version json %s", hash) } // tag the version with the commit hash @@ -136,9 +109,10 @@ func (i *Identity) Load(repo repository.Repo) error { versions = append(versions, &version) } - i.Versions = versions - - return nil + return &Identity{ + id: id, + Versions: versions, + }, nil } // NewFromGitUser will query the repository for user detail and -- cgit From 328a4e5abf3ec8ea41f89575fcfb83cf9f086c80 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sun, 3 Feb 2019 19:55:35 +0100 Subject: identity: wip push/pull --- identity/identity.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 2dafb353..3877e346 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -4,14 +4,17 @@ package identity import ( "encoding/json" "fmt" + "strings" + + "github.com/pkg/errors" "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 identityRemoteRefPattern = "refs/remotes/%s/identities/" const versionEntryName = "version" const identityConfigKey = "git-bug.identity" @@ -62,9 +65,22 @@ func (i *Identity) UnmarshalJSON(data []byte) error { panic("identity should be loaded with identity.UnmarshalJSON") } -// Read load an Identity from the identities data available in git -func Read(repo repository.Repo, id string) (*Identity, error) { +// ReadLocal load a local Identity from the identities data available in git +func ReadLocal(repo repository.Repo, id string) (*Identity, error) { ref := fmt.Sprintf("%s%s", identityRefPattern, id) + return read(repo, ref) +} + +// ReadRemote load a remote Identity from the identities data available in git +func ReadRemote(repo repository.Repo, remote string, id string) (*Identity, error) { + ref := fmt.Sprintf(identityRemoteRefPattern, remote) + id + return read(repo, ref) +} + +// read will load and parse an identity frdm git +func read(repo repository.Repo, ref string) (*Identity, error) { + refSplit := strings.Split(ref, "/") + id := refSplit[len(refSplit)-1] hashes, err := repo.ListCommits(ref) @@ -162,7 +178,7 @@ func GetUserIdentity(repo repository.Repo) (*Identity, error) { id = val } - return Read(repo, id) + return ReadLocal(repo, id) } func (i *Identity) AddVersion(version *Version) { -- cgit From 21048e785d976a04e26798e4a385ee675c95b88f Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Wed, 6 Feb 2019 22:06:42 +0100 Subject: identity: wip --- identity/identity.go | 162 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 114 insertions(+), 48 deletions(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 3877e346..59973489 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -21,17 +21,22 @@ const identityConfigKey = "git-bug.identity" var _ Interface = &Identity{} type Identity struct { - id string - Versions []*Version + // Id used as unique identifier + id string + + lastCommit git.Hash + + // all the successive version of the identity + versions []*Version } func NewIdentity(name string, email string) *Identity { return &Identity{ - Versions: []*Version{ + versions: []*Version{ { - Name: name, - Email: email, - Nonce: makeNonce(20), + name: name, + email: email, + nonce: makeNonce(20), }, }, } @@ -39,13 +44,13 @@ func NewIdentity(name string, email string) *Identity { func NewIdentityFull(name string, email string, login string, avatarUrl string) *Identity { return &Identity{ - Versions: []*Version{ + versions: []*Version{ { - Name: name, - Email: email, - Login: login, - AvatarUrl: avatarUrl, - Nonce: makeNonce(20), + name: name, + email: email, + login: login, + avatarURL: avatarUrl, + nonce: makeNonce(20), }, }, } @@ -84,13 +89,15 @@ func read(repo repository.Repo, ref string) (*Identity, error) { hashes, err := repo.ListCommits(ref) - var versions []*Version - // TODO: this is not perfect, it might be a command invoke error if err != nil { return nil, ErrIdentityNotExist } + i := &Identity{ + id: id, + } + for _, hash := range hashes { entries, err := repo.ListEntries(hash) if err != nil { @@ -121,14 +128,12 @@ func read(repo repository.Repo, ref string) (*Identity, error) { // tag the version with the commit hash version.commitHash = hash + i.lastCommit = hash - versions = append(versions, &version) + i.versions = append(i.versions, &version) } - return &Identity{ - id: id, - Versions: versions, - }, nil + return i, nil } // NewFromGitUser will query the repository for user detail and @@ -182,7 +187,7 @@ func GetUserIdentity(repo repository.Repo) (*Identity, error) { } func (i *Identity) AddVersion(version *Version) { - i.Versions = append(i.Versions, version) + i.versions = append(i.versions, version) } // Write the identity into the Repository. In particular, this ensure that @@ -190,11 +195,9 @@ func (i *Identity) AddVersion(version *Version) { func (i *Identity) Commit(repo repository.Repo) error { // Todo: check for mismatch between memory and commited data - var lastCommit git.Hash = "" - - for _, v := range i.Versions { + for _, v := range i.versions { if v.commitHash != "" { - lastCommit = v.commitHash + i.lastCommit = v.commitHash // ignore already commited versions continue } @@ -215,8 +218,8 @@ func (i *Identity) Commit(repo repository.Repo) error { } var commitHash git.Hash - if lastCommit != "" { - commitHash, err = repo.StoreCommitWithParent(treeHash, lastCommit) + if i.lastCommit != "" { + commitHash, err = repo.StoreCommitWithParent(treeHash, i.lastCommit) } else { commitHash, err = repo.StoreCommit(treeHash) } @@ -225,7 +228,8 @@ func (i *Identity) Commit(repo repository.Repo) error { return err } - lastCommit = commitHash + i.lastCommit = commitHash + v.commitHash = commitHash // if it was the first commit, use the commit hash as the Identity id if i.id == "" { @@ -238,7 +242,7 @@ func (i *Identity) Commit(repo repository.Repo) error { } ref := fmt.Sprintf("%s%s", identityRefPattern, i.id) - err := repo.UpdateRef(ref, lastCommit) + err := repo.UpdateRef(ref, i.lastCommit) if err != nil { return err @@ -247,31 +251,93 @@ func (i *Identity) Commit(repo repository.Repo) error { return nil } +// Merge will merge a different version of the same Identity +// +// To make sure that an Identity history can't be altered, a strict fast-forward +// only policy is applied here. As an Identity should be tied to a single user, this +// should work in practice but it does leave a possibility that a user would edit his +// Identity from two different repo concurrently and push the changes in a non-centralized +// network of repositories. In this case, it would result in some of the repo accepting one +// version and some other accepting another, preventing the network in general to converge +// to the same result. This would create a sort of partition of the network, and manual +// cleaning would be required. +// +// An alternative approach would be to have a determinist rebase: +// - any commits present in both local and remote version would be kept, never changed. +// - newer commits would be merged in a linear chain of commits, ordered based on the +// Lamport time +// +// However, this approach leave the possibility, in the case of a compromised crypto keys, +// of forging a new version with a bogus Lamport time to be inserted before a legit version, +// invalidating the correct version and hijacking the Identity. There would only be a short +// period of time where this would be possible (before the network converge) but I'm not +// confident enough to implement that. I choose the strict fast-forward only approach, +// despite it's potential problem with two different version as mentioned above. +func (i *Identity) Merge(repo repository.Repo, other *Identity) (bool, error) { + if i.id != other.id { + return false, errors.New("merging unrelated identities is not supported") + } + + if i.lastCommit == "" || other.lastCommit == "" { + return false, errors.New("can't merge identities that has never been stored") + } + + /*ancestor, err := repo.FindCommonAncestor(i.lastCommit, other.lastCommit) + if err != nil { + return false, errors.Wrap(err, "can't find common ancestor") + }*/ + + modified := false + for j, otherVersion := range other.versions { + // if there is more version in other, take them + if len(i.versions) == j { + i.versions = append(i.versions, otherVersion) + i.lastCommit = otherVersion.commitHash + modified = true + } + + // we have a non fast-forward merge. + // as explained in the doc above, refusing to merge + if i.versions[j].commitHash != otherVersion.commitHash { + return false, errors.New("non fast-forward identity merge") + } + } + + if modified { + err := repo.UpdateRef(identityRefPattern+i.id, i.lastCommit) + if err != nil { + return false, err + } + } + + return false, nil +} + // Validate check if the Identity data is valid func (i *Identity) Validate() error { lastTime := lamport.Time(0) - for _, v := range i.Versions { + for _, v := range i.versions { if err := v.Validate(); err != nil { return err } - if v.Time < lastTime { - return fmt.Errorf("non-chronological version (%d --> %d)", lastTime, v.Time) + if v.time < lastTime { + return fmt.Errorf("non-chronological version (%d --> %d)", lastTime, v.time) } - lastTime = v.Time + lastTime = v.time } return nil } func (i *Identity) lastVersion() *Version { - if len(i.Versions) <= 0 { + if len(i.versions) <= 0 { panic("no version at all") } - return i.Versions[len(i.Versions)-1] + return i.versions[len(i.versions)-1] } // Id return the Identity identifier @@ -286,27 +352,27 @@ func (i *Identity) Id() string { // Name return the last version of the name func (i *Identity) Name() string { - return i.lastVersion().Name + return i.lastVersion().name } // Email return the last version of the email func (i *Identity) Email() string { - return i.lastVersion().Email + return i.lastVersion().email } // Login return the last version of the login func (i *Identity) Login() string { - return i.lastVersion().Login + return i.lastVersion().login } -// Login return the last version of the Avatar URL +// AvatarUrl return the last version of the Avatar URL func (i *Identity) AvatarUrl() string { - return i.lastVersion().AvatarUrl + return i.lastVersion().avatarURL } -// Login return the last version of the valid keys +// Keys return the last version of the valid keys func (i *Identity) Keys() []Key { - return i.lastVersion().Keys + return i.lastVersion().keys } // IsProtected return true if the chain of git commits started to be signed. @@ -320,12 +386,12 @@ func (i *Identity) IsProtected() bool { func (i *Identity) ValidKeysAtTime(time lamport.Time) []Key { var result []Key - for _, v := range i.Versions { - if v.Time > time { + for _, v := range i.versions { + if v.time > time { return result } - result = v.Keys + result = v.keys } return result @@ -357,8 +423,8 @@ func (i *Identity) SetMetadata(key string, value string) { func (i *Identity) ImmutableMetadata() map[string]string { metadata := make(map[string]string) - for _, version := range i.Versions { - for key, value := range version.Metadata { + for _, version := range i.versions { + for key, value := range version.metadata { if _, has := metadata[key]; !has { metadata[key] = value } @@ -373,8 +439,8 @@ func (i *Identity) ImmutableMetadata() map[string]string { func (i *Identity) MutableMetadata() map[string]string { metadata := make(map[string]string) - for _, version := range i.Versions { - for key, value := range version.Metadata { + for _, version := range i.versions { + for key, value := range version.metadata { metadata[key] = value } } -- cgit From cd7ed7ff9e3250c10e97fe16c934b5a6151527bb Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sat, 16 Feb 2019 13:48:46 +0100 Subject: identity: add more test for serialisation and push/pull/merge + fixes --- identity/identity.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 59973489..725362f9 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -18,6 +18,8 @@ const identityRemoteRefPattern = "refs/remotes/%s/identities/" const versionEntryName = "version" const identityConfigKey = "git-bug.identity" +var ErrNonFastForwardMerge = errors.New("non fast-forward identity merge") + var _ Interface = &Identity{} type Identity struct { @@ -136,6 +138,50 @@ func read(repo repository.Repo, ref string) (*Identity, error) { return i, nil } +type StreamedIdentity struct { + Identity *Identity + Err error +} + +// ReadAllLocalIdentities read and parse all local Identity +func ReadAllLocalIdentities(repo repository.ClockedRepo) <-chan StreamedIdentity { + return readAllIdentities(repo, identityRefPattern) +} + +// ReadAllRemoteIdentities read and parse all remote Identity for a given remote +func ReadAllRemoteIdentities(repo repository.ClockedRepo, remote string) <-chan StreamedIdentity { + refPrefix := fmt.Sprintf(identityRemoteRefPattern, remote) + return readAllIdentities(repo, refPrefix) +} + +// Read and parse all available bug with a given ref prefix +func readAllIdentities(repo repository.ClockedRepo, refPrefix string) <-chan StreamedIdentity { + out := make(chan StreamedIdentity) + + go func() { + defer close(out) + + refs, err := repo.ListRefs(refPrefix) + if err != nil { + out <- StreamedIdentity{Err: err} + return + } + + for _, ref := range refs { + b, err := read(repo, ref) + + if err != nil { + out <- StreamedIdentity{Err: err} + return + } + + out <- StreamedIdentity{Identity: b} + } + }() + + return out +} + // NewFromGitUser will query the repository for user detail and // build the corresponding Identity func NewFromGitUser(repo repository.Repo) (*Identity, error) { @@ -195,6 +241,22 @@ func (i *Identity) AddVersion(version *Version) { func (i *Identity) Commit(repo repository.Repo) error { // Todo: check for mismatch between memory and commited data + needCommit := false + for _, v := range i.versions { + if v.commitHash == "" { + needCommit = true + break + } + } + + if !needCommit { + return fmt.Errorf("can't commit an identity with no pending version") + } + + if err := i.Validate(); err != nil { + return errors.Wrap(err, "can't commit an identity with invalid data") + } + for _, v := range i.versions { if v.commitHash != "" { i.lastCommit = v.commitHash @@ -299,7 +361,7 @@ func (i *Identity) Merge(repo repository.Repo, other *Identity) (bool, error) { // we have a non fast-forward merge. // as explained in the doc above, refusing to merge if i.versions[j].commitHash != otherVersion.commitHash { - return false, errors.New("non fast-forward identity merge") + return false, ErrNonFastForwardMerge } } -- cgit From d2483d83dd52365741f51eca106aa18c4e8d6420 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sat, 16 Feb 2019 17:32:30 +0100 Subject: identity: I can compile again !! --- identity/identity.go | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 725362f9..a0800bcd 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -241,15 +241,7 @@ func (i *Identity) AddVersion(version *Version) { func (i *Identity) Commit(repo repository.Repo) error { // Todo: check for mismatch between memory and commited data - needCommit := false - for _, v := range i.versions { - if v.commitHash == "" { - needCommit = true - break - } - } - - if !needCommit { + if !i.NeedCommit() { return fmt.Errorf("can't commit an identity with no pending version") } @@ -313,6 +305,23 @@ func (i *Identity) Commit(repo repository.Repo) error { return nil } +func (i *Identity) CommitAsNeeded(repo repository.Repo) error { + if !i.NeedCommit() { + return nil + } + return i.Commit(repo) +} + +func (i *Identity) NeedCommit() bool { + for _, v := range i.versions { + if v.commitHash == "" { + return true + } + } + + return false +} + // Merge will merge a different version of the same Identity // // To make sure that an Identity history can't be altered, a strict fast-forward @@ -379,6 +388,10 @@ func (i *Identity) Merge(repo repository.Repo, other *Identity) (bool, error) { func (i *Identity) Validate() error { lastTime := lamport.Time(0) + if len(i.versions) == 0 { + return fmt.Errorf("no version") + } + for _, v := range i.versions { if err := v.Validate(); err != nil { return err @@ -391,6 +404,11 @@ func (i *Identity) Validate() error { lastTime = v.time } + // The identity ID should be the hash of the first commit + if i.versions[0].commitHash != "" && string(i.versions[0].commitHash) != i.id { + return fmt.Errorf("identity id should be the first commit hash") + } + return nil } -- cgit From 864eae0d6bd0732260c0c56583bb77f9b25b60f6 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sun, 17 Feb 2019 16:12:06 +0100 Subject: identity: work on higher level now, cache, first two identity commands --- identity/identity.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index a0800bcd..35edca18 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -204,8 +204,22 @@ func NewFromGitUser(repo repository.Repo) (*Identity, error) { return NewIdentity(name, email), nil } +// IsUserIdentitySet tell if the user identity is correctly set. +func IsUserIdentitySet(repo repository.RepoCommon) (bool, error) { + configs, err := repo.ReadConfigs(identityConfigKey) + if err != nil { + return false, err + } + + if len(configs) > 1 { + return false, fmt.Errorf("multiple identity config exist") + } + + return len(configs) == 1, nil +} + // SetUserIdentity store the user identity's id in the git config -func SetUserIdentity(repo repository.RepoCommon, identity Identity) error { +func SetUserIdentity(repo repository.RepoCommon, identity *Identity) error { return repo.StoreConfig(identityConfigKey, identity.Id()) } -- cgit From 54f9838f0ab22ce5285f21cdd117ad81c737d822 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Mon, 18 Feb 2019 23:16:47 +0100 Subject: identity: working identity cache --- identity/identity.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 35edca18..193a3013 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -84,7 +84,7 @@ func ReadRemote(repo repository.Repo, remote string, id string) (*Identity, erro return read(repo, ref) } -// read will load and parse an identity frdm git +// read will load and parse an identity from git func read(repo repository.Repo, ref string) (*Identity, error) { refSplit := strings.Split(ref, "/") id := refSplit[len(refSplit)-1] -- cgit From 71f9290fdae7551f3d3ada2179ece4084304d734 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Tue, 19 Feb 2019 00:19:27 +0100 Subject: identity: store the times properly --- identity/identity.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 193a3013..b9d40967 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "github.com/pkg/errors" @@ -252,8 +253,8 @@ func (i *Identity) AddVersion(version *Version) { // Write the identity into the Repository. In particular, this ensure that // the Id is properly set. -func (i *Identity) Commit(repo repository.Repo) error { - // Todo: check for mismatch between memory and commited data +func (i *Identity) Commit(repo repository.ClockedRepo) error { + // Todo: check for mismatch between memory and commit data if !i.NeedCommit() { return fmt.Errorf("can't commit an identity with no pending version") @@ -266,10 +267,14 @@ func (i *Identity) Commit(repo repository.Repo) error { for _, v := range i.versions { if v.commitHash != "" { i.lastCommit = v.commitHash - // ignore already commited versions + // ignore already commit versions continue } + // get the times where new versions starts to be valid + v.time = repo.EditTime() + v.unixTime = time.Now().Unix() + blobHash, err := v.Write(repo) if err != nil { return err @@ -319,7 +324,7 @@ func (i *Identity) Commit(repo repository.Repo) error { return nil } -func (i *Identity) CommitAsNeeded(repo repository.Repo) error { +func (i *Identity) CommitAsNeeded(repo repository.ClockedRepo) error { if !i.NeedCommit() { return nil } -- cgit From 719303226096c905e602cb620dfdfbcf8fe106ad Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Tue, 19 Feb 2019 01:44:21 +0100 Subject: identity: fix tests --- identity/identity.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index b9d40967..809719e6 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -416,7 +416,7 @@ func (i *Identity) Validate() error { return err } - if v.time < lastTime { + if v.commitHash != "" && v.time < lastTime { return fmt.Errorf("non-chronological version (%d --> %d)", lastTime, v.time) } -- cgit From b8caddddc7aaf34b2da61c590fd1d9a0fae024fb Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sat, 23 Feb 2019 13:01:46 +0100 Subject: identity: some UX cleanup --- identity/identity.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 809719e6..114b954e 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -4,6 +4,7 @@ package identity import ( "encoding/json" "fmt" + "os" "strings" "time" @@ -20,6 +21,8 @@ const versionEntryName = "version" const identityConfigKey = "git-bug.identity" 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") var _ Interface = &Identity{} @@ -213,7 +216,7 @@ func IsUserIdentitySet(repo repository.RepoCommon) (bool, error) { } if len(configs) > 1 { - return false, fmt.Errorf("multiple identity config exist") + return false, ErrMultipleIdentitiesSet } return len(configs) == 1, nil @@ -232,11 +235,11 @@ func GetUserIdentity(repo repository.Repo) (*Identity, error) { } if len(configs) == 0 { - return nil, fmt.Errorf("no identity set") + return nil, ErrNoIdentitySet } if len(configs) > 1 { - return nil, fmt.Errorf("multiple identity config exist") + return nil, ErrMultipleIdentitiesSet } var id string @@ -244,7 +247,16 @@ func GetUserIdentity(repo repository.Repo) (*Identity, error) { id = val } - return ReadLocal(repo, id) + i, err := ReadLocal(repo, id) + if err == ErrIdentityNotExist { + innerErr := repo.RmConfigs(identityConfigKey) + if innerErr != nil { + _, _ = fmt.Fprintln(os.Stderr, errors.Wrap(innerErr, "can't clear user identity").Error()) + } + return nil, err + } + + return i, nil } func (i *Identity) AddVersion(version *Version) { -- cgit From 7a80d8f849861a6033cd0765e5d85a52b08a8854 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sun, 24 Feb 2019 14:17:52 +0100 Subject: commands: add a super-fast "user ls" command --- identity/identity.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 114b954e..d57e8ce0 100644 --- a/identity/identity.go +++ b/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 -- cgit From 46beb4b886761ff69abe2ce45c2498a4fafe50d9 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sun, 24 Feb 2019 18:49:12 +0100 Subject: identity: another round of cleanups --- identity/identity.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index d57e8ce0..720a1ebd 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -24,7 +24,7 @@ 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 ErrNoIdentitySet = errors.New("user identity first needs to be created using \"git bug user create\" or \"git bug user adopt\"") var ErrMultipleIdentitiesSet = errors.New("multiple user identities set") var _ Interface = &Identity{} @@ -391,11 +391,6 @@ func (i *Identity) Merge(repo repository.Repo, other *Identity) (bool, error) { return false, errors.New("can't merge identities that has never been stored") } - /*ancestor, err := repo.FindCommonAncestor(i.lastCommit, other.lastCommit) - if err != nil { - return false, errors.Wrap(err, "can't find common ancestor") - }*/ - modified := false for j, otherVersion := range other.versions { // if there is more version in other, take them -- cgit From c235d89d36500e58e3bcadda94e9cab06023dd55 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sun, 24 Feb 2019 23:05:03 +0100 Subject: commands: show the last modification time in "user" --- identity/identity.go | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) (limited to 'identity/identity.go') diff --git a/identity/identity.go b/identity/identity.go index 720a1ebd..3dddfaec 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/MichaelMure/git-bug/util/timestamp" "github.com/pkg/errors" "github.com/MichaelMure/git-bug/repository" @@ -33,10 +34,11 @@ type Identity struct { // Id used as unique identifier id string - lastCommit git.Hash - // all the successive version of the identity versions []*Version + + // not serialized + lastCommit git.Hash } func NewIdentity(name string, email string) *Identity { @@ -498,13 +500,6 @@ func (i *Identity) Keys() []Key { return i.lastVersion().keys } -// 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 *Identity) IsProtected() bool { - // Todo - return false -} - // ValidKeysAtTime return the set of keys valid at a given lamport time func (i *Identity) ValidKeysAtTime(time lamport.Time) []Key { var result []Key @@ -535,6 +530,23 @@ func (i *Identity) DisplayName() string { panic("invalid person data") } +// 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 *Identity) IsProtected() bool { + // Todo + return false +} + +// LastModificationLamportTime return the Lamport time at which the last version of the identity became valid. +func (i *Identity) LastModificationLamport() lamport.Time { + return i.lastVersion().time +} + +// LastModification return the timestamp at which the last version of the identity became valid. +func (i *Identity) LastModification() timestamp.Timestamp { + return timestamp.Timestamp(i.lastVersion().unixTime) +} + // SetMetadata store arbitrary metadata along the last defined Version. // If the Version has been commit to git already, it won't be overwritten. func (i *Identity) SetMetadata(key string, value string) { -- cgit