mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-14 17:51:44 +03:00
identity: wip
This commit is contained in:
parent
328a4e5abf
commit
21048e785d
@ -459,6 +459,7 @@ func (bug *Bug) Commit(repo repository.ClockedRepo) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bug.staging.commitHash = hash
|
||||||
bug.packs = append(bug.packs, bug.staging)
|
bug.packs = append(bug.packs, bug.staging)
|
||||||
bug.staging = OperationPack{}
|
bug.staging = OperationPack{}
|
||||||
|
|
||||||
@ -513,9 +514,8 @@ func (bug *Bug) Merge(repo repository.Repo, other Interface) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ancestor, err := repo.FindCommonAncestor(bug.lastCommit, otherBug.lastCommit)
|
ancestor, err := repo.FindCommonAncestor(bug.lastCommit, otherBug.lastCommit)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, errors.Wrap(err, "can't find common ancestor")
|
||||||
}
|
}
|
||||||
|
|
||||||
ancestorIndex := 0
|
ancestorIndex := 0
|
||||||
|
@ -55,7 +55,7 @@ func TestBugValidity(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBugSerialisation(t *testing.T) {
|
func TestBugCommitLoad(t *testing.T) {
|
||||||
bug1 := NewBug()
|
bug1 := NewBug()
|
||||||
|
|
||||||
bug1.Append(createOp)
|
bug1.Append(createOp)
|
||||||
@ -69,22 +69,30 @@ func TestBugSerialisation(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
bug2, err := ReadLocalBug(repo, bug1.Id())
|
bug2, err := ReadLocalBug(repo, bug1.Id())
|
||||||
if err != nil {
|
assert.NoError(t, err)
|
||||||
t.Error(err)
|
equivalentBug(t, bug1, bug2)
|
||||||
|
|
||||||
|
// add more op
|
||||||
|
|
||||||
|
bug1.Append(setTitleOp)
|
||||||
|
bug1.Append(addCommentOp)
|
||||||
|
|
||||||
|
err = bug1.Commit(repo)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
bug3, err := ReadLocalBug(repo, bug1.Id())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
equivalentBug(t, bug1, bug3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore some fields
|
func equivalentBug(t *testing.T, expected, actual *Bug) {
|
||||||
bug2.packs[0].commitHash = bug1.packs[0].commitHash
|
assert.Equal(t, len(expected.packs), len(actual.packs))
|
||||||
for i := range bug1.packs[0].Operations {
|
|
||||||
bug2.packs[0].Operations[i].base().hash = bug1.packs[0].Operations[i].base().hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// check hashes
|
for i := range expected.packs {
|
||||||
for i := range bug1.packs[0].Operations {
|
for j := range expected.packs[i].Operations {
|
||||||
if !bug2.packs[0].Operations[i].base().hash.IsValid() {
|
actual.packs[i].Operations[j].base().hash = expected.packs[i].Operations[j].base().hash
|
||||||
t.Fatal("invalid hash")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, bug1, bug2)
|
assert.Equal(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
@ -21,17 +21,22 @@ const identityConfigKey = "git-bug.identity"
|
|||||||
var _ Interface = &Identity{}
|
var _ Interface = &Identity{}
|
||||||
|
|
||||||
type Identity struct {
|
type Identity struct {
|
||||||
|
// Id used as unique identifier
|
||||||
id string
|
id string
|
||||||
Versions []*Version
|
|
||||||
|
lastCommit git.Hash
|
||||||
|
|
||||||
|
// all the successive version of the identity
|
||||||
|
versions []*Version
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIdentity(name string, email string) *Identity {
|
func NewIdentity(name string, email string) *Identity {
|
||||||
return &Identity{
|
return &Identity{
|
||||||
Versions: []*Version{
|
versions: []*Version{
|
||||||
{
|
{
|
||||||
Name: name,
|
name: name,
|
||||||
Email: email,
|
email: email,
|
||||||
Nonce: makeNonce(20),
|
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 {
|
func NewIdentityFull(name string, email string, login string, avatarUrl string) *Identity {
|
||||||
return &Identity{
|
return &Identity{
|
||||||
Versions: []*Version{
|
versions: []*Version{
|
||||||
{
|
{
|
||||||
Name: name,
|
name: name,
|
||||||
Email: email,
|
email: email,
|
||||||
Login: login,
|
login: login,
|
||||||
AvatarUrl: avatarUrl,
|
avatarURL: avatarUrl,
|
||||||
Nonce: makeNonce(20),
|
nonce: makeNonce(20),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -84,13 +89,15 @@ func read(repo repository.Repo, ref string) (*Identity, error) {
|
|||||||
|
|
||||||
hashes, err := repo.ListCommits(ref)
|
hashes, err := repo.ListCommits(ref)
|
||||||
|
|
||||||
var versions []*Version
|
|
||||||
|
|
||||||
// TODO: this is not perfect, it might be a command invoke error
|
// TODO: this is not perfect, it might be a command invoke error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrIdentityNotExist
|
return nil, ErrIdentityNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i := &Identity{
|
||||||
|
id: id,
|
||||||
|
}
|
||||||
|
|
||||||
for _, hash := range hashes {
|
for _, hash := range hashes {
|
||||||
entries, err := repo.ListEntries(hash)
|
entries, err := repo.ListEntries(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -121,14 +128,12 @@ func read(repo repository.Repo, ref string) (*Identity, error) {
|
|||||||
|
|
||||||
// tag the version with the commit hash
|
// tag the version with the commit hash
|
||||||
version.commitHash = hash
|
version.commitHash = hash
|
||||||
|
i.lastCommit = hash
|
||||||
|
|
||||||
versions = append(versions, &version)
|
i.versions = append(i.versions, &version)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Identity{
|
return i, nil
|
||||||
id: id,
|
|
||||||
Versions: versions,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFromGitUser will query the repository for user detail and
|
// 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) {
|
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
|
// 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 {
|
func (i *Identity) Commit(repo repository.Repo) error {
|
||||||
// Todo: check for mismatch between memory and commited data
|
// 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 != "" {
|
if v.commitHash != "" {
|
||||||
lastCommit = v.commitHash
|
i.lastCommit = v.commitHash
|
||||||
// ignore already commited versions
|
// ignore already commited versions
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -215,8 +218,8 @@ func (i *Identity) Commit(repo repository.Repo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var commitHash git.Hash
|
var commitHash git.Hash
|
||||||
if lastCommit != "" {
|
if i.lastCommit != "" {
|
||||||
commitHash, err = repo.StoreCommitWithParent(treeHash, lastCommit)
|
commitHash, err = repo.StoreCommitWithParent(treeHash, i.lastCommit)
|
||||||
} else {
|
} else {
|
||||||
commitHash, err = repo.StoreCommit(treeHash)
|
commitHash, err = repo.StoreCommit(treeHash)
|
||||||
}
|
}
|
||||||
@ -225,7 +228,8 @@ func (i *Identity) Commit(repo repository.Repo) error {
|
|||||||
return err
|
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 it was the first commit, use the commit hash as the Identity id
|
||||||
if i.id == "" {
|
if i.id == "" {
|
||||||
@ -238,7 +242,7 @@ func (i *Identity) Commit(repo repository.Repo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ref := fmt.Sprintf("%s%s", identityRefPattern, i.id)
|
ref := fmt.Sprintf("%s%s", identityRefPattern, i.id)
|
||||||
err := repo.UpdateRef(ref, lastCommit)
|
err := repo.UpdateRef(ref, i.lastCommit)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -247,31 +251,93 @@ func (i *Identity) Commit(repo repository.Repo) error {
|
|||||||
return nil
|
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
|
// Validate check if the Identity data is valid
|
||||||
func (i *Identity) Validate() error {
|
func (i *Identity) Validate() error {
|
||||||
lastTime := lamport.Time(0)
|
lastTime := lamport.Time(0)
|
||||||
|
|
||||||
for _, v := range i.Versions {
|
for _, v := range i.versions {
|
||||||
if err := v.Validate(); err != nil {
|
if err := v.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Time < lastTime {
|
if v.time < lastTime {
|
||||||
return fmt.Errorf("non-chronological version (%d --> %d)", lastTime, v.Time)
|
return fmt.Errorf("non-chronological version (%d --> %d)", lastTime, v.time)
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTime = v.Time
|
lastTime = v.time
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Identity) lastVersion() *Version {
|
func (i *Identity) lastVersion() *Version {
|
||||||
if len(i.Versions) <= 0 {
|
if len(i.versions) <= 0 {
|
||||||
panic("no version at all")
|
panic("no version at all")
|
||||||
}
|
}
|
||||||
|
|
||||||
return i.Versions[len(i.Versions)-1]
|
return i.versions[len(i.versions)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Id return the Identity identifier
|
// Id return the Identity identifier
|
||||||
@ -286,27 +352,27 @@ func (i *Identity) Id() string {
|
|||||||
|
|
||||||
// Name return the last version of the name
|
// Name return the last version of the name
|
||||||
func (i *Identity) Name() string {
|
func (i *Identity) Name() string {
|
||||||
return i.lastVersion().Name
|
return i.lastVersion().name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Email return the last version of the email
|
// Email return the last version of the email
|
||||||
func (i *Identity) Email() string {
|
func (i *Identity) Email() string {
|
||||||
return i.lastVersion().Email
|
return i.lastVersion().email
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login return the last version of the login
|
// Login return the last version of the login
|
||||||
func (i *Identity) Login() string {
|
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 {
|
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 {
|
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.
|
// 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 {
|
func (i *Identity) ValidKeysAtTime(time lamport.Time) []Key {
|
||||||
var result []Key
|
var result []Key
|
||||||
|
|
||||||
for _, v := range i.Versions {
|
for _, v := range i.versions {
|
||||||
if v.Time > time {
|
if v.time > time {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
result = v.Keys
|
result = v.keys
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@ -357,8 +423,8 @@ func (i *Identity) SetMetadata(key string, value string) {
|
|||||||
func (i *Identity) ImmutableMetadata() map[string]string {
|
func (i *Identity) ImmutableMetadata() map[string]string {
|
||||||
metadata := make(map[string]string)
|
metadata := make(map[string]string)
|
||||||
|
|
||||||
for _, version := range i.Versions {
|
for _, version := range i.versions {
|
||||||
for key, value := range version.Metadata {
|
for key, value := range version.metadata {
|
||||||
if _, has := metadata[key]; !has {
|
if _, has := metadata[key]; !has {
|
||||||
metadata[key] = value
|
metadata[key] = value
|
||||||
}
|
}
|
||||||
@ -373,8 +439,8 @@ func (i *Identity) ImmutableMetadata() map[string]string {
|
|||||||
func (i *Identity) MutableMetadata() map[string]string {
|
func (i *Identity) MutableMetadata() map[string]string {
|
||||||
metadata := make(map[string]string)
|
metadata := make(map[string]string)
|
||||||
|
|
||||||
for _, version := range i.Versions {
|
for _, version := range i.versions {
|
||||||
for key, value := range version.Metadata {
|
for key, value := range version.metadata {
|
||||||
metadata[key] = value
|
metadata[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,26 +46,6 @@ func Pull(repo repository.ClockedRepo, remote string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MergeAll will merge all the available remote identity
|
// 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 {
|
func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
|
||||||
out := make(chan MergeResult)
|
out := make(chan MergeResult)
|
||||||
|
|
||||||
@ -85,20 +65,19 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
|
|||||||
id := refSplitted[len(refSplitted)-1]
|
id := refSplitted[len(refSplitted)-1]
|
||||||
|
|
||||||
remoteIdentity, err := ReadLocal(repo, remoteRef)
|
remoteIdentity, err := ReadLocal(repo, remoteRef)
|
||||||
remoteBug, err := readBug(repo, remoteRef)
|
|
||||||
|
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for error in remote data
|
// Check for error in remote data
|
||||||
if err := remoteBug.Validate(); err != nil {
|
if err := remoteIdentity.Validate(); err != nil {
|
||||||
out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote bug is invalid").Error())
|
out <- newMergeInvalidStatus(id, errors.Wrap(err, "remote identity is invalid").Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
localRef := bugsRefPattern + remoteBug.Id()
|
localRef := identityRefPattern + remoteIdentity.Id()
|
||||||
localExist, err := repo.RefExist(localRef)
|
localExist, err := repo.RefExist(localRef)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -106,7 +85,7 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// the bug is not local yet, simply create the reference
|
// the identity is not local yet, simply create the reference
|
||||||
if !localExist {
|
if !localExist {
|
||||||
err := repo.CopyRef(remoteRef, localRef)
|
err := repo.CopyRef(remoteRef, localRef)
|
||||||
|
|
||||||
@ -115,18 +94,18 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out <- newMergeStatus(MergeStatusNew, id, remoteBug)
|
out <- newMergeStatus(MergeStatusNew, id, remoteIdentity)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
localBug, err := readBug(repo, localRef)
|
localIdentity, err := read(repo, localRef)
|
||||||
|
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updated, err := localBug.Merge(repo, remoteBug)
|
updated, err := localIdentity.Merge(repo, remoteIdentity)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
out <- newMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
|
out <- newMergeInvalidStatus(id, errors.Wrap(err, "merge failed").Error())
|
||||||
@ -134,9 +113,9 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan MergeResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if updated {
|
if updated {
|
||||||
out <- newMergeStatus(MergeStatusUpdated, id, localBug)
|
out <- newMergeStatus(MergeStatusUpdated, id, localIdentity)
|
||||||
} else {
|
} else {
|
||||||
out <- newMergeStatus(MergeStatusNothing, id, localBug)
|
out <- newMergeStatus(MergeStatusNothing, id, localIdentity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/MichaelMure/git-bug/repository"
|
"github.com/MichaelMure/git-bug/repository"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test the commit and load of an Identity with multiple versions
|
// Test the commit and load of an Identity with multiple versions
|
||||||
@ -16,10 +15,10 @@ func TestIdentityCommitLoad(t *testing.T) {
|
|||||||
// single version
|
// single version
|
||||||
|
|
||||||
identity := &Identity{
|
identity := &Identity{
|
||||||
Versions: []*Version{
|
versions: []*Version{
|
||||||
{
|
{
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -32,33 +31,33 @@ func TestIdentityCommitLoad(t *testing.T) {
|
|||||||
loaded, err := ReadLocal(mockRepo, identity.id)
|
loaded, err := ReadLocal(mockRepo, identity.id)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
commitsAreSet(t, loaded)
|
commitsAreSet(t, loaded)
|
||||||
equivalentIdentity(t, identity, loaded)
|
assert.Equal(t, identity, loaded)
|
||||||
|
|
||||||
// multiple version
|
// multiple version
|
||||||
|
|
||||||
identity = &Identity{
|
identity = &Identity{
|
||||||
Versions: []*Version{
|
versions: []*Version{
|
||||||
{
|
{
|
||||||
Time: 100,
|
time: 100,
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
Keys: []Key{
|
keys: []Key{
|
||||||
{PubKey: "pubkeyA"},
|
{PubKey: "pubkeyA"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Time: 200,
|
time: 200,
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
Keys: []Key{
|
keys: []Key{
|
||||||
{PubKey: "pubkeyB"},
|
{PubKey: "pubkeyB"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Time: 201,
|
time: 201,
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
Keys: []Key{
|
keys: []Key{
|
||||||
{PubKey: "pubkeyC"},
|
{PubKey: "pubkeyC"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -73,24 +72,24 @@ func TestIdentityCommitLoad(t *testing.T) {
|
|||||||
loaded, err = ReadLocal(mockRepo, identity.id)
|
loaded, err = ReadLocal(mockRepo, identity.id)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
commitsAreSet(t, loaded)
|
commitsAreSet(t, loaded)
|
||||||
equivalentIdentity(t, identity, loaded)
|
assert.Equal(t, identity, loaded)
|
||||||
|
|
||||||
// add more version
|
// add more version
|
||||||
|
|
||||||
identity.AddVersion(&Version{
|
identity.AddVersion(&Version{
|
||||||
Time: 201,
|
time: 201,
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
Keys: []Key{
|
keys: []Key{
|
||||||
{PubKey: "pubkeyD"},
|
{PubKey: "pubkeyD"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
identity.AddVersion(&Version{
|
identity.AddVersion(&Version{
|
||||||
Time: 300,
|
time: 300,
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
Keys: []Key{
|
keys: []Key{
|
||||||
{PubKey: "pubkeyE"},
|
{PubKey: "pubkeyE"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -103,66 +102,56 @@ func TestIdentityCommitLoad(t *testing.T) {
|
|||||||
loaded, err = ReadLocal(mockRepo, identity.id)
|
loaded, err = ReadLocal(mockRepo, identity.id)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
commitsAreSet(t, loaded)
|
commitsAreSet(t, loaded)
|
||||||
equivalentIdentity(t, identity, loaded)
|
assert.Equal(t, identity, loaded)
|
||||||
}
|
}
|
||||||
|
|
||||||
func commitsAreSet(t *testing.T, identity *Identity) {
|
func commitsAreSet(t *testing.T, identity *Identity) {
|
||||||
for _, version := range identity.Versions {
|
for _, version := range identity.versions {
|
||||||
assert.NotEmpty(t, version.commitHash)
|
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
|
// Test that the correct crypto keys are returned for a given lamport time
|
||||||
func TestIdentity_ValidKeysAtTime(t *testing.T) {
|
func TestIdentity_ValidKeysAtTime(t *testing.T) {
|
||||||
identity := Identity{
|
identity := Identity{
|
||||||
Versions: []*Version{
|
versions: []*Version{
|
||||||
{
|
{
|
||||||
Time: 100,
|
time: 100,
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
Keys: []Key{
|
keys: []Key{
|
||||||
{PubKey: "pubkeyA"},
|
{PubKey: "pubkeyA"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Time: 200,
|
time: 200,
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
Keys: []Key{
|
keys: []Key{
|
||||||
{PubKey: "pubkeyB"},
|
{PubKey: "pubkeyB"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Time: 201,
|
time: 201,
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
Keys: []Key{
|
keys: []Key{
|
||||||
{PubKey: "pubkeyC"},
|
{PubKey: "pubkeyC"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Time: 201,
|
time: 201,
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
Keys: []Key{
|
keys: []Key{
|
||||||
{PubKey: "pubkeyD"},
|
{PubKey: "pubkeyD"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Time: 300,
|
time: 300,
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
Keys: []Key{
|
keys: []Key{
|
||||||
{PubKey: "pubkeyE"},
|
{PubKey: "pubkeyE"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -197,8 +186,8 @@ func TestMetadata(t *testing.T) {
|
|||||||
|
|
||||||
// try override
|
// try override
|
||||||
identity.AddVersion(&Version{
|
identity.AddVersion(&Version{
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
})
|
})
|
||||||
|
|
||||||
identity.SetMetadata("key1", "value2")
|
identity.SetMetadata("key1", "value2")
|
||||||
@ -226,10 +215,10 @@ func TestJSON(t *testing.T) {
|
|||||||
mockRepo := repository.NewMockRepoForTest()
|
mockRepo := repository.NewMockRepoForTest()
|
||||||
|
|
||||||
identity := &Identity{
|
identity := &Identity{
|
||||||
Versions: []*Version{
|
versions: []*Version{
|
||||||
{
|
{
|
||||||
Name: "René Descartes",
|
name: "René Descartes",
|
||||||
Email: "rene.descartes@example.com",
|
email: "rene.descartes@example.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -24,25 +24,25 @@ type Version struct {
|
|||||||
|
|
||||||
// The lamport time at which this version become effective
|
// The lamport time at which this version become effective
|
||||||
// The reference time is the bug edition lamport clock
|
// The reference time is the bug edition lamport clock
|
||||||
Time lamport.Time
|
time lamport.Time
|
||||||
|
|
||||||
Name string
|
name string
|
||||||
Email string
|
email string
|
||||||
Login string
|
login string
|
||||||
AvatarUrl string
|
avatarURL string
|
||||||
|
|
||||||
// The set of keys valid at that time, from this version onward, until they get removed
|
// 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
|
// in a new version. This allow to have multiple key for the same identity (e.g. one per
|
||||||
// device) as well as revoke key.
|
// 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.
|
// 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 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.
|
// 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.
|
// 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 {
|
type VersionJSON struct {
|
||||||
@ -62,14 +62,14 @@ type VersionJSON struct {
|
|||||||
func (v *Version) MarshalJSON() ([]byte, error) {
|
func (v *Version) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(VersionJSON{
|
return json.Marshal(VersionJSON{
|
||||||
FormatVersion: formatVersion,
|
FormatVersion: formatVersion,
|
||||||
Time: v.Time,
|
Time: v.time,
|
||||||
Name: v.Name,
|
Name: v.name,
|
||||||
Email: v.Email,
|
Email: v.email,
|
||||||
Login: v.Login,
|
Login: v.login,
|
||||||
AvatarUrl: v.AvatarUrl,
|
AvatarUrl: v.avatarURL,
|
||||||
Keys: v.Keys,
|
Keys: v.keys,
|
||||||
Nonce: v.Nonce,
|
Nonce: v.nonce,
|
||||||
Metadata: v.Metadata,
|
Metadata: v.metadata,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,56 +84,56 @@ func (v *Version) UnmarshalJSON(data []byte) error {
|
|||||||
return fmt.Errorf("unknown format version %v", aux.FormatVersion)
|
return fmt.Errorf("unknown format version %v", aux.FormatVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Time = aux.Time
|
v.time = aux.Time
|
||||||
v.Name = aux.Name
|
v.name = aux.Name
|
||||||
v.Email = aux.Email
|
v.email = aux.Email
|
||||||
v.Login = aux.Login
|
v.login = aux.Login
|
||||||
v.AvatarUrl = aux.AvatarUrl
|
v.avatarURL = aux.AvatarUrl
|
||||||
v.Keys = aux.Keys
|
v.keys = aux.Keys
|
||||||
v.Nonce = aux.Nonce
|
v.nonce = aux.Nonce
|
||||||
v.Metadata = aux.Metadata
|
v.metadata = aux.Metadata
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Version) Validate() error {
|
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")
|
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")
|
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")
|
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")
|
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")
|
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")
|
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")
|
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")
|
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")
|
return fmt.Errorf("nonce is too big")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, k := range v.Keys {
|
for _, k := range v.keys {
|
||||||
if err := k.Validate(); err != nil {
|
if err := k.Validate(); err != nil {
|
||||||
return errors.Wrap(err, "invalid key")
|
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
|
// 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.
|
// If the Version has been commit to git already, it won't be overwritten.
|
||||||
func (v *Version) SetMetadata(key string, value string) {
|
func (v *Version) SetMetadata(key string, value string) {
|
||||||
if v.Metadata == nil {
|
if v.metadata == nil {
|
||||||
v.Metadata = make(map[string]string)
|
v.metadata = make(map[string]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Metadata[key] = value
|
v.metadata[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetadata retrieve arbitrary metadata about the Version
|
// GetMetadata retrieve arbitrary metadata about the Version
|
||||||
func (v *Version) GetMetadata(key string) (string, bool) {
|
func (v *Version) GetMetadata(key string) (string, bool) {
|
||||||
val, ok := v.Metadata[key]
|
val, ok := v.metadata[key]
|
||||||
return val, ok
|
return val, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllMetadata return all metadata for this Identity
|
// AllMetadata return all metadata for this Identity
|
||||||
func (v *Version) AllMetadata() map[string]string {
|
func (v *Version) AllMetadata() map[string]string {
|
||||||
return v.Metadata
|
return v.metadata
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user