aboutsummaryrefslogtreecommitdiffstats
path: root/identity
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2022-08-18 23:34:05 +0200
committerMichael Muré <batolettre@gmail.com>2022-08-18 23:44:06 +0200
commit5511c230b678a181cc596238bf6669428d1b1902 (patch)
tree8701efc87732439f993eb4f1d00585fc419b87ab /identity
parent5ca686b59751e3c87740b84108c54fc675a074cf (diff)
downloadgit-bug-5511c230b678a181cc596238bf6669428d1b1902.tar.gz
move {bug,identity} to /entities, move input to /commands
Diffstat (limited to 'identity')
-rw-r--r--identity/common.go37
-rw-r--r--identity/identity.go620
-rw-r--r--identity/identity_actions.go125
-rw-r--r--identity/identity_actions_test.go157
-rw-r--r--identity/identity_stub.go101
-rw-r--r--identity/identity_stub_test.go26
-rw-r--r--identity/identity_test.go292
-rw-r--r--identity/identity_user.go68
-rw-r--r--identity/interface.go62
-rw-r--r--identity/key.go234
-rw-r--r--identity/key_test.go60
-rw-r--r--identity/resolver.go34
-rw-r--r--identity/version.go273
-rw-r--r--identity/version_test.go78
14 files changed, 0 insertions, 2167 deletions
diff --git a/identity/common.go b/identity/common.go
deleted file mode 100644
index 5c6445e9..00000000
--- a/identity/common.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package identity
-
-import (
- "encoding/json"
- "errors"
- "fmt"
-
- "github.com/MichaelMure/git-bug/entity"
-)
-
-var ErrIdentityNotExist = errors.New("identity doesn't exist")
-
-func NewErrMultipleMatch(matching []entity.Id) *entity.ErrMultipleMatch {
- return entity.NewErrMultipleMatch("identity", matching)
-}
-
-// Custom unmarshaling function to allow package user to delegate
-// the decoding of an Identity and distinguish between an Identity
-// and a Bare.
-//
-// If the given message has a "id" field, it's considered being a proper Identity.
-func UnmarshalJSON(raw json.RawMessage) (Interface, error) {
- aux := &IdentityStub{}
-
- // First try to decode and load as a normal Identity
- err := json.Unmarshal(raw, &aux)
- if err == nil && aux.Id() != "" {
- return aux, nil
- }
-
- // abort if we have an error other than the wrong type
- if _, ok := err.(*json.UnmarshalTypeError); err != nil && !ok {
- return nil, err
- }
-
- return nil, fmt.Errorf("unknown identity type")
-}
diff --git a/identity/identity.go b/identity/identity.go
deleted file mode 100644
index 0a7642af..00000000
--- a/identity/identity.go
+++ /dev/null
@@ -1,620 +0,0 @@
-// Package identity contains the identity data model and low-level related functions
-package identity
-
-import (
- "encoding/json"
- "fmt"
- "reflect"
-
- "github.com/pkg/errors"
-
- "github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/repository"
- "github.com/MichaelMure/git-bug/util/lamport"
- "github.com/MichaelMure/git-bug/util/timestamp"
-)
-
-const identityRefPattern = "refs/identities/"
-const identityRemoteRefPattern = "refs/remotes/%s/identities/"
-const versionEntryName = "version"
-const identityConfigKey = "git-bug.identity"
-
-var ErrNonFastForwardMerge = errors.New("non fast-forward identity merge")
-var ErrNoIdentitySet = errors.New("No identity is set.\n" +
- "To interact with bugs, an identity first needs to be created using " +
- "\"git bug user create\"")
-var ErrMultipleIdentitiesSet = errors.New("multiple user identities set")
-
-func NewErrMultipleMatchIdentity(matching []entity.Id) *entity.ErrMultipleMatch {
- return entity.NewErrMultipleMatch("identity", matching)
-}
-
-var _ Interface = &Identity{}
-var _ entity.Interface = &Identity{}
-
-type Identity struct {
- // all the successive version of the identity
- versions []*version
-}
-
-func NewIdentity(repo repository.RepoClock, name string, email string) (*Identity, error) {
- return NewIdentityFull(repo, name, email, "", "", nil)
-}
-
-func NewIdentityFull(repo repository.RepoClock, name string, email string, login string, avatarUrl string, keys []*Key) (*Identity, error) {
- v, err := newVersion(repo, name, email, login, avatarUrl, keys)
- if err != nil {
- return nil, err
- }
- return &Identity{
- versions: []*version{v},
- }, nil
-}
-
-// NewFromGitUser will query the repository for user detail and
-// build the corresponding Identity
-func NewFromGitUser(repo repository.ClockedRepo) (*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(repo, name, email)
-}
-
-// MarshalJSON will only serialize the id
-func (i *Identity) MarshalJSON() ([]byte, error) {
- return json.Marshal(&IdentityStub{
- 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 {
- panic("identity should be loaded with identity.UnmarshalJSON")
-}
-
-// ReadLocal load a local Identity from the identities data available in git
-func ReadLocal(repo repository.Repo, id entity.Id) (*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 from git
-func read(repo repository.Repo, ref string) (*Identity, error) {
- id := entity.RefToId(ref)
-
- if err := id.Validate(); err != nil {
- return nil, errors.Wrap(err, "invalid ref")
- }
-
- hashes, err := repo.ListCommits(ref)
- if err != nil {
- return nil, ErrIdentityNotExist
- }
- if len(hashes) == 0 {
- return nil, fmt.Errorf("empty identity")
- }
-
- i := &Identity{}
-
- for _, hash := range hashes {
- entries, err := repo.ReadTree(hash)
- if err != nil {
- return nil, errors.Wrap(err, "can't list git tree entries")
- }
- if len(entries) != 1 {
- return nil, fmt.Errorf("invalid identity data at hash %s", hash)
- }
-
- entry := entries[0]
- if entry.Name != versionEntryName {
- return nil, fmt.Errorf("invalid identity data at hash %s", hash)
- }
-
- data, err := repo.ReadData(entry.Hash)
- if err != nil {
- return nil, errors.Wrap(err, "failed to read git blob data")
- }
-
- var version version
- err = json.Unmarshal(data, &version)
- if err != nil {
- return nil, errors.Wrapf(err, "failed to decode Identity version json %s", hash)
- }
-
- // tag the version with the commit hash
- version.commitHash = hash
-
- i.versions = append(i.versions, &version)
- }
-
- if id != i.versions[0].Id() {
- return nil, fmt.Errorf("identity ID doesn't math the first version ID")
- }
-
- return i, nil
-}
-
-// ListLocalIds list all the available local identity ids
-func ListLocalIds(repo repository.Repo) ([]entity.Id, error) {
- refs, err := repo.ListRefs(identityRefPattern)
- if err != nil {
- return nil, err
- }
-
- return entity.RefsToIds(refs), nil
-}
-
-// RemoveIdentity will remove a local identity from its entity.Id
-func RemoveIdentity(repo repository.ClockedRepo, id entity.Id) error {
- var fullMatches []string
-
- refs, err := repo.ListRefs(identityRefPattern + id.String())
- if err != nil {
- return err
- }
- if len(refs) > 1 {
- return NewErrMultipleMatchIdentity(entity.RefsToIds(refs))
- }
- if len(refs) == 1 {
- // we have the identity locally
- fullMatches = append(fullMatches, refs[0])
- }
-
- remotes, err := repo.GetRemotes()
- if err != nil {
- return err
- }
-
- for remote := range remotes {
- remotePrefix := fmt.Sprintf(identityRemoteRefPattern+id.String(), remote)
- remoteRefs, err := repo.ListRefs(remotePrefix)
- if err != nil {
- return err
- }
- if len(remoteRefs) > 1 {
- return NewErrMultipleMatchIdentity(entity.RefsToIds(refs))
- }
- if len(remoteRefs) == 1 {
- // found the identity in a remote
- fullMatches = append(fullMatches, remoteRefs[0])
- }
- }
-
- if len(fullMatches) == 0 {
- return ErrIdentityNotExist
- }
-
- for _, ref := range fullMatches {
- err = repo.RemoveRef(ref)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-type StreamedIdentity struct {
- Identity *Identity
- Err error
-}
-
-// ReadAllLocal read and parse all local Identity
-func ReadAllLocal(repo repository.ClockedRepo) <-chan StreamedIdentity {
- return readAll(repo, identityRefPattern)
-}
-
-// ReadAllRemote read and parse all remote Identity for a given remote
-func ReadAllRemote(repo repository.ClockedRepo, remote string) <-chan StreamedIdentity {
- refPrefix := fmt.Sprintf(identityRemoteRefPattern, remote)
- return readAll(repo, refPrefix)
-}
-
-// readAll read and parse all available bug with a given ref prefix
-func readAll(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
-}
-
-type Mutator struct {
- Name string
- Login string
- Email string
- AvatarUrl string
- Keys []*Key
-}
-
-// Mutate allow to create a new version of the Identity in one go
-func (i *Identity) Mutate(repo repository.RepoClock, f func(orig *Mutator)) error {
- copyKeys := func(keys []*Key) []*Key {
- result := make([]*Key, len(keys))
- for i, key := range keys {
- result[i] = key.Clone()
- }
- return result
- }
-
- orig := Mutator{
- Name: i.Name(),
- Email: i.Email(),
- Login: i.Login(),
- AvatarUrl: i.AvatarUrl(),
- Keys: copyKeys(i.Keys()),
- }
- mutated := orig
- mutated.Keys = copyKeys(orig.Keys)
-
- f(&mutated)
-
- if reflect.DeepEqual(orig, mutated) {
- return nil
- }
-
- v, err := newVersion(repo,
- mutated.Name,
- mutated.Email,
- mutated.Login,
- mutated.AvatarUrl,
- mutated.Keys,
- )
- if err != nil {
- return err
- }
-
- i.versions = append(i.versions, v)
- return nil
-}
-
-// Write the identity into the Repository. In particular, this ensure that
-// the Id is properly set.
-func (i *Identity) Commit(repo repository.ClockedRepo) error {
- if !i.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")
- }
-
- var lastCommit repository.Hash
- for _, v := range i.versions {
- if v.commitHash != "" {
- lastCommit = v.commitHash
- // ignore already commit 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 repository.Hash
- if lastCommit != "" {
- commitHash, err = repo.StoreCommit(treeHash, lastCommit)
- } else {
- commitHash, err = repo.StoreCommit(treeHash)
- }
- if err != nil {
- return err
- }
-
- lastCommit = commitHash
- v.commitHash = commitHash
- }
-
- ref := fmt.Sprintf("%s%s", identityRefPattern, i.Id().String())
- return repo.UpdateRef(ref, lastCommit)
-}
-
-func (i *Identity) CommitAsNeeded(repo repository.ClockedRepo) 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
-// 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 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 when 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 its 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")
- }
-
- modified := false
- var lastCommit repository.Hash
- 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)
- 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, ErrNonFastForwardMerge
- }
- }
-
- if modified {
- err := repo.UpdateRef(identityRefPattern+i.Id().String(), lastCommit)
- if err != nil {
- return false, err
- }
- }
-
- return false, nil
-}
-
-// Validate check if the Identity data is valid
-func (i *Identity) Validate() error {
- lastTimes := make(map[string]lamport.Time)
-
- if len(i.versions) == 0 {
- return fmt.Errorf("no version")
- }
-
- for _, v := range i.versions {
- if err := v.Validate(); err != nil {
- return err
- }
-
- // check for always increasing lamport time
- // check that a new version didn't drop a clock
- for name, previous := range lastTimes {
- if now, ok := v.times[name]; ok {
- if now < previous {
- return fmt.Errorf("non-chronological lamport clock %s (%d --> %d)", name, previous, now)
- }
- } else {
- return fmt.Errorf("version has less lamport clocks than before (missing %s)", name)
- }
- }
-
- for name, now := range v.times {
- lastTimes[name] = now
- }
- }
-
- 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() entity.Id {
- // id is the id of the first version
- return i.versions[0].Id()
-}
-
-// Name return the last version of the name
-func (i *Identity) Name() string {
- return i.lastVersion().name
-}
-
-// 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")
-}
-
-// 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
-}
-
-// AvatarUrl return the last version of the Avatar URL
-func (i *Identity) AvatarUrl() string {
- return i.lastVersion().avatarURL
-}
-
-// Keys return the last version of the valid keys
-func (i *Identity) Keys() []*Key {
- return i.lastVersion().keys
-}
-
-// SigningKey return the key that should be used to sign new messages. If no key is available, return nil.
-func (i *Identity) SigningKey(repo repository.RepoKeyring) (*Key, error) {
- keys := i.Keys()
- for _, key := range keys {
- err := key.ensurePrivateKey(repo)
- if err == errNoPrivateKey {
- continue
- }
- if err != nil {
- return nil, err
- }
- return key, nil
- }
- return nil, nil
-}
-
-// ValidKeysAtTime return the set of keys valid at a given lamport time
-func (i *Identity) ValidKeysAtTime(clockName string, time lamport.Time) []*Key {
- var result []*Key
-
- var lastTime lamport.Time
- for _, v := range i.versions {
- refTime, ok := v.times[clockName]
- if !ok {
- refTime = lastTime
- }
- lastTime = refTime
-
- if refTime > time {
- return result
- }
-
- result = v.keys
- }
-
- return result
-}
-
-// 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)
-}
-
-// LastModificationLamports return the lamport times at which the last version of the identity became valid.
-func (i *Identity) LastModificationLamports() map[string]lamport.Time {
- return i.lastVersion().times
-}
-
-// 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
-}
-
-// SetMetadata store arbitrary metadata along the last not-commit version.
-// If the version has been commit to git already, a new identical version is added and will need to be
-// commit.
-func (i *Identity) SetMetadata(key string, value string) {
- // once commit, data is immutable so we create a new version
- if i.lastVersion().commitHash != "" {
- i.versions = append(i.versions, i.lastVersion().Clone())
- }
- // if Id() has been called, we can't change the first version anymore, so we create a new version
- if len(i.versions) == 1 && i.versions[0].id != entity.UnsetId && i.versions[0].id != "" {
- i.versions = append(i.versions, i.lastVersion().Clone())
- }
-
- 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
-}
diff --git a/identity/identity_actions.go b/identity/identity_actions.go
deleted file mode 100644
index b58bb2d9..00000000
--- a/identity/identity_actions.go
+++ /dev/null
@@ -1,125 +0,0 @@
-package identity
-
-import (
- "fmt"
- "strings"
-
- "github.com/pkg/errors"
-
- "github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/repository"
-)
-
-// Fetch retrieve updates from a remote
-// This does not change the local identities state
-func Fetch(repo repository.Repo, remote string) (string, error) {
- return repo.FetchRefs(remote, "identities")
-}
-
-// Push update a remote with the local changes
-func Push(repo repository.Repo, remote string) (string, error) {
- return repo.PushRefs(remote, "identities")
-}
-
-// Pull will do a Fetch + MergeAll
-// This function will return an error if a merge fail
-func Pull(repo repository.ClockedRepo, remote string) error {
- _, err := Fetch(repo, remote)
- if err != nil {
- return err
- }
-
- for merge := range MergeAll(repo, remote) {
- if merge.Err != nil {
- return merge.Err
- }
- if merge.Status == entity.MergeStatusInvalid {
- return errors.Errorf("merge failure: %s", merge.Reason)
- }
- }
-
- return nil
-}
-
-// MergeAll will merge all the available remote identity
-func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeResult {
- out := make(chan entity.MergeResult)
-
- go func() {
- defer close(out)
-
- remoteRefSpec := fmt.Sprintf(identityRemoteRefPattern, remote)
- remoteRefs, err := repo.ListRefs(remoteRefSpec)
-
- if err != nil {
- out <- entity.MergeResult{Err: err}
- return
- }
-
- for _, remoteRef := range remoteRefs {
- refSplit := strings.Split(remoteRef, "/")
- id := entity.Id(refSplit[len(refSplit)-1])
-
- if err := id.Validate(); err != nil {
- out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error())
- continue
- }
-
- remoteIdentity, err := read(repo, remoteRef)
-
- if err != nil {
- out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote identity is not readable").Error())
- continue
- }
-
- // Check for error in remote data
- if err := remoteIdentity.Validate(); err != nil {
- out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "remote identity is invalid").Error())
- continue
- }
-
- localRef := identityRefPattern + remoteIdentity.Id().String()
- localExist, err := repo.RefExist(localRef)
-
- if err != nil {
- out <- entity.NewMergeError(err, id)
- continue
- }
-
- // the identity is not local yet, simply create the reference
- if !localExist {
- err := repo.CopyRef(remoteRef, localRef)
-
- if err != nil {
- out <- entity.NewMergeError(err, id)
- return
- }
-
- out <- entity.NewMergeNewStatus(id, remoteIdentity)
- continue
- }
-
- localIdentity, err := read(repo, localRef)
-
- if err != nil {
- out <- entity.NewMergeError(errors.Wrap(err, "local identity is not readable"), id)
- return
- }
-
- updated, err := localIdentity.Merge(repo, remoteIdentity)
-
- if err != nil {
- out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
- return
- }
-
- if updated {
- out <- entity.NewMergeUpdatedStatus(id, localIdentity)
- } else {
- out <- entity.NewMergeNothingStatus(id)
- }
- }
- }()
-
- return out
-}
diff --git a/identity/identity_actions_test.go b/identity/identity_actions_test.go
deleted file mode 100644
index 351fb7a4..00000000
--- a/identity/identity_actions_test.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package identity
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
-
- "github.com/MichaelMure/git-bug/repository"
-)
-
-func TestIdentityPushPull(t *testing.T) {
- repoA, repoB, _ := repository.SetupGoGitReposAndRemote(t)
-
- identity1, err := NewIdentity(repoA, "name1", "email1")
- require.NoError(t, err)
- err = identity1.Commit(repoA)
- require.NoError(t, err)
-
- // A --> remote --> B
- _, err = Push(repoA, "origin")
- require.NoError(t, err)
-
- err = Pull(repoB, "origin")
- require.NoError(t, err)
-
- identities := allIdentities(t, ReadAllLocal(repoB))
-
- if len(identities) != 1 {
- t.Fatal("Unexpected number of bugs")
- }
-
- // B --> remote --> A
- identity2, err := NewIdentity(repoB, "name2", "email2")
- require.NoError(t, err)
- err = identity2.Commit(repoB)
- require.NoError(t, err)
-
- _, err = Push(repoB, "origin")
- require.NoError(t, err)
-
- err = Pull(repoA, "origin")
- require.NoError(t, err)
-
- identities = allIdentities(t, ReadAllLocal(repoA))
-
- if len(identities) != 2 {
- t.Fatal("Unexpected number of bugs")
- }
-
- // Update both
-
- err = identity1.Mutate(repoA, func(orig *Mutator) {
- orig.Name = "name1b"
- orig.Email = "email1b"
- })
- require.NoError(t, err)
- err = identity1.Commit(repoA)
- require.NoError(t, err)
-
- err = identity2.Mutate(repoB, func(orig *Mutator) {
- orig.Name = "name2b"
- orig.Email = "email2b"
- })
- require.NoError(t, err)
- err = identity2.Commit(repoB)
- require.NoError(t, err)
-
- // A --> remote --> B
-
- _, err = Push(repoA, "origin")
- require.NoError(t, err)
-
- err = Pull(repoB, "origin")
- require.NoError(t, err)
-
- identities = allIdentities(t, ReadAllLocal(repoB))
-
- if len(identities) != 2 {
- t.Fatal("Unexpected number of bugs")
- }
-
- // B --> remote --> A
-
- _, err = Push(repoB, "origin")
- require.NoError(t, err)
-
- err = Pull(repoA, "origin")
- require.NoError(t, err)
-
- identities = allIdentities(t, ReadAllLocal(repoA))
-
- if len(identities) != 2 {
- t.Fatal("Unexpected number of bugs")
- }
-
- // Concurrent update
-
- err = identity1.Mutate(repoA, func(orig *Mutator) {
- orig.Name = "name1c"
- orig.Email = "email1c"
- })
- require.NoError(t, err)
- err = identity1.Commit(repoA)
- require.NoError(t, err)
-
- identity1B, err := ReadLocal(repoB, identity1.Id())
- require.NoError(t, err)
-
- err = identity1B.Mutate(repoB, func(orig *Mutator) {
- orig.Name = "name1concurrent"
- orig.Email = "name1concurrent"
- })
- require.NoError(t, err)
- err = identity1B.Commit(repoB)
- require.NoError(t, err)
-
- // A --> remote --> B
-
- _, err = Push(repoA, "origin")
- require.NoError(t, err)
-
- // Pulling a non-fast-forward update should fail
- err = Pull(repoB, "origin")
- require.Error(t, err)
-
- identities = allIdentities(t, ReadAllLocal(repoB))
-
- if len(identities) != 2 {
- t.Fatal("Unexpected number of bugs")
- }
-
- // B --> remote --> A
-
- // Pushing a non-fast-forward update should fail
- _, err = Push(repoB, "origin")
- require.Error(t, err)
-
- err = Pull(repoA, "origin")
- require.NoError(t, err)
-
- identities = allIdentities(t, ReadAllLocal(repoA))
-
- if len(identities) != 2 {
- t.Fatal("Unexpected number of bugs")
- }
-}
-
-func allIdentities(t testing.TB, identities <-chan StreamedIdentity) []*Identity {
- var result []*Identity
- for streamed := range identities {
- if streamed.Err != nil {
- t.Fatal(streamed.Err)
- }
- result = append(result, streamed.Identity)
- }
- return result
-}
diff --git a/identity/identity_stub.go b/identity/identity_stub.go
deleted file mode 100644
index fb5c90a5..00000000
--- a/identity/identity_stub.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package identity
-
-import (
- "encoding/json"
-
- "github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/repository"
- "github.com/MichaelMure/git-bug/util/lamport"
- "github.com/MichaelMure/git-bug/util/timestamp"
-)
-
-var _ Interface = &IdentityStub{}
-
-// IdentityStub is an almost empty Identity, holding only the id.
-// When a normal Identity is serialized into JSON, only the id is serialized.
-// All the other data are stored in git in a chain of commit + a ref.
-// When this JSON is deserialized, an IdentityStub is returned instead, to be replaced
-// later by the proper Identity, loaded from the Repo.
-type IdentityStub struct {
- id entity.Id
-}
-
-func (i *IdentityStub) MarshalJSON() ([]byte, error) {
- // TODO: add a type marker
- return json.Marshal(struct {
- Id entity.Id `json:"id"`
- }{
- Id: i.id,
- })
-}
-
-func (i *IdentityStub) UnmarshalJSON(data []byte) error {
- aux := struct {
- Id entity.Id `json:"id"`
- }{}
-
- if err := json.Unmarshal(data, &aux); err != nil {
- return err
- }
-
- i.id = aux.Id
-
- return nil
-}
-
-// Id return the Identity identifier
-func (i *IdentityStub) Id() entity.Id {
- return i.id
-}
-
-func (IdentityStub) Name() string {
- panic("identities needs to be properly loaded with identity.ReadLocal()")
-}
-
-func (IdentityStub) DisplayName() string {
- panic("identities needs to be properly loaded with identity.ReadLocal()")
-}
-
-func (IdentityStub) Email() string {
- panic("identities needs to be properly loaded with identity.ReadLocal()")
-}
-
-func (IdentityStub) Login() string {
- panic("identities needs to be properly loaded with identity.ReadLocal()")
-}
-
-func (IdentityStub) AvatarUrl() string {
- panic("identities needs to be properly loaded with identity.ReadLocal()")
-}
-
-func (IdentityStub) Keys() []*Key {
- panic("identities needs to be properly loaded with identity.ReadLocal()")
-}
-
-func (i *IdentityStub) SigningKey(repo repository.RepoKeyring) (*Key, error) {
- panic("identities needs to be properly loaded with identity.ReadLocal()")
-}
-
-func (IdentityStub) ValidKeysAtTime(_ string, _ lamport.Time) []*Key {
- panic("identities needs to be properly loaded with identity.ReadLocal()")
-}
-
-func (i *IdentityStub) LastModification() timestamp.Timestamp {
- panic("identities needs to be properly loaded with identity.ReadLocal()")
-}
-
-func (i *IdentityStub) LastModificationLamports() map[string]lamport.Time {
- panic("identities needs to be properly loaded with identity.ReadLocal()")
-}
-
-func (IdentityStub) IsProtected() bool {
- panic("identities needs to be properly loaded with identity.ReadLocal()")
-}
-
-func (IdentityStub) Validate() error {
- panic("identities needs to be properly loaded with identity.ReadLocal()")
-}
-
-func (i *IdentityStub) NeedCommit() bool {
- return false
-}
diff --git a/identity/identity_stub_test.go b/identity/identity_stub_test.go
deleted file mode 100644
index b01a718c..00000000
--- a/identity/identity_stub_test.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package identity
-
-import (
- "encoding/json"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestIdentityStubSerialize(t *testing.T) {
- before := &IdentityStub{
- id: "id1234",
- }
-
- data, err := json.Marshal(before)
- assert.NoError(t, err)
-
- var after IdentityStub
- err = json.Unmarshal(data, &after)
- assert.NoError(t, err)
-
- // enforce creating the Id
- before.Id()
-
- assert.Equal(t, before, &after)
-}
diff --git a/identity/identity_test.go b/identity/identity_test.go
deleted file mode 100644
index f0c3bbe9..00000000
--- a/identity/identity_test.go
+++ /dev/null
@@ -1,292 +0,0 @@
-package identity
-
-import (
- "encoding/json"
- "testing"
-
- "github.com/stretchr/testify/require"
-
- "github.com/MichaelMure/git-bug/repository"
- "github.com/MichaelMure/git-bug/util/lamport"
-)
-
-// Test the commit and load of an Identity with multiple versions
-func TestIdentityCommitLoad(t *testing.T) {
- repo := makeIdentityTestRepo(t)
-
- // single version
-
- identity, err := NewIdentity(repo, "René Descartes", "rene.descartes@example.com")
- require.NoError(t, err)
-
- idBeforeCommit := identity.Id()
-
- err = identity.Commit(repo)
- require.NoError(t, err)
-
- commitsAreSet(t, identity)
- require.NotEmpty(t, identity.Id())
- require.Equal(t, idBeforeCommit, identity.Id())
- require.Equal(t, idBeforeCommit, identity.versions[0].Id())
-
- loaded, err := ReadLocal(repo, identity.Id())
- require.NoError(t, err)
- commitsAreSet(t, loaded)
- require.Equal(t, identity, loaded)
-
- // multiple versions
-
- identity, err = NewIdentityFull(repo, "René Descartes", "rene.descartes@example.com", "", "", []*Key{generatePublicKey()})
- require.NoError(t, err)
-
- idBeforeCommit = identity.Id()
-
- err = identity.Mutate(repo, func(orig *Mutator) {
- orig.Keys = []*Key{generatePublicKey()}
- })
- require.NoError(t, err)
-
- err = identity.Mutate(repo, func(orig *Mutator) {
- orig.Keys = []*Key{generatePublicKey()}
- })
- require.NoError(t, err)
-
- require.Equal(t, idBeforeCommit, identity.Id())
-
- err = identity.Commit(repo)
- require.NoError(t, err)
-
- commitsAreSet(t, identity)
- require.NotEmpty(t, identity.Id())
- require.Equal(t, idBeforeCommit, identity.Id())
- require.Equal(t, idBeforeCommit, identity.versions[0].Id())
-
- loaded, err = ReadLocal(repo, identity.Id())
- require.NoError(t, err)
- commitsAreSet(t, loaded)
- require.Equal(t, identity, loaded)
-
- // add more version
-
- err = identity.Mutate(repo, func(orig *Mutator) {
- orig.Email = "rene@descartes.com"
- orig.Keys = []*Key{generatePublicKey()}
- })
- require.NoError(t, err)
-
- err = identity.Mutate(repo, func(orig *Mutator) {
- orig.Email = "rene@descartes.com"
- orig.Keys = []*Key{generatePublicKey(), generatePublicKey()}
- })
- require.NoError(t, err)
-
- err = identity.Commit(repo)
- require.NoError(t, err)
-
- commitsAreSet(t, identity)
- require.NotEmpty(t, identity.Id())
- require.Equal(t, idBeforeCommit, identity.Id())
- require.Equal(t, idBeforeCommit, identity.versions[0].Id())
-
- loaded, err = ReadLocal(repo, identity.Id())
- require.NoError(t, err)
- commitsAreSet(t, loaded)
- require.Equal(t, identity, loaded)
-}
-
-func TestIdentityMutate(t *testing.T) {
- repo := makeIdentityTestRepo(t)
-
- identity, err := NewIdentity(repo, "René Descartes", "rene.descartes@example.com")
- require.NoError(t, err)
-
- require.Len(t, identity.versions, 1)
-
- err = identity.Mutate(repo, func(orig *Mutator) {
- orig.Email = "rene@descartes.fr"
- orig.Name = "René"
- orig.Login = "rene"
- })
- require.NoError(t, err)
-
- require.Len(t, identity.versions, 2)
- require.Equal(t, identity.Email(), "rene@descartes.fr")
- require.Equal(t, identity.Name(), "René")
- require.Equal(t, identity.Login(), "rene")
-}
-
-func commitsAreSet(t *testing.T, identity *Identity) {
- for _, version := range identity.versions {
- require.NotEmpty(t, version.commitHash)
- }
-}
-
-// Test that the correct crypto keys are returned for a given lamport time
-func TestIdentity_ValidKeysAtTime(t *testing.T) {
- pubKeyA := generatePublicKey()
- pubKeyB := generatePublicKey()
- pubKeyC := generatePublicKey()
- pubKeyD := generatePublicKey()
- pubKeyE := generatePublicKey()
-
- identity := Identity{
- versions: []*version{
- {
- times: map[string]lamport.Time{"foo": 100},
- keys: []*Key{pubKeyA},
- },
- {
- times: map[string]lamport.Time{"foo": 200},
- keys: []*Key{pubKeyB},
- },
- {
- times: map[string]lamport.Time{"foo": 201},
- keys: []*Key{pubKeyC},
- },
- {
- times: map[string]lamport.Time{"foo": 201},
- keys: []*Key{pubKeyD},
- },
- {
- times: map[string]lamport.Time{"foo": 300},
- keys: []*Key{pubKeyE},
- },
- },
- }
-
- require.Nil(t, identity.ValidKeysAtTime("foo", 10))
- require.Equal(t, identity.ValidKeysAtTime("foo", 100), []*Key{pubKeyA})
- require.Equal(t, identity.ValidKeysAtTime("foo", 140), []*Key{pubKeyA})
- require.Equal(t, identity.ValidKeysAtTime("foo", 200), []*Key{pubKeyB})
- require.Equal(t, identity.ValidKeysAtTime("foo", 201), []*Key{pubKeyD})
- require.Equal(t, identity.ValidKeysAtTime("foo", 202), []*Key{pubKeyD})
- require.Equal(t, identity.ValidKeysAtTime("foo", 300), []*Key{pubKeyE})
- require.Equal(t, identity.ValidKeysAtTime("foo", 3000), []*Key{pubKeyE})
-}
-
-// Test the immutable or mutable metadata search
-func TestMetadata(t *testing.T) {
- repo := makeIdentityTestRepo(t)
-
- identity, err := NewIdentity(repo, "René Descartes", "rene.descartes@example.com")
- require.NoError(t, err)
-
- identity.SetMetadata("key1", "value1")
- assertHasKeyValue(t, identity.ImmutableMetadata(), "key1", "value1")
- assertHasKeyValue(t, identity.MutableMetadata(), "key1", "value1")
-
- err = identity.Commit(repo)
- require.NoError(t, err)
-
- assertHasKeyValue(t, identity.ImmutableMetadata(), "key1", "value1")
- assertHasKeyValue(t, identity.MutableMetadata(), "key1", "value1")
-
- // try override
- err = identity.Mutate(repo, func(orig *Mutator) {
- orig.Email = "rene@descartes.fr"
- })
- require.NoError(t, err)
-
- identity.SetMetadata("key1", "value2")
- assertHasKeyValue(t, identity.ImmutableMetadata(), "key1", "value1")
- assertHasKeyValue(t, identity.MutableMetadata(), "key1", "value2")
-
- err = identity.Commit(repo)
- require.NoError(t, err)
-
- // reload
- loaded, err := ReadLocal(repo, identity.Id())
- require.NoError(t, err)
-
- assertHasKeyValue(t, loaded.ImmutableMetadata(), "key1", "value1")
- assertHasKeyValue(t, loaded.MutableMetadata(), "key1", "value2")
-
- // set metadata after commit
- versionCount := len(identity.versions)
- identity.SetMetadata("foo", "bar")
- require.True(t, identity.NeedCommit())
- require.Len(t, identity.versions, versionCount+1)
-
- err = identity.Commit(repo)
- require.NoError(t, err)
- require.Len(t, identity.versions, versionCount+1)
-}
-
-func assertHasKeyValue(t *testing.T, metadata map[string]string, key, value string) {
- val, ok := metadata[key]
- require.True(t, ok)
- require.Equal(t, val, value)
-}
-
-func TestJSON(t *testing.T) {
- repo := makeIdentityTestRepo(t)
-
- identity, err := NewIdentity(repo, "René Descartes", "rene.descartes@example.com")
- require.NoError(t, err)
-
- // commit to make sure we have an Id
- err = identity.Commit(repo)
- require.NoError(t, err)
- require.NotEmpty(t, identity.Id())
-
- // serialize
- data, err := json.Marshal(identity)
- require.NoError(t, err)
-
- // deserialize, got a IdentityStub with the same id
- var i Interface
- i, err = UnmarshalJSON(data)
- require.NoError(t, err)
- require.Equal(t, identity.Id(), i.Id())
-
- // make sure we can load the identity properly
- i, err = ReadLocal(repo, i.Id())
- require.NoError(t, err)
-}
-
-func TestIdentityRemove(t *testing.T) {
- repo := repository.CreateGoGitTestRepo(t, false)
- remoteA := repository.CreateGoGitTestRepo(t, true)
- remoteB := repository.CreateGoGitTestRepo(t, true)
-
- err := repo.AddRemote("remoteA", remoteA.GetLocalRemote())
- require.NoError(t, err)
-
- err = repo.AddRemote("remoteB", remoteB.GetLocalRemote())
- require.NoError(t, err)
-
- // generate an identity for testing
- rene, err := NewIdentity(repo, "René Descartes", "rene@descartes.fr")
- require.NoError(t, err)
-
- err = rene.Commit(repo)
- require.NoError(t, err)
-
- _, err = Push(repo, "remoteA")
- require.NoError(t, err)
-
- _, err = Push(repo, "remoteB")
- require.NoError(t, err)
-
- _, err = Fetch(repo, "remoteA")
- require.NoError(t, err)
-
- _, err = Fetch(repo, "remoteB")
- require.NoError(t, err)
-
- err = RemoveIdentity(repo, rene.Id())
- require.NoError(t, err)
-
- _, err = ReadLocal(repo, rene.Id())
- require.Error(t, ErrIdentityNotExist, err)
-
- _, err = ReadRemote(repo, "remoteA", string(rene.Id()))
- require.Error(t, ErrIdentityNotExist, err)
-
- _, err = ReadRemote(repo, "remoteB", string(rene.Id()))
- require.Error(t, ErrIdentityNotExist, err)
-
- ids, err := ListLocalIds(repo)
- require.NoError(t, err)
- require.Len(t, ids, 0)
-}
diff --git a/identity/identity_user.go b/identity/identity_user.go
deleted file mode 100644
index cd67459e..00000000
--- a/identity/identity_user.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package identity
-
-import (
- "fmt"
- "os"
-
- "github.com/pkg/errors"
-
- "github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/repository"
-)
-
-// SetUserIdentity store the user identity's id in the git config
-func SetUserIdentity(repo repository.RepoConfig, identity *Identity) error {
- return repo.LocalConfig().StoreString(identityConfigKey, identity.Id().String())
-}
-
-// GetUserIdentity read the current user identity, set with a git config entry
-func GetUserIdentity(repo repository.Repo) (*Identity, error) {
- id, err := GetUserIdentityId(repo)
- if err != nil {
- return nil, err
- }
-
- i, err := ReadLocal(repo, id)
- if err == ErrIdentityNotExist {
- innerErr := repo.LocalConfig().RemoveAll(identityConfigKey)
- if innerErr != nil {
- _, _ = fmt.Fprintln(os.Stderr, errors.Wrap(innerErr, "can't clear user identity").Error())
- }
- return nil, err
- }
-
- return i, nil
-}
-
-func GetUserIdentityId(repo repository.Repo) (entity.Id, error) {
- val, err := repo.LocalConfig().ReadString(identityConfigKey)
- if err == repository.ErrNoConfigEntry {
- return entity.UnsetId, ErrNoIdentitySet
- }
- if err == repository.ErrMultipleConfigEntry {
- return entity.UnsetId, ErrMultipleIdentitiesSet
- }
- if err != nil {
- return entity.UnsetId, err
- }
-
- var id = entity.Id(val)
-
- if err := id.Validate(); err != nil {
- return entity.UnsetId, err
- }
-
- return id, nil
-}
-
-// IsUserIdentitySet say if the user has set his identity
-func IsUserIdentitySet(repo repository.Repo) (bool, error) {
- _, err := repo.LocalConfig().ReadString(identityConfigKey)
- if err == repository.ErrNoConfigEntry {
- return false, nil
- }
- if err != nil {
- return false, err
- }
- return true, nil
-}
diff --git a/identity/interface.go b/identity/interface.go
deleted file mode 100644
index c6e22e00..00000000
--- a/identity/interface.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package identity
-
-import (
- "github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/repository"
- "github.com/MichaelMure/git-bug/util/lamport"
- "github.com/MichaelMure/git-bug/util/timestamp"
-)
-
-type Interface interface {
- entity.Interface
-
- // Name return the last version of the name
- // Can be empty.
- Name() string
-
- // DisplayName return a non-empty string to display, representing the
- // identity, based on the non-empty values.
- DisplayName() string
-
- // Email return the last version of the email
- // Can be empty.
- Email() string
-
- // Login return the last version of the login
- // Can be empty.
- // Warning: this login can be defined when importing from a bridge but should *not* be
- // used to identify an identity as multiple bridge with different login can map to the same
- // identity. Use the metadata system for that usage instead.
- Login() string
-
- // AvatarUrl return the last version of the Avatar URL
- // Can be empty.
- AvatarUrl() string
-
- // Keys return the last version of the valid keys
- // Can be empty.
- Keys() []*Key
-
- // SigningKey return the key that should be used to sign new messages. If no key is available, return nil.
- SigningKey(repo repository.RepoKeyring) (*Key, error)
-
- // ValidKeysAtTime return the set of keys valid at a given lamport time for a given clock of another entity
- // Can be empty.
- ValidKeysAtTime(clockName string, time lamport.Time) []*Key
-
- // LastModification return the timestamp at which the last version of the identity became valid.
- LastModification() timestamp.Timestamp
-
- // LastModificationLamports return the lamport times at which the last version of the identity became valid.
- LastModificationLamports() map[string]lamport.Time
-
- // 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.
- IsProtected() bool
-
- // Validate check if the Identity data is valid
- Validate() error
-
- // NeedCommit indicate that the in-memory state changed and need to be committed in the repository
- NeedCommit() bool
-}
diff --git a/identity/key.go b/identity/key.go
deleted file mode 100644
index 82b9b95c..00000000
--- a/identity/key.go
+++ /dev/null
@@ -1,234 +0,0 @@
-package identity
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "strings"
- "time"
-
- "github.com/ProtonMail/go-crypto/openpgp"
- "github.com/ProtonMail/go-crypto/openpgp/armor"
- "github.com/ProtonMail/go-crypto/openpgp/packet"
- "github.com/pkg/errors"
-
- "github.com/MichaelMure/git-bug/repository"
-)
-
-var errNoPrivateKey = fmt.Errorf("no private key")
-
-type Key struct {
- public *packet.PublicKey
- private *packet.PrivateKey
-}
-
-// GenerateKey generate a keypair (public+private)
-// The type and configuration of the key is determined by the default value in go's OpenPGP.
-func GenerateKey() *Key {
- entity, err := openpgp.NewEntity("", "", "", &packet.Config{
- // The armored format doesn't include the creation time, which makes the round-trip data not being fully equal.
- // We don't care about the creation time so we can set it to the zero value.
- Time: func() time.Time {
- return time.Time{}
- },
- })
- if err != nil {
- panic(err)
- }
- return &Key{
- public: entity.PrimaryKey,
- private: entity.PrivateKey,
- }
-}
-
-// generatePublicKey generate only a public key (only useful for testing)
-// See GenerateKey for the details.
-func generatePublicKey() *Key {
- k := GenerateKey()
- k.private = nil
- return k
-}
-
-func (k *Key) Public() *packet.PublicKey {
- return k.public
-}
-
-func (k *Key) Private() *packet.PrivateKey {
- return k.private
-}
-
-func (k *Key) Validate() error {
- if k.public == nil {
- return fmt.Errorf("nil public key")
- }
- if !k.public.CanSign() {
- return fmt.Errorf("public key can't sign")
- }
-
- if k.private != nil {
- if !k.private.CanSign() {
- return fmt.Errorf("private key can't sign")
- }
- }
-
- return nil
-}
-
-func (k *Key) Clone() *Key {
- clone := &Key{}
-
- pub := *k.public
- clone.public = &pub
-
- if k.private != nil {
- priv := *k.private
- clone.private = &priv
- }
-
- return clone
-}
-
-func (k *Key) MarshalJSON() ([]byte, error) {
- // Serialize only the public key, in the armored format.
- var buf bytes.Buffer
- w, err := armor.Encode(&buf, openpgp.PublicKeyType, nil)
- if err != nil {
- return nil, err
- }
-
- err = k.public.Serialize(w)
- if err != nil {
- return nil, err
- }
- err = w.Close()
- if err != nil {
- return nil, err
- }
- return json.Marshal(buf.String())
-}
-
-func (k *Key) UnmarshalJSON(data []byte) error {
- // De-serialize only the public key, in the armored format.
- var armored string
- err := json.Unmarshal(data, &armored)
- if err != nil {
- return err
- }
-
- block, err := armor.Decode(strings.NewReader(armored))
- if err == io.EOF {
- return fmt.Errorf("no armored data found")
- }
- if err != nil {
- return err
- }
-
- if block.Type != openpgp.PublicKeyType {
- return fmt.Errorf("invalid key type")
- }
-
- p, err := packet.Read(block.Body)
- if err != nil {
- return errors.Wrap(err, "failed to read public key packet")
- }
-
- public, ok := p.(*packet.PublicKey)
- if !ok {
- return errors.New("got no packet.publicKey")
- }
-
- // The armored format doesn't include the creation time, which makes the round-trip data not being fully equal.
- // We don't care about the creation time so we can set it to the zero value.
- public.CreationTime = time.Time{}
-
- k.public = public
- return nil
-}
-
-func (k *Key) loadPrivate(repo repository.RepoKeyring) error {
- item, err := repo.Keyring().Get(k.public.KeyIdString())
- if err == repository.ErrKeyringKeyNotFound {
- return errNoPrivateKey
- }
- if err != nil {
- return err
- }
-
- block, err := armor.Decode(bytes.NewReader(item.Data))
- if err == io.EOF {
- return fmt.Errorf("no armored data found")
- }
- if err != nil {
- return err
- }
-
- if block.Type != openpgp.PrivateKeyType {
- return fmt.Errorf("invalid key type")
- }
-
- p, err := packet.Read(block.Body)
- if err != nil {
- return errors.Wrap(err, "failed to read private key packet")
- }
-
- private, ok := p.(*packet.PrivateKey)
- if !ok {
- return errors.New("got no packet.privateKey")
- }
-
- // The armored format doesn't include the creation time, which makes the round-trip data not being fully equal.
- // We don't care about the creation time so we can set it to the zero value.
- private.CreationTime = time.Time{}
-
- k.private = private
- return nil
-}
-
-// ensurePrivateKey attempt to load the corresponding private key if it is not loaded already.
-// If no private key is found, returns errNoPrivateKey
-func (k *Key) ensurePrivateKey(repo repository.RepoKeyring) error {
- if k.private != nil {
- return nil
- }
-
- return k.loadPrivate(repo)
-}
-
-func (k *Key) storePrivate(repo repository.RepoKeyring) error {
- var buf bytes.Buffer
- w, err := armor.Encode(&buf, openpgp.PrivateKeyType, nil)
- if err != nil {
- return err
- }
- err = k.private.Serialize(w)
- if err != nil {
- return err
- }
- err = w.Close()
- if err != nil {
- return err
- }
-
- return repo.Keyring().Set(repository.Item{
- Key: k.public.KeyIdString(),
- Data: buf.Bytes(),
- })
-}
-
-func (k *Key) PGPEntity() *openpgp.Entity {
- uid := packet.NewUserId("", "", "")
- return &openpgp.Entity{
- PrimaryKey: k.public,
- PrivateKey: k.private,
- Identities: map[string]*openpgp.Identity{
- uid.Id: {
- Name: uid.Id,
- UserId: uid,
- SelfSignature: &packet.Signature{
- IsPrimaryId: func() *bool { b := true; return &b }(),
- },
- },
- },
- }
-}
diff --git a/identity/key_test.go b/identity/key_test.go
deleted file mode 100644
index 6e320dc2..00000000
--- a/identity/key_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package identity
-
-import (
- "crypto/rsa"
- "encoding/json"
- "testing"
-
- "github.com/stretchr/testify/require"
-
- "github.com/MichaelMure/git-bug/repository"
-)
-
-func TestPublicKeyJSON(t *testing.T) {
- k := generatePublicKey()
-
- dataJSON, err := json.Marshal(k)
- require.NoError(t, err)
-
- var read Key
- err = json.Unmarshal(dataJSON, &read)
- require.NoError(t, err)
-
- require.Equal(t, k, &read)
-}
-
-func TestStoreLoad(t *testing.T) {
- repo := repository.NewMockRepoKeyring()
-
- // public + private
- k := GenerateKey()
-
- // Store
-
- dataJSON, err := json.Marshal(k)
- require.NoError(t, err)
-
- err = k.storePrivate(repo)
- require.NoError(t, err)
-
- // Load
-
- var read Key
- err = json.Unmarshal(dataJSON, &read)
- require.NoError(t, err)
-
- err = read.ensurePrivateKey(repo)
- require.NoError(t, err)
-
- require.Equal(t, k.public, read.public)
-
- require.IsType(t, (*rsa.PrivateKey)(nil), k.private.PrivateKey)
-
- // See https://github.com/golang/crypto/pull/175
- rsaPriv := read.private.PrivateKey.(*rsa.PrivateKey)
- back := rsaPriv.Primes[0]
- rsaPriv.Primes[0] = rsaPriv.Primes[1]
- rsaPriv.Primes[1] = back
-
- require.True(t, k.private.PrivateKey.(*rsa.PrivateKey).Equal(read.private.PrivateKey))
-}
diff --git a/identity/resolver.go b/identity/resolver.go
deleted file mode 100644
index 5468a8f8..00000000
--- a/identity/resolver.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package identity
-
-import (
- "github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/repository"
-)
-
-var _ entity.Resolver = &SimpleResolver{}
-
-// SimpleResolver is a Resolver loading Identities directly from a Repo
-type SimpleResolver struct {
- repo repository.Repo
-}
-
-func NewSimpleResolver(repo repository.Repo) *SimpleResolver {
- return &SimpleResolver{repo: repo}
-}
-
-func (r *SimpleResolver) Resolve(id entity.Id) (entity.Interface, error) {
- return ReadLocal(r.repo, id)
-}
-
-var _ entity.Resolver = &StubResolver{}
-
-// StubResolver is a Resolver that doesn't load anything, only returning IdentityStub instances
-type StubResolver struct{}
-
-func NewStubResolver() *StubResolver {
- return &StubResolver{}
-}
-
-func (s *StubResolver) Resolve(id entity.Id) (entity.Interface, error) {
- return &IdentityStub{id: id}, nil
-}
diff --git a/identity/version.go b/identity/version.go
deleted file mode 100644
index 9a52d089..00000000
--- a/identity/version.go
+++ /dev/null
@@ -1,273 +0,0 @@
-package identity
-
-import (
- "crypto/rand"
- "encoding/json"
- "fmt"
- "time"
-
- "github.com/pkg/errors"
-
- "github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/repository"
- "github.com/MichaelMure/git-bug/util/lamport"
- "github.com/MichaelMure/git-bug/util/text"
-)
-
-// 1: original format
-// 2: Identity Ids are generated from the first version serialized data instead of from the first git commit
-// + Identity hold multiple lamport clocks from other entities, instead of just bug edit
-const formatVersion = 2
-
-// version is a complete set of information about an Identity at a point in time.
-type version struct {
- name string
- email string // as defined in git or from a bridge when importing the identity
- login string // from a bridge when importing the identity
- avatarURL string
-
- // The lamport times of the other entities at which this version become effective
- times map[string]lamport.Time
- unixTime int64
-
- // 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
-
- // mandatory random bytes to ensure a better randomness of the data of the first
- // version of an identity, used to later generate the ID
- // len(Nonce) should be > 20 and < 64 bytes
- // It has no functional purpose and should be ignored.
- // TODO: optional after first version?
- nonce []byte
-
- // A set of arbitrary key/value to store metadata about a version or about an Identity in general.
- metadata map[string]string
-
- // Not serialized. Store the version's id in memory.
- id entity.Id
- // Not serialized
- commitHash repository.Hash
-}
-
-func newVersion(repo repository.RepoClock, name string, email string, login string, avatarURL string, keys []*Key) (*version, error) {
- clocks, err := repo.AllClocks()
- if err != nil {
- return nil, err
- }
-
- times := make(map[string]lamport.Time)
- for name, clock := range clocks {
- times[name] = clock.Time()
- }
-
- return &version{
- id: entity.UnsetId,
- name: name,
- email: email,
- login: login,
- avatarURL: avatarURL,
- times: times,
- unixTime: time.Now().Unix(),
- keys: keys,
- nonce: makeNonce(20),
- }, nil
-}
-
-type versionJSON struct {
- // Additional field to version the data
- FormatVersion uint `json:"version"`
-
- Times map[string]lamport.Time `json:"times"`
- UnixTime int64 `json:"unix_time"`
- Name string `json:"name,omitempty"`
- Email string `json:"email,omitempty"`
- Login string `json:"login,omitempty"`
- AvatarUrl string `json:"avatar_url,omitempty"`
- Keys []*Key `json:"pub_keys,omitempty"`
- Nonce []byte `json:"nonce"`
- Metadata map[string]string `json:"metadata,omitempty"`
-}
-
-// Id return the identifier of the version
-func (v *version) Id() entity.Id {
- if v.id == "" {
- // something went really wrong
- panic("version's id not set")
- }
- if v.id == entity.UnsetId {
- // This means we are trying to get the version's Id *before* it has been stored.
- // As the Id is computed based on the actual bytes written on the disk, we are going to predict
- // those and then get the Id. This is safe as it will be the exact same code writing on disk later.
- data, err := json.Marshal(v)
- if err != nil {
- panic(err)
- }
- v.id = entity.DeriveId(data)
- }
- return v.id
-}
-
-// Make a deep copy
-func (v *version) Clone() *version {
- // copy direct fields
- clone := *v
-
- // reset some fields
- clone.commitHash = ""
- clone.id = entity.UnsetId
-
- clone.times = make(map[string]lamport.Time)
- for name, t := range v.times {
- clone.times[name] = t
- }
-
- clone.keys = make([]*Key, len(v.keys))
- for i, key := range v.keys {
- clone.keys[i] = key.Clone()
- }
-
- clone.nonce = make([]byte, len(v.nonce))
- copy(clone.nonce, v.nonce)
-
- // not copying metadata
-
- return &clone
-}
-
-func (v *version) MarshalJSON() ([]byte, error) {
- return json.Marshal(versionJSON{
- FormatVersion: formatVersion,
- Times: v.times,
- UnixTime: v.unixTime,
- Name: v.name,
- Email: v.email,
- Login: v.login,
- AvatarUrl: v.avatarURL,
- Keys: v.keys,
- Nonce: v.nonce,
- Metadata: v.metadata,
- })
-}
-
-func (v *version) UnmarshalJSON(data []byte) error {
- var aux versionJSON
-
- if err := json.Unmarshal(data, &aux); err != nil {
- return err
- }
-
- if aux.FormatVersion != formatVersion {
- return entity.NewErrInvalidFormat(aux.FormatVersion, formatVersion)
- }
-
- v.id = entity.DeriveId(data)
- v.times = aux.Times
- v.unixTime = aux.UnixTime
- 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 {
- // time must be set after a commit
- if v.commitHash != "" && v.unixTime == 0 {
- return fmt.Errorf("unix time not set")
- }
-
- if text.Empty(v.name) && text.Empty(v.login) {
- return fmt.Errorf("either name or login should be set")
- }
- if !text.SafeOneLine(v.name) {
- return fmt.Errorf("name has unsafe characters")
- }
-
- if !text.SafeOneLine(v.login) {
- return fmt.Errorf("login has unsafe characters")
- }
-
- if !text.SafeOneLine(v.email) {
- return fmt.Errorf("email has unsafe characters")
- }
-
- if v.avatarURL != "" && !text.ValidUrl(v.avatarURL) {
- return fmt.Errorf("avatarUrl is not a valid URL")
- }
-
- if len(v.nonce) > 64 {
- return fmt.Errorf("nonce is too big")
- }
- if len(v.nonce) < 20 {
- return fmt.Errorf("nonce is too small")
- }
-
- for _, k := range v.keys {
- if err := k.Validate(); err != nil {
- return errors.Wrap(err, "invalid key")
- }
- }
-
- return nil
-}
-
-// Write will serialize and store the version as a git blob and return
-// its hash
-func (v *version) Write(repo repository.Repo) (repository.Hash, error) {
- // make sure we don't write invalid data
- err := v.Validate()
- if err != nil {
- return "", errors.Wrap(err, "validation error")
- }
-
- data, err := json.Marshal(v)
- if err != nil {
- return "", err
- }
-
- hash, err := repo.StoreData(data)
- if err != nil {
- return "", err
- }
-
- // make sure we set the Id when writing in the repo
- v.id = entity.DeriveId(data)
-
- return hash, nil
-}
-
-func makeNonce(len int) []byte {
- result := make([]byte, len)
- _, err := rand.Read(result)
- if err != nil {
- panic(err)
- }
- return result
-}
-
-// 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.
-// Beware: changing the metadata on a version will change it's ID
-func (v *version) SetMetadata(key string, value string) {
- if v.metadata == nil {
- v.metadata = make(map[string]string)
- }
- v.metadata[key] = value
-}
-
-// GetMetadata retrieve arbitrary metadata about the version
-func (v *version) GetMetadata(key string) (string, bool) {
- val, ok := v.metadata[key]
- return val, ok
-}
-
-// AllMetadata return all metadata for this version
-func (v *version) AllMetadata() map[string]string {
- return v.metadata
-}
diff --git a/identity/version_test.go b/identity/version_test.go
deleted file mode 100644
index 385ad4d7..00000000
--- a/identity/version_test.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package identity
-
-import (
- "encoding/json"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/repository"
- "github.com/MichaelMure/git-bug/util/lamport"
-)
-
-func makeIdentityTestRepo(t *testing.T) repository.ClockedRepo {
- repo := repository.NewMockRepo()
-
- clock1, err := repo.GetOrCreateClock("foo")
- require.NoError(t, err)
- err = clock1.Witness(42)
- require.NoError(t, err)
-
- clock2, err := repo.GetOrCreateClock("bar")
- require.NoError(t, err)
- err = clock2.Witness(34)
- require.NoError(t, err)
-
- return repo
-}
-
-func TestVersionJSON(t *testing.T) {
- repo := makeIdentityTestRepo(t)
-
- keys := []*Key{
- generatePublicKey(),
- generatePublicKey(),
- }
-
- before, err := newVersion(repo, "name", "email", "login", "avatarUrl", keys)
- require.NoError(t, err)
-
- before.SetMetadata("key1", "value1")
- before.SetMetadata("key2", "value2")
-
- expected := &version{
- id: entity.UnsetId,
- name: "name",
- email: "email",
- login: "login",
- avatarURL: "avatarUrl",
- unixTime: time.Now().Unix(),
- times: map[string]lamport.Time{
- "foo": 42,
- "bar": 34,
- },
- keys: keys,
- nonce: before.nonce,
- metadata: map[string]string{
- "key1": "value1",
- "key2": "value2",
- },
- }
-
- require.Equal(t, expected, before)
-
- data, err := json.Marshal(before)
- assert.NoError(t, err)
-
- var after version
- err = json.Unmarshal(data, &after)
- assert.NoError(t, err)
-
- // make sure we now have an Id
- expected.Id()
-
- assert.Equal(t, expected, &after)
-}