aboutsummaryrefslogtreecommitdiffstats
path: root/identity
diff options
context:
space:
mode:
Diffstat (limited to 'identity')
-rw-r--r--identity/identity.go162
-rw-r--r--identity/identity_actions.go43
-rw-r--r--identity/identity_test.go119
-rw-r--r--identity/version.go78
4 files changed, 218 insertions, 184 deletions
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
}
}
diff --git a/identity/identity_actions.go b/identity/identity_actions.go
index 69f77a2b..da7a064c 100644
--- a/identity/identity_actions.go
+++ b/identity/identity_actions.go
@@ -46,26 +46,6 @@ func Pull(repo repository.ClockedRepo, remote string) error {
}
// MergeAll will merge all the available remote 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 some of the repo accepting one
-// version, 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 MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
out := make(chan MergeResult)
@@ -85,20 +65,19 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
id := refSplitted[len(refSplitted)-1]
remoteIdentity, err := ReadLocal(repo, remoteRef)
- remoteBug, err := readBug(repo, remoteRef)
if err != nil {
- out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote bug is not readable").Error())
+ out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote identity is not readable").Error())
continue
}
// Check for error in remote data
- if err := remoteBug.Validate(); err != nil {
- out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote bug is invalid").Error())
+ if err := remoteIdentity.Validate(); err != nil {
+ out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote identity is invalid").Error())
continue
}
- localRef := bugsRefPattern + remoteBug.Id()
+ localRef := identityRefPattern + remoteIdentity.Id()
localExist, err := repo.RefExist(localRef)
if err != nil {
@@ -106,7 +85,7 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
continue
}
- // the bug is not local yet, simply create the reference
+ // the identity is not local yet, simply create the reference
if !localExist {
err := repo.CopyRef(remoteRef, localRef)
@@ -115,18 +94,18 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
return
}
- out <- newMergeStatus(MergeStatusNew, id, remoteBug)
+ out <- newMergeStatus(MergeStatusNew, id, remoteIdentity)
continue
}
- localBug, err := readBug(repo, localRef)
+ localIdentity, err := read(repo, localRef)
if err != nil {
- out <- newMergeError(errors.Wrap(err, "local bug is not readable"), id)
+ out <- newMergeError(errors.Wrap(err, "local identity is not readable"), id)
return
}
- updated, err := localBug.Merge(repo, remoteBug)
+ updated, err := localIdentity.Merge(repo, remoteIdentity)
if err != nil {
out <- newMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
@@ -134,9 +113,9 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
}
if updated {
- out <- newMergeStatus(MergeStatusUpdated, id, localBug)
+ out <- newMergeStatus(MergeStatusUpdated, id, localIdentity)
} else {
- out <- newMergeStatus(MergeStatusNothing, id, localBug)
+ out <- newMergeStatus(MergeStatusNothing, id, localIdentity)
}
}
}()
diff --git a/identity/identity_test.go b/identity/identity_test.go
index 3ab49d76..2ddb64ea 100644
--- a/identity/identity_test.go
+++ b/identity/identity_test.go
@@ -6,7 +6,6 @@ import (
"github.com/MichaelMure/git-bug/repository"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
)
// Test the commit and load of an Identity with multiple versions
@@ -16,10 +15,10 @@ func TestIdentityCommitLoad(t *testing.T) {
// single version
identity := &Identity{
- Versions: []*Version{
+ versions: []*Version{
{
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
},
},
}
@@ -32,33 +31,33 @@ func TestIdentityCommitLoad(t *testing.T) {
loaded, err := ReadLocal(mockRepo, identity.id)
assert.Nil(t, err)
commitsAreSet(t, loaded)
- equivalentIdentity(t, identity, loaded)
+ assert.Equal(t, identity, loaded)
// multiple version
identity = &Identity{
- Versions: []*Version{
+ versions: []*Version{
{
- Time: 100,
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
- Keys: []Key{
+ time: 100,
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
+ keys: []Key{
{PubKey: "pubkeyA"},
},
},
{
- Time: 200,
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
- Keys: []Key{
+ time: 200,
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
+ keys: []Key{
{PubKey: "pubkeyB"},
},
},
{
- Time: 201,
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
- Keys: []Key{
+ time: 201,
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
+ keys: []Key{
{PubKey: "pubkeyC"},
},
},
@@ -73,24 +72,24 @@ func TestIdentityCommitLoad(t *testing.T) {
loaded, err = ReadLocal(mockRepo, identity.id)
assert.Nil(t, err)
commitsAreSet(t, loaded)
- equivalentIdentity(t, identity, loaded)
+ assert.Equal(t, identity, loaded)
// add more version
identity.AddVersion(&Version{
- Time: 201,
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
- Keys: []Key{
+ time: 201,
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
+ keys: []Key{
{PubKey: "pubkeyD"},
},
})
identity.AddVersion(&Version{
- Time: 300,
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
- Keys: []Key{
+ time: 300,
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
+ keys: []Key{
{PubKey: "pubkeyE"},
},
})
@@ -103,66 +102,56 @@ func TestIdentityCommitLoad(t *testing.T) {
loaded, err = ReadLocal(mockRepo, identity.id)
assert.Nil(t, err)
commitsAreSet(t, loaded)
- equivalentIdentity(t, identity, loaded)
+ assert.Equal(t, identity, loaded)
}
func commitsAreSet(t *testing.T, identity *Identity) {
- for _, version := range identity.Versions {
+ for _, version := range identity.versions {
assert.NotEmpty(t, version.commitHash)
}
}
-func equivalentIdentity(t *testing.T, expected, actual *Identity) {
- require.Equal(t, len(expected.Versions), len(actual.Versions))
-
- for i, version := range expected.Versions {
- actual.Versions[i].commitHash = version.commitHash
- }
-
- assert.Equal(t, expected, actual)
-}
-
// Test that the correct crypto keys are returned for a given lamport time
func TestIdentity_ValidKeysAtTime(t *testing.T) {
identity := Identity{
- Versions: []*Version{
+ versions: []*Version{
{
- Time: 100,
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
- Keys: []Key{
+ time: 100,
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
+ keys: []Key{
{PubKey: "pubkeyA"},
},
},
{
- Time: 200,
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
- Keys: []Key{
+ time: 200,
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
+ keys: []Key{
{PubKey: "pubkeyB"},
},
},
{
- Time: 201,
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
- Keys: []Key{
+ time: 201,
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
+ keys: []Key{
{PubKey: "pubkeyC"},
},
},
{
- Time: 201,
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
- Keys: []Key{
+ time: 201,
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
+ keys: []Key{
{PubKey: "pubkeyD"},
},
},
{
- Time: 300,
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
- Keys: []Key{
+ time: 300,
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
+ keys: []Key{
{PubKey: "pubkeyE"},
},
},
@@ -197,8 +186,8 @@ func TestMetadata(t *testing.T) {
// try override
identity.AddVersion(&Version{
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
})
identity.SetMetadata("key1", "value2")
@@ -226,10 +215,10 @@ func TestJSON(t *testing.T) {
mockRepo := repository.NewMockRepoForTest()
identity := &Identity{
- Versions: []*Version{
+ versions: []*Version{
{
- Name: "René Descartes",
- Email: "rene.descartes@example.com",
+ name: "René Descartes",
+ email: "rene.descartes@example.com",
},
},
}
diff --git a/identity/version.go b/identity/version.go
index f8b9cc73..90bf83f2 100644
--- a/identity/version.go
+++ b/identity/version.go
@@ -24,25 +24,25 @@ type Version struct {
// The lamport time at which this version become effective
// The reference time is the bug edition lamport clock
- Time lamport.Time
+ time lamport.Time
- Name string
- Email string
- Login string
- AvatarUrl string
+ name string
+ email string
+ login string
+ avatarURL string
// The set of keys valid at that time, from this version onward, until they get removed
// in a new version. This allow to have multiple key for the same identity (e.g. one per
// device) as well as revoke key.
- Keys []Key
+ keys []Key
// This optional array is here to ensure a better randomness of the identity id to avoid collisions.
// It has no functional purpose and should be ignored.
// It is advised to fill this array if there is not enough entropy, e.g. if there is no keys.
- Nonce []byte
+ nonce []byte
// A set of arbitrary key/value to store metadata about a version or about an Identity in general.
- Metadata map[string]string
+ metadata map[string]string
}
type VersionJSON struct {
@@ -62,14 +62,14 @@ type VersionJSON struct {
func (v *Version) MarshalJSON() ([]byte, error) {
return json.Marshal(VersionJSON{
FormatVersion: formatVersion,
- Time: v.Time,
- Name: v.Name,
- Email: v.Email,
- Login: v.Login,
- AvatarUrl: v.AvatarUrl,
- Keys: v.Keys,
- Nonce: v.Nonce,
- Metadata: v.Metadata,
+ Time: v.time,
+ Name: v.name,
+ Email: v.email,
+ Login: v.login,
+ AvatarUrl: v.avatarURL,
+ Keys: v.keys,
+ Nonce: v.nonce,
+ Metadata: v.metadata,
})
}
@@ -84,56 +84,56 @@ func (v *Version) UnmarshalJSON(data []byte) error {
return fmt.Errorf("unknown format version %v", aux.FormatVersion)
}
- v.Time = aux.Time
- v.Name = aux.Name
- v.Email = aux.Email
- v.Login = aux.Login
- v.AvatarUrl = aux.AvatarUrl
- v.Keys = aux.Keys
- v.Nonce = aux.Nonce
- v.Metadata = aux.Metadata
+ v.time = aux.Time
+ v.name = aux.Name
+ v.email = aux.Email
+ v.login = aux.Login
+ v.avatarURL = aux.AvatarUrl
+ v.keys = aux.Keys
+ v.nonce = aux.Nonce
+ v.metadata = aux.Metadata
return nil
}
func (v *Version) Validate() error {
- if text.Empty(v.Name) && text.Empty(v.Login) {
+ if text.Empty(v.name) && text.Empty(v.login) {
return fmt.Errorf("either name or login should be set")
}
- if strings.Contains(v.Name, "\n") {
+ if strings.Contains(v.name, "\n") {
return fmt.Errorf("name should be a single line")
}
- if !text.Safe(v.Name) {
+ if !text.Safe(v.name) {
return fmt.Errorf("name is not fully printable")
}
- if strings.Contains(v.Login, "\n") {
+ if strings.Contains(v.login, "\n") {
return fmt.Errorf("login should be a single line")
}
- if !text.Safe(v.Login) {
+ if !text.Safe(v.login) {
return fmt.Errorf("login is not fully printable")
}
- if strings.Contains(v.Email, "\n") {
+ if strings.Contains(v.email, "\n") {
return fmt.Errorf("email should be a single line")
}
- if !text.Safe(v.Email) {
+ if !text.Safe(v.email) {
return fmt.Errorf("email is not fully printable")
}
- if v.AvatarUrl != "" && !text.ValidUrl(v.AvatarUrl) {
+ if v.avatarURL != "" && !text.ValidUrl(v.avatarURL) {
return fmt.Errorf("avatarUrl is not a valid URL")
}
- if len(v.Nonce) > 64 {
+ if len(v.nonce) > 64 {
return fmt.Errorf("nonce is too big")
}
- for _, k := range v.Keys {
+ for _, k := range v.keys {
if err := k.Validate(); err != nil {
return errors.Wrap(err, "invalid key")
}
@@ -178,20 +178,20 @@ func makeNonce(len int) []byte {
// SetMetadata store arbitrary metadata about a version or an Identity in general
// If the Version has been commit to git already, it won't be overwritten.
func (v *Version) SetMetadata(key string, value string) {
- if v.Metadata == nil {
- v.Metadata = make(map[string]string)
+ if v.metadata == nil {
+ v.metadata = make(map[string]string)
}
- v.Metadata[key] = value
+ v.metadata[key] = value
}
// GetMetadata retrieve arbitrary metadata about the Version
func (v *Version) GetMetadata(key string) (string, bool) {
- val, ok := v.Metadata[key]
+ val, ok := v.metadata[key]
return val, ok
}
// AllMetadata return all metadata for this Identity
func (v *Version) AllMetadata() map[string]string {
- return v.Metadata
+ return v.metadata
}