aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Muré <michael.mure@consensys.net>2018-11-21 18:56:12 +0100
committerMichael Muré <batolettre@gmail.com>2019-03-01 22:35:36 +0100
commitfeab9412dffe5772048aad29893c4cb01d566387 (patch)
treeb7bc9751f2ebdf8d41f5621bbf372eaf7625c4b9
parent0aefae6fcca5786f2c898029c3d6282f760f2c63 (diff)
downloadgit-bug-feab9412dffe5772048aad29893c4cb01d566387.tar.gz
WIP identity in git
-rw-r--r--Gopkg.lock1
-rw-r--r--bridge/core/bridge.go6
-rw-r--r--bug/bug_actions.go5
-rw-r--r--bug/bug_actions_test.go2
-rw-r--r--bug/comment.go3
-rw-r--r--bug/op_add_comment.go8
-rw-r--r--bug/op_create.go8
-rw-r--r--bug/op_create_test.go6
-rw-r--r--bug/op_edit_comment.go8
-rw-r--r--bug/op_edit_comment_test.go6
-rw-r--r--bug/op_label_change.go8
-rw-r--r--bug/op_noop.go9
-rw-r--r--bug/op_set_metadata.go9
-rw-r--r--bug/op_set_metadata_test.go6
-rw-r--r--bug/op_set_status.go9
-rw-r--r--bug/op_set_title.go8
-rw-r--r--bug/operation.go50
-rw-r--r--bug/operation_iterator_test.go7
-rw-r--r--bug/operation_pack.go31
-rw-r--r--bug/operation_test.go11
-rw-r--r--bug/person.go95
-rw-r--r--bug/snapshot.go3
-rw-r--r--bug/timeline.go5
-rw-r--r--cache/bug_cache.go26
-rw-r--r--cache/bug_excerpt.go4
-rw-r--r--cache/repo_cache.go5
-rw-r--r--commands/id.go35
-rw-r--r--graphql/gqlgen.yml15
-rw-r--r--graphql/graph/gen_graph.go658
-rw-r--r--graphql/graphql_test.go (renamed from tests/graphql_test.go)8
-rw-r--r--graphql/resolvers/person.go37
-rw-r--r--graphql/resolvers/root.go4
-rw-r--r--graphql/schema/bug.graphql (renamed from graphql/bug.graphql)18
-rw-r--r--graphql/schema/identity.graphql13
-rw-r--r--graphql/schema/operations.graphql (renamed from graphql/operations.graphql)14
-rw-r--r--graphql/schema/root.graphql (renamed from graphql/root.graphql)2
-rw-r--r--graphql/schema/timeline.graphql (renamed from graphql/timeline.graphql)10
-rw-r--r--identity/bare.go144
-rw-r--r--identity/identity.go285
-rw-r--r--identity/identity_test.go145
-rw-r--r--identity/interface.go30
-rw-r--r--identity/key.go7
-rw-r--r--identity/version.go105
-rw-r--r--misc/random_bugs/create_random_bugs.go26
-rw-r--r--termui/bug_table.go3
-rw-r--r--tests/read_bugs_test.go52
-rw-r--r--util/test/repo.go52
47 files changed, 1366 insertions, 636 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index 0ea5b61b..a208c91a 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -469,6 +469,7 @@
"github.com/go-test/deep",
"github.com/gorilla/mux",
"github.com/icrowley/fake",
+ "github.com/mattn/go-runewidth",
"github.com/phayes/freeport",
"github.com/pkg/errors",
"github.com/shurcooL/githubv4",
diff --git a/bridge/core/bridge.go b/bridge/core/bridge.go
index 91ed5bfb..96646edb 100644
--- a/bridge/core/bridge.go
+++ b/bridge/core/bridge.go
@@ -15,6 +15,8 @@ import (
var ErrImportNorSupported = errors.New("import is not supported")
var ErrExportNorSupported = errors.New("export is not supported")
+const bridgeConfigKeyPrefix = "git-bug.bridge"
+
var bridgeImpl map[string]reflect.Type
// Bridge is a wrapper around a BridgeImpl that will bind low-level
@@ -114,12 +116,12 @@ func splitFullName(fullName string) (string, string, error) {
// ConfiguredBridges return the list of bridge that are configured for the given
// repo
func ConfiguredBridges(repo repository.RepoCommon) ([]string, error) {
- configs, err := repo.ReadConfigs("git-bug.bridge.")
+ configs, err := repo.ReadConfigs(bridgeConfigKeyPrefix + ".")
if err != nil {
return nil, errors.Wrap(err, "can't read configured bridges")
}
- re, err := regexp.Compile(`git-bug.bridge.([^.]+\.[^.]+)`)
+ re, err := regexp.Compile(bridgeConfigKeyPrefix + `.([^.]+\.[^.]+)`)
if err != nil {
panic(err)
}
diff --git a/bug/bug_actions.go b/bug/bug_actions.go
index 487ba25e..a21db826 100644
--- a/bug/bug_actions.go
+++ b/bug/bug_actions.go
@@ -35,6 +35,11 @@ func Pull(repo repository.ClockedRepo, remote string) error {
if merge.Err != nil {
return merge.Err
}
+ if merge.Status == MergeStatusInvalid {
+ // Not awesome: simply output the merge failure here as this function
+ // is only used in tests for now.
+ fmt.Println(merge)
+ }
}
return nil
diff --git a/bug/bug_actions_test.go b/bug/bug_actions_test.go
index a60e5c68..4327ae58 100644
--- a/bug/bug_actions_test.go
+++ b/bug/bug_actions_test.go
@@ -50,7 +50,7 @@ func cleanupRepo(repo repository.Repo) error {
func setupRepos(t testing.TB) (repoA, repoB, remote *repository.GitRepo) {
repoA = createRepo(false)
repoB = createRepo(false)
- remote = createRepo(true)
+ remote = createRepo(false)
remoteAddr := "file://" + remote.GetPath()
diff --git a/bug/comment.go b/bug/comment.go
index 67936634..84d34299 100644
--- a/bug/comment.go
+++ b/bug/comment.go
@@ -1,13 +1,14 @@
package bug
import (
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/git"
"github.com/dustin/go-humanize"
)
// Comment represent a comment in a Bug
type Comment struct {
- Author Person
+ Author identity.Interface
Message string
Files []git.Hash
diff --git a/bug/op_add_comment.go b/bug/op_add_comment.go
index 2d6fb21a..23a10419 100644
--- a/bug/op_add_comment.go
+++ b/bug/op_add_comment.go
@@ -3,6 +3,8 @@ package bug
import (
"fmt"
+ "github.com/MichaelMure/git-bug/identity"
+
"github.com/MichaelMure/git-bug/util/git"
"github.com/MichaelMure/git-bug/util/text"
)
@@ -68,7 +70,7 @@ func (op *AddCommentOperation) Validate() error {
// Sign post method for gqlgen
func (op *AddCommentOperation) IsAuthored() {}
-func NewAddCommentOp(author Person, unixTime int64, message string, files []git.Hash) *AddCommentOperation {
+func NewAddCommentOp(author identity.Interface, unixTime int64, message string, files []git.Hash) *AddCommentOperation {
return &AddCommentOperation{
OpBase: newOpBase(AddCommentOp, author, unixTime),
Message: message,
@@ -82,11 +84,11 @@ type AddCommentTimelineItem struct {
}
// Convenience function to apply the operation
-func AddComment(b Interface, author Person, unixTime int64, message string) (*AddCommentOperation, error) {
+func AddComment(b Interface, author identity.Interface, unixTime int64, message string) (*AddCommentOperation, error) {
return AddCommentWithFiles(b, author, unixTime, message, nil)
}
-func AddCommentWithFiles(b Interface, author Person, unixTime int64, message string, files []git.Hash) (*AddCommentOperation, error) {
+func AddCommentWithFiles(b Interface, author identity.Interface, unixTime int64, message string, files []git.Hash) (*AddCommentOperation, error) {
addCommentOp := NewAddCommentOp(author, unixTime, message, files)
if err := addCommentOp.Validate(); err != nil {
return nil, err
diff --git a/bug/op_create.go b/bug/op_create.go
index 3816d8b7..01b2bf03 100644
--- a/bug/op_create.go
+++ b/bug/op_create.go
@@ -4,6 +4,8 @@ import (
"fmt"
"strings"
+ "github.com/MichaelMure/git-bug/identity"
+
"github.com/MichaelMure/git-bug/util/git"
"github.com/MichaelMure/git-bug/util/text"
)
@@ -84,7 +86,7 @@ func (op *CreateOperation) Validate() error {
// Sign post method for gqlgen
func (op *CreateOperation) IsAuthored() {}
-func NewCreateOp(author Person, unixTime int64, title, message string, files []git.Hash) *CreateOperation {
+func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []git.Hash) *CreateOperation {
return &CreateOperation{
OpBase: newOpBase(CreateOp, author, unixTime),
Title: title,
@@ -99,11 +101,11 @@ type CreateTimelineItem struct {
}
// Convenience function to apply the operation
-func Create(author Person, unixTime int64, title, message string) (*Bug, *CreateOperation, error) {
+func Create(author identity.Interface, unixTime int64, title, message string) (*Bug, *CreateOperation, error) {
return CreateWithFiles(author, unixTime, title, message, nil)
}
-func CreateWithFiles(author Person, unixTime int64, title, message string, files []git.Hash) (*Bug, *CreateOperation, error) {
+func CreateWithFiles(author identity.Interface, unixTime int64, title, message string, files []git.Hash) (*Bug, *CreateOperation, error) {
newBug := NewBug()
createOp := NewCreateOp(author, unixTime, title, message, files)
diff --git a/bug/op_create_test.go b/bug/op_create_test.go
index d74051ec..227dea27 100644
--- a/bug/op_create_test.go
+++ b/bug/op_create_test.go
@@ -4,16 +4,14 @@ import (
"testing"
"time"
+ "github.com/MichaelMure/git-bug/identity"
"github.com/go-test/deep"
)
func TestCreate(t *testing.T) {
snapshot := Snapshot{}
- var rene = Person{
- Name: "René Descartes",
- Email: "rene@descartes.fr",
- }
+ var rene = identity.NewBare("René Descartes", "rene@descartes.fr")
unix := time.Now().Unix()
diff --git a/bug/op_edit_comment.go b/bug/op_edit_comment.go
index bc87310a..9e0afc02 100644
--- a/bug/op_edit_comment.go
+++ b/bug/op_edit_comment.go
@@ -3,6 +3,8 @@ package bug
import (
"fmt"
+ "github.com/MichaelMure/git-bug/identity"
+
"github.com/MichaelMure/git-bug/util/git"
"github.com/MichaelMure/git-bug/util/text"
)
@@ -95,7 +97,7 @@ func (op *EditCommentOperation) Validate() error {
// Sign post method for gqlgen
func (op *EditCommentOperation) IsAuthored() {}
-func NewEditCommentOp(author Person, unixTime int64, target git.Hash, message string, files []git.Hash) *EditCommentOperation {
+func NewEditCommentOp(author identity.Interface, unixTime int64, target git.Hash, message string, files []git.Hash) *EditCommentOperation {
return &EditCommentOperation{
OpBase: newOpBase(EditCommentOp, author, unixTime),
Target: target,
@@ -105,11 +107,11 @@ func NewEditCommentOp(author Person, unixTime int64, target git.Hash, message st
}
// Convenience function to apply the operation
-func EditComment(b Interface, author Person, unixTime int64, target git.Hash, message string) (*EditCommentOperation, error) {
+func EditComment(b Interface, author identity.Interface, unixTime int64, target git.Hash, message string) (*EditCommentOperation, error) {
return EditCommentWithFiles(b, author, unixTime, target, message, nil)
}
-func EditCommentWithFiles(b Interface, author Person, unixTime int64, target git.Hash, message string, files []git.Hash) (*EditCommentOperation, error) {
+func EditCommentWithFiles(b Interface, author identity.Interface, unixTime int64, target git.Hash, message string, files []git.Hash) (*EditCommentOperation, error) {
editCommentOp := NewEditCommentOp(author, unixTime, target, message, files)
if err := editCommentOp.Validate(); err != nil {
return nil, err
diff --git a/bug/op_edit_comment_test.go b/bug/op_edit_comment_test.go
index 71a7dda2..ba9bc9d5 100644
--- a/bug/op_edit_comment_test.go
+++ b/bug/op_edit_comment_test.go
@@ -4,16 +4,14 @@ import (
"testing"
"time"
+ "github.com/MichaelMure/git-bug/identity"
"gotest.tools/assert"
)
func TestEdit(t *testing.T) {
snapshot := Snapshot{}
- var rene = Person{
- Name: "René Descartes",
- Email: "rene@descartes.fr",
- }
+ var rene = identity.NewBare("René Descartes", "rene@descartes.fr")
unix := time.Now().Unix()
diff --git a/bug/op_label_change.go b/bug/op_label_change.go
index d7aab06b..5d0b6a78 100644
--- a/bug/op_label_change.go
+++ b/bug/op_label_change.go
@@ -4,6 +4,8 @@ import (
"fmt"
"sort"
+ "github.com/MichaelMure/git-bug/identity"
+
"github.com/MichaelMure/git-bug/util/git"
"github.com/pkg/errors"
)
@@ -100,7 +102,7 @@ func (op *LabelChangeOperation) Validate() error {
// Sign post method for gqlgen
func (op *LabelChangeOperation) IsAuthored() {}
-func NewLabelChangeOperation(author Person, unixTime int64, added, removed []Label) *LabelChangeOperation {
+func NewLabelChangeOperation(author identity.Interface, unixTime int64, added, removed []Label) *LabelChangeOperation {
return &LabelChangeOperation{
OpBase: newOpBase(LabelChangeOp, author, unixTime),
Added: added,
@@ -110,7 +112,7 @@ func NewLabelChangeOperation(author Person, unixTime int64, added, removed []Lab
type LabelChangeTimelineItem struct {
hash git.Hash
- Author Person
+ Author identity.Interface
UnixTime Timestamp
Added []Label
Removed []Label
@@ -121,7 +123,7 @@ func (l LabelChangeTimelineItem) Hash() git.Hash {
}
// ChangeLabels is a convenience function to apply the operation
-func ChangeLabels(b Interface, author Person, unixTime int64, add, remove []string) ([]LabelChangeResult, *LabelChangeOperation, error) {
+func ChangeLabels(b Interface, author identity.Interface, unixTime int64, add, remove []string) ([]LabelChangeResult, *LabelChangeOperation, error) {
var added, removed []Label
var results []LabelChangeResult
diff --git a/bug/op_noop.go b/bug/op_noop.go
index ac898dde..410799b3 100644
--- a/bug/op_noop.go
+++ b/bug/op_noop.go
@@ -1,6 +1,9 @@
package bug
-import "github.com/MichaelMure/git-bug/util/git"
+import (
+ "github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/util/git"
+)
var _ Operation = &NoOpOperation{}
@@ -30,14 +33,14 @@ func (op *NoOpOperation) Validate() error {
// Sign post method for gqlgen
func (op *NoOpOperation) IsAuthored() {}
-func NewNoOpOp(author Person, unixTime int64) *NoOpOperation {
+func NewNoOpOp(author identity.Interface, unixTime int64) *NoOpOperation {
return &NoOpOperation{
OpBase: newOpBase(NoOpOp, author, unixTime),
}
}
// Convenience function to apply the operation
-func NoOp(b Interface, author Person, unixTime int64, metadata map[string]string) (*NoOpOperation, error) {
+func NoOp(b Interface, author identity.Interface, unixTime int64, metadata map[string]string) (*NoOpOperation, error) {
op := NewNoOpOp(author, unixTime)
for key, value := range metadata {
diff --git a/bug/op_set_metadata.go b/bug/op_set_metadata.go
index aac81f3b..e18f1cb6 100644
--- a/bug/op_set_metadata.go
+++ b/bug/op_set_metadata.go
@@ -1,6 +1,9 @@
package bug
-import "github.com/MichaelMure/git-bug/util/git"
+import (
+ "github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/util/git"
+)
var _ Operation = &SetMetadataOperation{}
@@ -56,7 +59,7 @@ func (op *SetMetadataOperation) Validate() error {
// Sign post method for gqlgen
func (op *SetMetadataOperation) IsAuthored() {}
-func NewSetMetadataOp(author Person, unixTime int64, target git.Hash, newMetadata map[string]string) *SetMetadataOperation {
+func NewSetMetadataOp(author identity.Interface, unixTime int64, target git.Hash, newMetadata map[string]string) *SetMetadataOperation {
return &SetMetadataOperation{
OpBase: newOpBase(SetMetadataOp, author, unixTime),
Target: target,
@@ -65,7 +68,7 @@ func NewSetMetadataOp(author Person, unixTime int64, target git.Hash, newMetadat
}
// Convenience function to apply the operation
-func SetMetadata(b Interface, author Person, unixTime int64, target git.Hash, newMetadata map[string]string) (*SetMetadataOperation, error) {
+func SetMetadata(b Interface, author identity.Interface, unixTime int64, target git.Hash, newMetadata map[string]string) (*SetMetadataOperation, error) {
SetMetadataOp := NewSetMetadataOp(author, unixTime, target, newMetadata)
if err := SetMetadataOp.Validate(); err != nil {
return nil, err
diff --git a/bug/op_set_metadata_test.go b/bug/op_set_metadata_test.go
index 068e2bb0..c6f5c3c1 100644
--- a/bug/op_set_metadata_test.go
+++ b/bug/op_set_metadata_test.go
@@ -4,16 +4,14 @@ import (
"testing"
"time"
+ "github.com/MichaelMure/git-bug/identity"
"github.com/stretchr/testify/assert"
)
func TestSetMetadata(t *testing.T) {
snapshot := Snapshot{}
- var rene = Person{
- Name: "René Descartes",
- Email: "rene@descartes.fr",
- }
+ var rene = identity.NewBare("René Descartes", "rene@descartes.fr")
unix := time.Now().Unix()
diff --git a/bug/op_set_status.go b/bug/op_set_status.go
index 54f476cb..9fc64e52 100644
--- a/bug/op_set_status.go
+++ b/bug/op_set_status.go
@@ -1,6 +1,7 @@
package bug
import (
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/git"
"github.com/pkg/errors"
)
@@ -56,7 +57,7 @@ func (op *SetStatusOperation) Validate() error {
// Sign post method for gqlgen
func (op *SetStatusOperation) IsAuthored() {}
-func NewSetStatusOp(author Person, unixTime int64, status Status) *SetStatusOperation {
+func NewSetStatusOp(author identity.Interface, unixTime int64, status Status) *SetStatusOperation {
return &SetStatusOperation{
OpBase: newOpBase(SetStatusOp, author, unixTime),
Status: status,
@@ -65,7 +66,7 @@ func NewSetStatusOp(author Person, unixTime int64, status Status) *SetStatusOper
type SetStatusTimelineItem struct {
hash git.Hash
- Author Person
+ Author identity.Interface
UnixTime Timestamp
Status Status
}
@@ -75,7 +76,7 @@ func (s SetStatusTimelineItem) Hash() git.Hash {
}
// Convenience function to apply the operation
-func Open(b Interface, author Person, unixTime int64) (*SetStatusOperation, error) {
+func Open(b Interface, author identity.Interface, unixTime int64) (*SetStatusOperation, error) {
op := NewSetStatusOp(author, unixTime, OpenStatus)
if err := op.Validate(); err != nil {
return nil, err
@@ -85,7 +86,7 @@ func Open(b Interface, author Person, unixTime int64) (*SetStatusOperation, erro
}
// Convenience function to apply the operation
-func Close(b Interface, author Person, unixTime int64) (*SetStatusOperation, error) {
+func Close(b Interface, author identity.Interface, unixTime int64) (*SetStatusOperation, error) {
op := NewSetStatusOp(author, unixTime, ClosedStatus)
if err := op.Validate(); err != nil {
return nil, err
diff --git a/bug/op_set_title.go b/bug/op_set_title.go
index b631ca18..3b253c06 100644
--- a/bug/op_set_title.go
+++ b/bug/op_set_title.go
@@ -4,6 +4,8 @@ import (
"fmt"
"strings"
+ "github.com/MichaelMure/git-bug/identity"
+
"github.com/MichaelMure/git-bug/util/git"
"github.com/MichaelMure/git-bug/util/text"
)
@@ -77,7 +79,7 @@ func (op *SetTitleOperation) Validate() error {
// Sign post method for gqlgen
func (op *SetTitleOperation) IsAuthored() {}
-func NewSetTitleOp(author Person, unixTime int64, title string, was string) *SetTitleOperation {
+func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was string) *SetTitleOperation {
return &SetTitleOperation{
OpBase: newOpBase(SetTitleOp, author, unixTime),
Title: title,
@@ -87,7 +89,7 @@ func NewSetTitleOp(author Person, unixTime int64, title string, was string) *Set
type SetTitleTimelineItem struct {
hash git.Hash
- Author Person
+ Author identity.Interface
UnixTime Timestamp
Title string
Was string
@@ -98,7 +100,7 @@ func (s SetTitleTimelineItem) Hash() git.Hash {
}
// Convenience function to apply the operation
-func SetTitle(b Interface, author Person, unixTime int64, title string) (*SetTitleOperation, error) {
+func SetTitle(b Interface, author identity.Interface, unixTime int64, title string) (*SetTitleOperation, error) {
it := NewOperationIterator(b)
var lastTitleOp Operation
diff --git a/bug/operation.go b/bug/operation.go
index 592b5616..8dec5644 100644
--- a/bug/operation.go
+++ b/bug/operation.go
@@ -6,6 +6,8 @@ import (
"fmt"
"time"
+ "github.com/MichaelMure/git-bug/identity"
+
"github.com/MichaelMure/git-bug/util/git"
"github.com/pkg/errors"
)
@@ -74,21 +76,23 @@ func hashOperation(op Operation) (git.Hash, error) {
return base.hash, nil
}
+// TODO: serialization with identity
+
// OpBase implement the common code for all operations
type OpBase struct {
- OperationType OperationType `json:"type"`
- Author Person `json:"author"`
- UnixTime int64 `json:"timestamp"`
- Metadata map[string]string `json:"metadata,omitempty"`
+ OperationType OperationType
+ Author identity.Interface
+ UnixTime int64
+ Metadata map[string]string
// Not serialized. Store the op's hash in memory.
hash git.Hash
- // Not serialized. Store the extra metadata compiled from SetMetadataOperation
- // in memory.
+ // Not serialized. Store the extra metadata in memory,
+ // compiled from SetMetadataOperation.
extraMetadata map[string]string
}
// newOpBase is the constructor for an OpBase
-func newOpBase(opType OperationType, author Person, unixTime int64) OpBase {
+func newOpBase(opType OperationType, author identity.Interface, unixTime int64) OpBase {
return OpBase{
OperationType: opType,
Author: author,
@@ -96,6 +100,34 @@ func newOpBase(opType OperationType, author Person, unixTime int64) OpBase {
}
}
+type opBaseJson struct {
+ OperationType OperationType `json:"type"`
+ UnixTime int64 `json:"timestamp"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
+func (op *OpBase) MarshalJSON() ([]byte, error) {
+ return json.Marshal(opBaseJson{
+ OperationType: op.OperationType,
+ UnixTime: op.UnixTime,
+ Metadata: op.Metadata,
+ })
+}
+
+func (op *OpBase) UnmarshalJSON(data []byte) error {
+ aux := opBaseJson{}
+
+ if err := json.Unmarshal(data, &aux); err != nil {
+ return err
+ }
+
+ op.OperationType = aux.OperationType
+ op.UnixTime = aux.UnixTime
+ op.Metadata = aux.Metadata
+
+ return nil
+}
+
// Time return the time when the operation was added
func (op *OpBase) Time() time.Time {
return time.Unix(op.UnixTime, 0)
@@ -125,6 +157,10 @@ func opBaseValidate(op Operation, opType OperationType) error {
return fmt.Errorf("time not set")
}
+ if op.base().Author == nil {
+ return fmt.Errorf("author not set")
+ }
+
if err := op.base().Author.Validate(); err != nil {
return errors.Wrap(err, "author")
}
diff --git a/bug/operation_iterator_test.go b/bug/operation_iterator_test.go
index 506cc94f..6b32cfc4 100644
--- a/bug/operation_iterator_test.go
+++ b/bug/operation_iterator_test.go
@@ -1,17 +1,14 @@
package bug
import (
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
"testing"
"time"
)
var (
- rene = Person{
- Name: "René Descartes",
- Email: "rene@descartes.fr",
- }
-
+ rene = identity.NewBare("René Descartes", "rene@descartes.fr")
unix = time.Now().Unix()
createOp = NewCreateOp(rene, unix, "title", "message", nil)
diff --git a/bug/operation_pack.go b/bug/operation_pack.go
index f33d94bf..fc395d90 100644
--- a/bug/operation_pack.go
+++ b/bug/operation_pack.go
@@ -20,7 +20,7 @@ const formatVersion = 1
type OperationPack struct {
Operations []Operation
- // Private field so not serialized by gob
+ // Private field so not serialized
commitHash git.Hash
}
@@ -57,6 +57,7 @@ func (opp *OperationPack) UnmarshalJSON(data []byte) error {
return err
}
+ // delegate to specialized unmarshal function
op, err := opp.unmarshalOp(raw, t.OperationType)
if err != nil {
return err
@@ -73,28 +74,36 @@ func (opp *OperationPack) UnmarshalJSON(data []byte) error {
func (opp *OperationPack) unmarshalOp(raw []byte, _type OperationType) (Operation, error) {
switch _type {
+ case AddCommentOp:
+ op := &AddCommentOperation{}
+ err := json.Unmarshal(raw, &op)
+ return op, err
case CreateOp:
op := &CreateOperation{}
err := json.Unmarshal(raw, &op)
return op, err
- case SetTitleOp:
- op := &SetTitleOperation{}
+ case EditCommentOp:
+ op := &EditCommentOperation{}
err := json.Unmarshal(raw, &op)
return op, err
- case AddCommentOp:
- op := &AddCommentOperation{}
+ case LabelChangeOp:
+ op := &LabelChangeOperation{}
err := json.Unmarshal(raw, &op)
return op, err
- case SetStatusOp:
- op := &SetStatusOperation{}
+ case NoOpOp:
+ op := &NoOpOperation{}
err := json.Unmarshal(raw, &op)
return op, err
- case LabelChangeOp:
- op := &LabelChangeOperation{}
+ case SetMetadataOp:
+ op := &SetMetadataOperation{}
err := json.Unmarshal(raw, &op)
return op, err
- case EditCommentOp:
- op := &EditCommentOperation{}
+ case SetStatusOp:
+ op := &SetStatusOperation{}
+ err := json.Unmarshal(raw, &op)
+ return op, err
+ case SetTitleOp:
+ op := &SetTitleOperation{}
err := json.Unmarshal(raw, &op)
return op, err
default:
diff --git a/bug/operation_test.go b/bug/operation_test.go
index 255d6d98..0e2afc6c 100644
--- a/bug/operation_test.go
+++ b/bug/operation_test.go
@@ -3,6 +3,7 @@ package bug
import (
"testing"
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/git"
"github.com/stretchr/testify/require"
@@ -25,11 +26,11 @@ func TestValidate(t *testing.T) {
bad := []Operation{
// opbase
- NewSetStatusOp(Person{Name: "", Email: "rene@descartes.fr"}, unix, ClosedStatus),
- NewSetStatusOp(Person{Name: "René Descartes\u001b", Email: "rene@descartes.fr"}, unix, ClosedStatus),
- NewSetStatusOp(Person{Name: "René Descartes", Email: "rene@descartes.fr\u001b"}, unix, ClosedStatus),
- NewSetStatusOp(Person{Name: "René \nDescartes", Email: "rene@descartes.fr"}, unix, ClosedStatus),
- NewSetStatusOp(Person{Name: "René Descartes", Email: "rene@\ndescartes.fr"}, unix, ClosedStatus),
+ NewSetStatusOp(identity.NewBare("", "rene@descartes.fr"), unix, ClosedStatus),
+ NewSetStatusOp(identity.NewBare("René Descartes\u001b", "rene@descartes.fr"), unix, ClosedStatus),
+ NewSetStatusOp(identity.NewBare("René Descartes", "rene@descartes.fr\u001b"), unix, ClosedStatus),
+ NewSetStatusOp(identity.NewBare("René \nDescartes", "rene@descartes.fr"), unix, ClosedStatus),
+ NewSetStatusOp(identity.NewBare("René Descartes", "rene@\ndescartes.fr"), unix, ClosedStatus),
&CreateOperation{OpBase: OpBase{
Author: rene,
UnixTime: 0,
diff --git a/bug/person.go b/bug/person.go
deleted file mode 100644
index 449e2262..00000000
--- a/bug/person.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package bug
-
-import (
- "errors"
- "fmt"
- "strings"
-
- "github.com/MichaelMure/git-bug/repository"
- "github.com/MichaelMure/git-bug/util/text"
-)
-
-type Person struct {
- Name string `json:"name"`
- Email string `json:"email"`
- Login string `json:"login"`
- AvatarUrl string `json:"avatar_url"`
-}
-
-// GetUser will query the repository for user detail and build the corresponding Person
-func GetUser(repo repository.Repo) (Person, error) {
- name, err := repo.GetUserName()
- if err != nil {
- return Person{}, err
- }
- if name == "" {
- return Person{}, 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 Person{}, err
- }
- if email == "" {
- return Person{}, errors.New("User name is not configured in git yet. Please use `git config --global user.email johndoe@example.com`")
- }
-
- return Person{Name: name, Email: email}, nil
-}
-
-// Match tell is the Person match the given query string
-func (p Person) Match(query string) bool {
- query = strings.ToLower(query)
-
- return strings.Contains(strings.ToLower(p.Name), query) ||
- strings.Contains(strings.ToLower(p.Login), query)
-}
-
-func (p Person) Validate() error {
- if text.Empty(p.Name) && text.Empty(p.Login) {
- return fmt.Errorf("either name or login should be set")
- }
-
- if strings.Contains(p.Name, "\n") {
- return fmt.Errorf("name should be a single line")
- }
-
- if !text.Safe(p.Name) {
- return fmt.Errorf("name is not fully printable")
- }
-
- if strings.Contains(p.Login, "\n") {
- return fmt.Errorf("login should be a single line")
- }
-
- if !text.Safe(p.Login) {
- return fmt.Errorf("login is not fully printable")
- }
-
- if strings.Contains(p.Email, "\n") {
- return fmt.Errorf("email should be a single line")
- }
-
- if !text.Safe(p.Email) {
- return fmt.Errorf("email is not fully printable")
- }
-
- if p.AvatarUrl != "" && !text.ValidUrl(p.AvatarUrl) {
- return fmt.Errorf("avatarUrl is not a valid URL")
- }
-
- return nil
-}
-
-func (p Person) DisplayName() string {
- switch {
- case p.Name == "" && p.Login != "":
- return p.Login
- case p.Name != "" && p.Login == "":
- return p.Name
- case p.Name != "" && p.Login != "":
- return fmt.Sprintf("%s (%s)", p.Name, p.Login)
- }
-
- panic("invalid person data")
-}
diff --git a/bug/snapshot.go b/bug/snapshot.go
index 72e673d4..46618ebd 100644
--- a/bug/snapshot.go
+++ b/bug/snapshot.go
@@ -4,6 +4,7 @@ import (
"fmt"
"time"
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/git"
)
@@ -15,7 +16,7 @@ type Snapshot struct {
Title string
Comments []Comment
Labels []Label
- Author Person
+ Author identity.Interface
CreatedAt time.Time
Timeline []TimelineItem
diff --git a/bug/timeline.go b/bug/timeline.go
index a84c45e4..306ffa9e 100644
--- a/bug/timeline.go
+++ b/bug/timeline.go
@@ -3,6 +3,7 @@ package bug
import (
"strings"
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/git"
)
@@ -15,7 +16,7 @@ type TimelineItem interface {
type CommentHistoryStep struct {
// The author of the edition, not necessarily the same as the author of the
// original comment
- Author Person
+ Author identity.Interface
// The new message
Message string
UnixTime Timestamp
@@ -24,7 +25,7 @@ type CommentHistoryStep struct {
// CommentTimelineItem is a TimelineItem that holds a Comment and its edition history
type CommentTimelineItem struct {
hash git.Hash
- Author Person
+ Author identity.Interface
Message string
Files []git.Hash
CreatedAt Timestamp
diff --git a/cache/bug_cache.go b/cache/bug_cache.go
index 52e9eafb..25ff000c 100644
--- a/cache/bug_cache.go
+++ b/cache/bug_cache.go
@@ -5,6 +5,8 @@ import (
"strings"
"time"
+ "github.com/MichaelMure/git-bug/identity"
+
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/util/git"
)
@@ -87,7 +89,7 @@ func (c *BugCache) AddComment(message string) error {
}
func (c *BugCache) AddCommentWithFiles(message string, files []git.Hash) error {
- author, err := bug.GetUser(c.repoCache.repo)
+ author, err := identity.GetIdentity(c.repoCache.repo)
if err != nil {
return err
}
@@ -95,7 +97,7 @@ func (c *BugCache) AddCommentWithFiles(message string, files []git.Hash) error {
return c.AddCommentRaw(author, time.Now().Unix(), message, files, nil)
}
-func (c *BugCache) AddCommentRaw(author bug.Person, unixTime int64, message string, files []git.Hash, metadata map[string]string) error {
+func (c *BugCache) AddCommentRaw(author *identity.Identity, unixTime int64, message string, files []git.Hash, metadata map[string]string) error {
op, err := bug.AddCommentWithFiles(c.bug, author, unixTime, message, files)
if err != nil {
return err
@@ -109,7 +111,7 @@ func (c *BugCache) AddCommentRaw(author bug.Person, unixTime int64, message stri
}
func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelChangeResult, error) {
- author, err := bug.GetUser(c.repoCache.repo)
+ author, err := identity.GetIdentity(c.repoCache.repo)
if err != nil {
return nil, err
}
@@ -117,7 +119,7 @@ func (c *BugCache) ChangeLabels(added []string, removed []string) ([]bug.LabelCh
return c.ChangeLabelsRaw(author, time.Now().Unix(), added, removed, nil)
}
-func (c *BugCache) ChangeLabelsRaw(author bug.Person, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, error) {
+func (c *BugCache) ChangeLabelsRaw(author *identity.Identity, unixTime int64, added []string, removed []string, metadata map[string]string) ([]bug.LabelChangeResult, error) {
changes, op, err := bug.ChangeLabels(c.bug, author, unixTime, added, removed)
if err != nil {
return changes, err
@@ -136,7 +138,7 @@ func (c *BugCache) ChangeLabelsRaw(author bug.Person, unixTime int64, added []st
}
func (c *BugCache) Open() error {
- author, err := bug.GetUser(c.repoCache.repo)
+ author, err := identity.GetIdentity(c.repoCache.repo)
if err != nil {
return err
}
@@ -144,7 +146,7 @@ func (c *BugCache) Open() error {
return c.OpenRaw(author, time.Now().Unix(), nil)
}
-func (c *BugCache) OpenRaw(author bug.Person, unixTime int64, metadata map[string]string) error {
+func (c *BugCache) OpenRaw(author *identity.Identity, unixTime int64, metadata map[string]string) error {
op, err := bug.Open(c.bug, author, unixTime)
if err != nil {
return err
@@ -158,7 +160,7 @@ func (c *BugCache) OpenRaw(author bug.Person, unixTime int64, metadata map[strin
}
func (c *BugCache) Close() error {
- author, err := bug.GetUser(c.repoCache.repo)
+ author, err := identity.GetIdentity(c.repoCache.repo)
if err != nil {
return err
}
@@ -166,7 +168,7 @@ func (c *BugCache) Close() error {
return c.CloseRaw(author, time.Now().Unix(), nil)
}
-func (c *BugCache) CloseRaw(author bug.Person, unixTime int64, metadata map[string]string) error {
+func (c *BugCache) CloseRaw(author *identity.Identity, unixTime int64, metadata map[string]string) error {
op, err := bug.Close(c.bug, author, unixTime)
if err != nil {
return err
@@ -180,7 +182,7 @@ func (c *BugCache) CloseRaw(author bug.Person, unixTime int64, metadata map[stri
}
func (c *BugCache) SetTitle(title string) error {
- author, err := bug.GetUser(c.repoCache.repo)
+ author, err := identity.GetIdentity(c.repoCache.repo)
if err != nil {
return err
}
@@ -188,7 +190,7 @@ func (c *BugCache) SetTitle(title string) error {
return c.SetTitleRaw(author, time.Now().Unix(), title, nil)
}
-func (c *BugCache) SetTitleRaw(author bug.Person, unixTime int64, title string, metadata map[string]string) error {
+func (c *BugCache) SetTitleRaw(author *identity.Identity, unixTime int64, title string, metadata map[string]string) error {
op, err := bug.SetTitle(c.bug, author, unixTime, title)
if err != nil {
return err
@@ -202,7 +204,7 @@ func (c *BugCache) SetTitleRaw(author bug.Person, unixTime int64, title string,
}
func (c *BugCache) EditComment(target git.Hash, message string) error {
- author, err := bug.GetUser(c.repoCache.repo)
+ author, err := identity.GetIdentity(c.repoCache.repo)
if err != nil {
return err
}
@@ -210,7 +212,7 @@ func (c *BugCache) EditComment(target git.Hash, message string) error {
return c.EditCommentRaw(author, time.Now().Unix(), target, message, nil)
}
-func (c *BugCache) EditCommentRaw(author bug.Person, unixTime int64, target git.Hash, message string, metadata map[string]string) error {
+func (c *BugCache) EditCommentRaw(author *identity.Identity, unixTime int64, target git.Hash, message string, metadata map[string]string) error {
op, err := bug.EditComment(c.bug, author, unixTime, target, message)
if err != nil {
return err
diff --git a/cache/bug_excerpt.go b/cache/bug_excerpt.go
index 7a11fa82..77c18175 100644
--- a/cache/bug_excerpt.go
+++ b/cache/bug_excerpt.go
@@ -3,6 +3,8 @@ package cache
import (
"encoding/gob"
+ "github.com/MichaelMure/git-bug/identity"
+
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/util/lamport"
)
@@ -18,7 +20,7 @@ type BugExcerpt struct {
EditUnixTime int64
Status bug.Status
- Author bug.Person
+ Author *identity.Identity
Labels []bug.Label
CreateMetadata map[string]string
diff --git a/cache/repo_cache.go b/cache/repo_cache.go
index 286e27a5..a149fd73 100644
--- a/cache/repo_cache.go
+++ b/cache/repo_cache.go
@@ -14,6 +14,7 @@ import (
"time"
"github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/git"
"github.com/MichaelMure/git-bug/util/process"
@@ -376,7 +377,7 @@ func (c *RepoCache) NewBug(title string, message string) (*BugCache, error) {
// NewBugWithFiles create a new bug with attached files for the message
// The new bug is written in the repository (commit)
func (c *RepoCache) NewBugWithFiles(title string, message string, files []git.Hash) (*BugCache, error) {
- author, err := bug.GetUser(c.repo)
+ author, err := identity.GetIdentity(c.repo)
if err != nil {
return nil, err
}
@@ -387,7 +388,7 @@ func (c *RepoCache) NewBugWithFiles(title string, message string, files []git.Ha
// NewBugWithFilesMeta create a new bug with attached files for the message, as
// well as metadata for the Create operation.
// The new bug is written in the repository (commit)
-func (c *RepoCache) NewBugRaw(author bug.Person, unixTime int64, title string, message string, files []git.Hash, metadata map[string]string) (*BugCache, error) {
+func (c *RepoCache) NewBugRaw(author *identity.Identity, unixTime int64, title string, message string, files []git.Hash, metadata map[string]string) (*BugCache, error) {
b, op, err := bug.CreateWithFiles(author, unixTime, title, message, files)
if err != nil {
return nil, err
diff --git a/commands/id.go b/commands/id.go
new file mode 100644
index 00000000..485c5457
--- /dev/null
+++ b/commands/id.go
@@ -0,0 +1,35 @@
+package commands
+
+import (
+ "fmt"
+
+ "github.com/MichaelMure/git-bug/identity"
+ "github.com/spf13/cobra"
+)
+
+func runId(cmd *cobra.Command, args []string) error {
+ id, err := identity.GetIdentity(repo)
+ if err != nil {
+ return err
+ }
+
+ fmt.Printf("Id: %s\n", id.Id())
+ fmt.Printf("Identity: %s\n", id.DisplayName())
+ fmt.Printf("Name: %s\n", id.Name())
+ fmt.Printf("Login: %s\n", id.Login())
+ fmt.Printf("Email: %s\n", id.Email())
+ fmt.Printf("Protected: %v\n", id.IsProtected())
+
+ return nil
+}
+
+var idCmd = &cobra.Command{
+ Use: "id",
+ Short: "Display or change the user identity",
+ PreRunE: loadRepo,
+ RunE: runId,
+}
+
+func init() {
+ RootCmd.AddCommand(idCmd)
+}
diff --git a/graphql/gqlgen.yml b/graphql/gqlgen.yml
index b6dc3ae5..019f3444 100644
--- a/graphql/gqlgen.yml
+++ b/graphql/gqlgen.yml
@@ -1,4 +1,4 @@
-schema: "*.graphql"
+schema: "schema/*.graphql"
exec:
filename: graph/gen_graph.go
model:
@@ -13,17 +13,8 @@ models:
model: github.com/MichaelMure/git-bug/bug.Snapshot
Comment:
model: github.com/MichaelMure/git-bug/bug.Comment
- Person:
- model: github.com/MichaelMure/git-bug/bug.Person
- fields:
- name:
- resolver: true
- email:
- resolver: true
- login:
- resolver: true
- avatarUrl:
- resolver: true
+ Identity:
+ model: github.com/MichaelMure/git-bug/identity.Identity
Label:
model: github.com/MichaelMure/git-bug/bug.Label
Hash:
diff --git a/graphql/graph/gen_graph.go b/graphql/graph/gen_graph.go
index e7d09ef4..cc714ecc 100644
--- a/graphql/graph/gen_graph.go
+++ b/graphql/graph/gen_graph.go
@@ -15,6 +15,7 @@ import (
"github.com/99designs/gqlgen/graphql/introspection"
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/graphql/models"
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/git"
"github.com/vektah/gqlparser"
"github.com/vektah/gqlparser/ast"
@@ -46,7 +47,6 @@ type ResolverRoot interface {
LabelChangeOperation() LabelChangeOperationResolver
LabelChangeTimelineItem() LabelChangeTimelineItemResolver
Mutation() MutationResolver
- Person() PersonResolver
Query() QueryResolver
Repository() RepositoryResolver
SetStatusOperation() SetStatusOperationResolver
@@ -158,6 +158,14 @@ type ComplexityRoot struct {
Files func(childComplexity int) int
}
+ Identity struct {
+ Name func(childComplexity int) int
+ Email func(childComplexity int) int
+ Login func(childComplexity int) int
+ DisplayName func(childComplexity int) int
+ AvatarUrl func(childComplexity int) int
+ }
+
LabelChangeOperation struct {
Hash func(childComplexity int) int
Author func(childComplexity int) int
@@ -203,14 +211,6 @@ type ComplexityRoot struct {
EndCursor func(childComplexity int) int
}
- Person struct {
- Name func(childComplexity int) int
- Email func(childComplexity int) int
- Login func(childComplexity int) int
- DisplayName func(childComplexity int) int
- AvatarUrl func(childComplexity int) int
- }
-
Query struct {
DefaultRepository func(childComplexity int) int
Repository func(childComplexity int, id string) int
@@ -307,13 +307,6 @@ type MutationResolver interface {
SetTitle(ctx context.Context, repoRef *string, prefix string, title string) (bug.Snapshot, error)
Commit(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error)
}
-type PersonResolver interface {
- Name(ctx context.Context, obj *bug.Person) (*string, error)
- Email(ctx context.Context, obj *bug.Person) (*string, error)
- Login(ctx context.Context, obj *bug.Person) (*string, error)
-
- AvatarURL(ctx context.Context, obj *bug.Person) (*string, error)
-}
type QueryResolver interface {
DefaultRepository(ctx context.Context) (*models.Repository, error)
Repository(ctx context.Context, id string) (*models.Repository, error)
@@ -1453,6 +1446,41 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.EditCommentOperation.Files(childComplexity), true
+ case "Identity.name":
+ if e.complexity.Identity.Name == nil {
+ break
+ }
+
+ return e.complexity.Identity.Name(childComplexity), true
+
+ case "Identity.email":
+ if e.complexity.Identity.Email == nil {
+ break
+ }
+
+ return e.complexity.Identity.Email(childComplexity), true
+
+ case "Identity.login":
+ if e.complexity.Identity.Login == nil {
+ break
+ }
+
+ return e.complexity.Identity.Login(childComplexity), true
+
+ case "Identity.displayName":
+ if e.complexity.Identity.DisplayName == nil {
+ break
+ }
+
+ return e.complexity.Identity.DisplayName(childComplexity), true
+
+ case "Identity.avatarUrl":
+ if e.complexity.Identity.AvatarUrl == nil {
+ break
+ }
+
+ return e.complexity.Identity.AvatarUrl(childComplexity), true
+
case "LabelChangeOperation.hash":
if e.complexity.LabelChangeOperation.Hash == nil {
break
@@ -1677,41 +1705,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.PageInfo.EndCursor(childComplexity), true
- case "Person.name":
- if e.complexity.Person.Name == nil {
- break
- }
-
- return e.complexity.Person.Name(childComplexity), true
-
- case "Person.email":
- if e.complexity.Person.Email == nil {
- break
- }
-
- return e.complexity.Person.Email(childComplexity), true
-
- case "Person.login":
- if e.complexity.Person.Login == nil {
- break
- }
-
- return e.complexity.Person.Login(childComplexity), true
-
- case "Person.displayName":
- if e.complexity.Person.DisplayName == nil {
- break
- }
-
- return e.complexity.Person.DisplayName(childComplexity), true
-
- case "Person.avatarUrl":
- if e.complexity.Person.AvatarUrl == nil {
- break
- }
-
- return e.complexity.Person.AvatarUrl(childComplexity), true
-
case "Query.defaultRepository":
if e.complexity.Query.DefaultRepository == nil {
break
@@ -2072,11 +2065,18 @@ func (ec *executionContext) _AddCommentOperation_author(ctx context.Context, fie
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -2296,11 +2296,18 @@ func (ec *executionContext) _AddCommentTimelineItem_author(ctx context.Context,
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -2800,11 +2807,18 @@ func (ec *executionContext) _Bug_author(ctx context.Context, field graphql.Colle
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -3334,11 +3348,18 @@ func (ec *executionContext) _Comment_author(ctx context.Context, field graphql.C
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -3916,11 +3937,18 @@ func (ec *executionContext) _CreateOperation_author(ctx context.Context, field g
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -4167,11 +4195,18 @@ func (ec *executionContext) _CreateTimelineItem_author(ctx context.Context, fiel
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -4513,11 +4548,18 @@ func (ec *executionContext) _EditCommentOperation_author(ctx context.Context, fi
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -4637,6 +4679,167 @@ func (ec *executionContext) _EditCommentOperation_files(ctx context.Context, fie
return arr1
}
+var identityImplementors = []string{"Identity"}
+
+// nolint: gocyclo, errcheck, gas, goconst
+func (ec *executionContext) _Identity(ctx context.Context, sel ast.SelectionSet, obj *identity.Identity) graphql.Marshaler {
+ fields := graphql.CollectFields(ctx, sel, identityImplementors)
+
+ out := graphql.NewOrderedMap(len(fields))
+ invalid := false
+ for i, field := range fields {
+ out.Keys[i] = field.Alias
+
+ switch field.Name {
+ case "__typename":
+ out.Values[i] = graphql.MarshalString("Identity")
+ case "name":
+ out.Values[i] = ec._Identity_name(ctx, field, obj)
+ case "email":
+ out.Values[i] = ec._Identity_email(ctx, field, obj)
+ case "login":
+ out.Values[i] = ec._Identity_login(ctx, field, obj)
+ case "displayName":
+ out.Values[i] = ec._Identity_displayName(ctx, field, obj)
+ if out.Values[i] == graphql.Null {
+ invalid = true
+ }
+ case "avatarUrl":
+ out.Values[i] = ec._Identity_avatarUrl(ctx, field, obj)
+ default:
+ panic("unknown field " + strconv.Quote(field.Name))
+ }
+ }
+
+ if invalid {
+ return graphql.Null
+ }
+ return out
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _Identity_name(ctx context.Context, field graphql.CollectedField, obj *identity.Identity) graphql.Marshaler {
+ ctx = ec.Tracer.StartFieldExecution(ctx, field)
+ defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+ rctx := &graphql.ResolverContext{
+ Object: "Identity",
+ Args: nil,
+ Field: field,
+ }
+ ctx = graphql.WithResolverContext(ctx, rctx)
+ ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+ resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return obj.Name(), nil
+ })
+ if resTmp == nil {
+ return graphql.Null
+ }
+ res := resTmp.(string)
+ rctx.Result = res
+ ctx = ec.Tracer.StartFieldChildExecution(ctx)
+ return graphql.MarshalString(res)
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _Identity_email(ctx context.Context, field graphql.CollectedField, obj *identity.Identity) graphql.Marshaler {
+ ctx = ec.Tracer.StartFieldExecution(ctx, field)
+ defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+ rctx := &graphql.ResolverContext{
+ Object: "Identity",
+ Args: nil,
+ Field: field,
+ }
+ ctx = graphql.WithResolverContext(ctx, rctx)
+ ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+ resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return obj.Email(), nil
+ })
+ if resTmp == nil {
+ return graphql.Null
+ }
+ res := resTmp.(string)
+ rctx.Result = res
+ ctx = ec.Tracer.StartFieldChildExecution(ctx)
+ return graphql.MarshalString(res)
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _Identity_login(ctx context.Context, field graphql.CollectedField, obj *identity.Identity) graphql.Marshaler {
+ ctx = ec.Tracer.StartFieldExecution(ctx, field)
+ defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+ rctx := &graphql.ResolverContext{
+ Object: "Identity",
+ Args: nil,
+ Field: field,
+ }
+ ctx = graphql.WithResolverContext(ctx, rctx)
+ ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+ resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return obj.Login(), nil
+ })
+ if resTmp == nil {
+ return graphql.Null
+ }
+ res := resTmp.(string)
+ rctx.Result = res
+ ctx = ec.Tracer.StartFieldChildExecution(ctx)
+ return graphql.MarshalString(res)
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _Identity_displayName(ctx context.Context, field graphql.CollectedField, obj *identity.Identity) graphql.Marshaler {
+ ctx = ec.Tracer.StartFieldExecution(ctx, field)
+ defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+ rctx := &graphql.ResolverContext{
+ Object: "Identity",
+ Args: nil,
+ Field: field,
+ }
+ ctx = graphql.WithResolverContext(ctx, rctx)
+ ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+ resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return obj.DisplayName(), nil
+ })
+ if resTmp == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ res := resTmp.(string)
+ rctx.Result = res
+ ctx = ec.Tracer.StartFieldChildExecution(ctx)
+ return graphql.MarshalString(res)
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _Identity_avatarUrl(ctx context.Context, field graphql.CollectedField, obj *identity.Identity) graphql.Marshaler {
+ ctx = ec.Tracer.StartFieldExecution(ctx, field)
+ defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+ rctx := &graphql.ResolverContext{
+ Object: "Identity",
+ Args: nil,
+ Field: field,
+ }
+ ctx = graphql.WithResolverContext(ctx, rctx)
+ ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+ resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return obj.AvatarURL(), nil
+ })
+ if resTmp == nil {
+ return graphql.Null
+ }
+ res := resTmp.(string)
+ rctx.Result = res
+ ctx = ec.Tracer.StartFieldChildExecution(ctx)
+ return graphql.MarshalString(res)
+}
+
var labelChangeOperationImplementors = []string{"LabelChangeOperation", "Operation", "Authored"}
// nolint: gocyclo, errcheck, gas, goconst
@@ -4740,11 +4943,18 @@ func (ec *executionContext) _LabelChangeOperation_author(ctx context.Context, fi
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -4949,11 +5159,18 @@ func (ec *executionContext) _LabelChangeTimelineItem_author(ctx context.Context,
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -5820,200 +6037,6 @@ func (ec *executionContext) _PageInfo_endCursor(ctx context.Context, field graph
return graphql.MarshalString(res)
}
-var personImplementors = []string{"Person"}
-
-// nolint: gocyclo, errcheck, gas, goconst
-func (ec *executionContext) _Person(ctx context.Context, sel ast.SelectionSet, obj *bug.Person) graphql.Marshaler {
- fields := graphql.CollectFields(ctx, sel, personImplementors)
-
- var wg sync.WaitGroup
- out := graphql.NewOrderedMap(len(fields))
- invalid := false
- for i, field := range fields {
- out.Keys[i] = field.Alias
-
- switch field.Name {
- case "__typename":
- out.Values[i] = graphql.MarshalString("Person")
- case "name":
- wg.Add(1)
- go func(i int, field graphql.CollectedField) {
- out.Values[i] = ec._Person_name(ctx, field, obj)
- wg.Done()
- }(i, field)
- case "email":
- wg.Add(1)
- go func(i int, field graphql.CollectedField) {
- out.Values[i] = ec._Person_email(ctx, field, obj)
- wg.Done()
- }(i, field)
- case "login":
- wg.Add(1)
- go func(i int, field graphql.CollectedField) {
- out.Values[i] = ec._Person_login(ctx, field, obj)
- wg.Done()
- }(i, field)
- case "displayName":
- out.Values[i] = ec._Person_displayName(ctx, field, obj)
- if out.Values[i] == graphql.Null {
- invalid = true
- }
- case "avatarUrl":
- wg.Add(1)
- go func(i int, field graphql.CollectedField) {
- out.Values[i] = ec._Person_avatarUrl(ctx, field, obj)
- wg.Done()
- }(i, field)
- default:
- panic("unknown field " + strconv.Quote(field.Name))
- }
- }
- wg.Wait()
- if invalid {
- return graphql.Null
- }
- return out
-}
-
-// nolint: vetshadow
-func (ec *executionContext) _Person_name(ctx context.Context, field graphql.CollectedField, obj *bug.Person) graphql.Marshaler {
- ctx = ec.Tracer.StartFieldExecution(ctx, field)
- defer func() { ec.Tracer.EndFieldExecution(ctx) }()
- rctx := &graphql.ResolverContext{
- Object: "Person",
- Args: nil,
- Field: field,
- }
- ctx = graphql.WithResolverContext(ctx, rctx)
- ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
- resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
- ctx = rctx // use context from middleware stack in children
- return ec.resolvers.Person().Name(rctx, obj)
- })
- if resTmp == nil {
- return graphql.Null
- }
- res := resTmp.(*string)
- rctx.Result = res
- ctx = ec.Tracer.StartFieldChildExecution(ctx)
-
- if res == nil {
- return graphql.Null
- }
- return graphql.MarshalString(*res)
-}
-
-// nolint: vetshadow
-func (ec *executionContext) _Person_email(ctx context.Context, field graphql.CollectedField, obj *bug.Person) graphql.Marshaler {
- ctx = ec.Tracer.StartFieldExecution(ctx, field)
- defer func() { ec.Tracer.EndFieldExecution(ctx) }()
- rctx := &graphql.ResolverContext{
- Object: "Person",
- Args: nil,
- Field: field,
- }
- ctx = graphql.WithResolverContext(ctx, rctx)
- ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
- resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
- ctx = rctx // use context from middleware stack in children
- return ec.resolvers.Person().Email(rctx, obj)
- })
- if resTmp == nil {
- return graphql.Null
- }
- res := resTmp.(*string)
- rctx.Result = res
- ctx = ec.Tracer.StartFieldChildExecution(ctx)
-
- if res == nil {
- return graphql.Null
- }
- return graphql.MarshalString(*res)
-}
-
-// nolint: vetshadow
-func (ec *executionContext) _Person_login(ctx context.Context, field graphql.CollectedField, obj *bug.Person) graphql.Marshaler {
- ctx = ec.Tracer.StartFieldExecution(ctx, field)
- defer func() { ec.Tracer.EndFieldExecution(ctx) }()
- rctx := &graphql.ResolverContext{
- Object: "Person",
- Args: nil,
- Field: field,
- }
- ctx = graphql.WithResolverContext(ctx, rctx)
- ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
- resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
- ctx = rctx // use context from middleware stack in children
- return ec.resolvers.Person().Login(rctx, obj)
- })
- if resTmp == nil {
- return graphql.Null
- }
- res := resTmp.(*string)
- rctx.Result = res
- ctx = ec.Tracer.StartFieldChildExecution(ctx)
-
- if res == nil {
- return graphql.Null
- }
- return graphql.MarshalString(*res)
-}
-
-// nolint: vetshadow
-func (ec *executionContext) _Person_displayName(ctx context.Context, field graphql.CollectedField, obj *bug.Person) graphql.Marshaler {
- ctx = ec.Tracer.StartFieldExecution(ctx, field)
- defer func() { ec.Tracer.EndFieldExecution(ctx) }()
- rctx := &graphql.ResolverContext{
- Object: "Person",
- Args: nil,
- Field: field,
- }
- ctx = graphql.WithResolverContext(ctx, rctx)
- ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
- resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
- ctx = rctx // use context from middleware stack in children
- return obj.DisplayName(), nil
- })
- if resTmp == nil {
- if !ec.HasError(rctx) {
- ec.Errorf(ctx, "must not be null")
- }
- return graphql.Null
- }
- res := resTmp.(string)
- rctx.Result = res
- ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return graphql.MarshalString(res)
-}
-
-// nolint: vetshadow
-func (ec *executionContext) _Person_avatarUrl(ctx context.Context, field graphql.CollectedField, obj *bug.Person) graphql.Marshaler {
- ctx = ec.Tracer.StartFieldExecution(ctx, field)
- defer func() { ec.Tracer.EndFieldExecution(ctx) }()
- rctx := &graphql.ResolverContext{
- Object: "Person",
- Args: nil,
- Field: field,
- }
- ctx = graphql.WithResolverContext(ctx, rctx)
- ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
- resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
- ctx = rctx // use context from middleware stack in children
- return ec.resolvers.Person().AvatarURL(rctx, obj)
- })
- if resTmp == nil {
- return graphql.Null
- }
- res := resTmp.(*string)
- rctx.Result = res
- ctx = ec.Tracer.StartFieldChildExecution(ctx)
-
- if res == nil {
- return graphql.Null
- }
- return graphql.MarshalString(*res)
-}
-
var queryImplementors = []string{"Query"}
// nolint: gocyclo, errcheck, gas, goconst
@@ -6400,11 +6423,18 @@ func (ec *executionContext) _SetStatusOperation_author(ctx context.Context, fiel
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -6563,11 +6593,18 @@ func (ec *executionContext) _SetStatusTimelineItem_author(ctx context.Context, f
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -6727,11 +6764,18 @@ func (ec *executionContext) _SetTitleOperation_author(ctx context.Context, field
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -6918,11 +6962,18 @@ func (ec *executionContext) _SetTitleTimelineItem_author(ctx context.Context, fi
}
return graphql.Null
}
- res := resTmp.(bug.Person)
+ res := resTmp.(*identity.Identity)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
- return ec._Person(ctx, field.Selections, &res)
+ if res == nil {
+ if !ec.HasError(rctx) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+
+ return ec._Identity(ctx, field.Selections, res)
}
// nolint: vetshadow
@@ -8862,24 +8913,10 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er
}
var parsedSchema = gqlparser.MustLoadSchema(
- &ast.Source{Name: "bug.graphql", Input: `"""Represents an person"""
-type Person {
- """The name of the person, if known."""
- name: String
- """The email of the person, if known."""
- email: String
- """The login of the person, if known."""
- login: String
- """A string containing the either the name of the person, its login or both"""
- displayName: String!
- """An url to an avatar"""
- avatarUrl: String
-}
-
-"""Represents a comment on a bug."""
+ &ast.Source{Name: "schema/bug.graphql", Input: `"""Represents a comment on a bug."""
type Comment implements Authored {
"""The author of this comment."""
- author: Person!
+ author: Identity!
"""The message of this comment."""
message: String!
@@ -8911,7 +8948,7 @@ type Bug {
status: Status!
title: String!
labels: [Label!]!
- author: Person!
+ author: Identity!
createdAt: Time!
lastEdit: Time!
@@ -8985,12 +9022,25 @@ type Repository {
}
`},
- &ast.Source{Name: "operations.graphql", Input: `"""An operation applied to a bug."""
+ &ast.Source{Name: "schema/identity.graphql", Input: `"""Represents an identity"""
+type Identity {
+ """The name of the person, if known."""
+ name: String
+ """The email of the person, if known."""
+ email: String
+ """The login of the person, if known."""
+ login: String
+ """A string containing the either the name of the person, its login or both"""
+ displayName: String!
+ """An url to an avatar"""
+ avatarUrl: String
+}`},
+ &ast.Source{Name: "schema/operations.graphql", Input: `"""An operation applied to a bug."""
interface Operation {
"""The hash of the operation"""
hash: Hash!
"""The operations author."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
}
@@ -9017,7 +9067,7 @@ type CreateOperation implements Operation & Authored {
"""The hash of the operation"""
hash: Hash!
"""The author of this object."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
@@ -9030,7 +9080,7 @@ type SetTitleOperation implements Operation & Authored {
"""The hash of the operation"""
hash: Hash!
"""The author of this object."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
@@ -9042,7 +9092,7 @@ type AddCommentOperation implements Operation & Authored {
"""The hash of the operation"""
hash: Hash!
"""The author of this object."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
@@ -9054,7 +9104,7 @@ type EditCommentOperation implements Operation & Authored {
"""The hash of the operation"""
hash: Hash!
"""The author of this object."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
@@ -9067,7 +9117,7 @@ type SetStatusOperation implements Operation & Authored {
"""The hash of the operation"""
hash: Hash!
"""The author of this object."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
@@ -9078,14 +9128,14 @@ type LabelChangeOperation implements Operation & Authored {
"""The hash of the operation"""
hash: Hash!
"""The author of this object."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
added: [Label!]!
removed: [Label!]!
}`},
- &ast.Source{Name: "root.graphql", Input: `scalar Time
+ &ast.Source{Name: "schema/root.graphql", Input: `scalar Time
scalar Label
scalar Hash
@@ -9104,7 +9154,7 @@ type PageInfo {
"""An object that has an author."""
interface Authored {
"""The author of this object."""
- author: Person!
+ author: Identity!
}
type Query {
@@ -9123,7 +9173,7 @@ type Mutation {
commit(repoRef: String, prefix: String!): Bug!
}`},
- &ast.Source{Name: "timeline.graphql", Input: `"""An item in the timeline of events"""
+ &ast.Source{Name: "schema/timeline.graphql", Input: `"""An item in the timeline of events"""
interface TimelineItem {
"""The hash of the source operation"""
hash: Hash!
@@ -9157,7 +9207,7 @@ type TimelineItemEdge {
type CreateTimelineItem implements TimelineItem {
"""The hash of the source operation"""
hash: Hash!
- author: Person!
+ author: Identity!
message: String!
messageIsEmpty: Boolean!
files: [Hash!]!
@@ -9171,7 +9221,7 @@ type CreateTimelineItem implements TimelineItem {
type AddCommentTimelineItem implements TimelineItem {
"""The hash of the source operation"""
hash: Hash!
- author: Person!
+ author: Identity!
message: String!
messageIsEmpty: Boolean!
files: [Hash!]!
@@ -9185,7 +9235,7 @@ type AddCommentTimelineItem implements TimelineItem {
type LabelChangeTimelineItem implements TimelineItem {
"""The hash of the source operation"""
hash: Hash!
- author: Person!
+ author: Identity!
date: Time!
added: [Label!]!
removed: [Label!]!
@@ -9195,7 +9245,7 @@ type LabelChangeTimelineItem implements TimelineItem {
type SetStatusTimelineItem implements TimelineItem {
"""The hash of the source operation"""
hash: Hash!
- author: Person!
+ author: Identity!
date: Time!
status: Status!
}
@@ -9204,7 +9254,7 @@ type SetStatusTimelineItem implements TimelineItem {
type SetTitleTimelineItem implements TimelineItem {
"""The hash of the source operation"""
hash: Hash!
- author: Person!
+ author: Identity!
date: Time!
title: String!
was: String!
diff --git a/tests/graphql_test.go b/graphql/graphql_test.go
index 77008628..90381987 100644
--- a/tests/graphql_test.go
+++ b/graphql/graphql_test.go
@@ -1,18 +1,18 @@
-package tests
+package graphql
import (
"net/http/httptest"
"testing"
- "github.com/MichaelMure/git-bug/graphql"
"github.com/MichaelMure/git-bug/graphql/models"
+ "github.com/MichaelMure/git-bug/util/test"
"github.com/vektah/gqlgen/client"
)
func TestQueries(t *testing.T) {
- repo := createFilledRepo(10)
+ repo := test.CreateFilledRepo(10)
- handler, err := graphql.NewHandler(repo)
+ handler, err := NewHandler(repo)
if err != nil {
t.Fatal(err)
}
diff --git a/graphql/resolvers/person.go b/graphql/resolvers/person.go
deleted file mode 100644
index bb4bcb0d..00000000
--- a/graphql/resolvers/person.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package resolvers
-
-import (
- "context"
-
- "github.com/MichaelMure/git-bug/bug"
-)
-
-type personResolver struct{}
-
-func (personResolver) Name(ctx context.Context, obj *bug.Person) (*string, error) {
- if obj.Name == "" {
- return nil, nil
- }
- return &obj.Name, nil
-}
-
-func (personResolver) Email(ctx context.Context, obj *bug.Person) (*string, error) {
- if obj.Email == "" {
- return nil, nil
- }
- return &obj.Email, nil
-}
-
-func (personResolver) Login(ctx context.Context, obj *bug.Person) (*string, error) {
- if obj.Login == "" {
- return nil, nil
- }
- return &obj.Login, nil
-}
-
-func (personResolver) AvatarURL(ctx context.Context, obj *bug.Person) (*string, error) {
- if obj.AvatarUrl == "" {
- return nil, nil
- }
- return &obj.AvatarUrl, nil
-}
diff --git a/graphql/resolvers/root.go b/graphql/resolvers/root.go
index d7bd6021..9b3a730b 100644
--- a/graphql/resolvers/root.go
+++ b/graphql/resolvers/root.go
@@ -32,10 +32,6 @@ func (RootResolver) Bug() graph.BugResolver {
return &bugResolver{}
}
-func (r RootResolver) Person() graph.PersonResolver {
- return &personResolver{}
-}
-
func (RootResolver) CommentHistoryStep() graph.CommentHistoryStepResolver {
return &commentHistoryStepResolver{}
}
diff --git a/graphql/bug.graphql b/graphql/schema/bug.graphql
index 27bbba99..9530c576 100644
--- a/graphql/bug.graphql
+++ b/graphql/schema/bug.graphql
@@ -1,21 +1,7 @@
-"""Represents an person"""
-type Person {
- """The name of the person, if known."""
- name: String
- """The email of the person, if known."""
- email: String
- """The login of the person, if known."""
- login: String
- """A string containing the either the name of the person, its login or both"""
- displayName: String!
- """An url to an avatar"""
- avatarUrl: String
-}
-
"""Represents a comment on a bug."""
type Comment implements Authored {
"""The author of this comment."""
- author: Person!
+ author: Identity!
"""The message of this comment."""
message: String!
@@ -47,7 +33,7 @@ type Bug {
status: Status!
title: String!
labels: [Label!]!
- author: Person!
+ author: Identity!
createdAt: Time!
lastEdit: Time!
diff --git a/graphql/schema/identity.graphql b/graphql/schema/identity.graphql
new file mode 100644
index 00000000..9e76d885
--- /dev/null
+++ b/graphql/schema/identity.graphql
@@ -0,0 +1,13 @@
+"""Represents an identity"""
+type Identity {
+ """The name of the person, if known."""
+ name: String
+ """The email of the person, if known."""
+ email: String
+ """The login of the person, if known."""
+ login: String
+ """A string containing the either the name of the person, its login or both"""
+ displayName: String!
+ """An url to an avatar"""
+ avatarUrl: String
+} \ No newline at end of file
diff --git a/graphql/operations.graphql b/graphql/schema/operations.graphql
index 420a9e12..2b206418 100644
--- a/graphql/operations.graphql
+++ b/graphql/schema/operations.graphql
@@ -3,7 +3,7 @@ interface Operation {
"""The hash of the operation"""
hash: Hash!
"""The operations author."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
}
@@ -30,7 +30,7 @@ type CreateOperation implements Operation & Authored {
"""The hash of the operation"""
hash: Hash!
"""The author of this object."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
@@ -43,7 +43,7 @@ type SetTitleOperation implements Operation & Authored {
"""The hash of the operation"""
hash: Hash!
"""The author of this object."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
@@ -55,7 +55,7 @@ type AddCommentOperation implements Operation & Authored {
"""The hash of the operation"""
hash: Hash!
"""The author of this object."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
@@ -67,7 +67,7 @@ type EditCommentOperation implements Operation & Authored {
"""The hash of the operation"""
hash: Hash!
"""The author of this object."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
@@ -80,7 +80,7 @@ type SetStatusOperation implements Operation & Authored {
"""The hash of the operation"""
hash: Hash!
"""The author of this object."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
@@ -91,7 +91,7 @@ type LabelChangeOperation implements Operation & Authored {
"""The hash of the operation"""
hash: Hash!
"""The author of this object."""
- author: Person!
+ author: Identity!
"""The datetime when this operation was issued."""
date: Time!
diff --git a/graphql/root.graphql b/graphql/schema/root.graphql
index fd8419fa..56558f7c 100644
--- a/graphql/root.graphql
+++ b/graphql/schema/root.graphql
@@ -17,7 +17,7 @@ type PageInfo {
"""An object that has an author."""
interface Authored {
"""The author of this object."""
- author: Person!
+ author: Identity!
}
type Query {
diff --git a/graphql/timeline.graphql b/graphql/schema/timeline.graphql
index 75f72305..29ed6e60 100644
--- a/graphql/timeline.graphql
+++ b/graphql/schema/timeline.graphql
@@ -32,7 +32,7 @@ type TimelineItemEdge {
type CreateTimelineItem implements TimelineItem {
"""The hash of the source operation"""
hash: Hash!
- author: Person!
+ author: Identity!
message: String!
messageIsEmpty: Boolean!
files: [Hash!]!
@@ -46,7 +46,7 @@ type CreateTimelineItem implements TimelineItem {
type AddCommentTimelineItem implements TimelineItem {
"""The hash of the source operation"""
hash: Hash!
- author: Person!
+ author: Identity!
message: String!
messageIsEmpty: Boolean!
files: [Hash!]!
@@ -60,7 +60,7 @@ type AddCommentTimelineItem implements TimelineItem {
type LabelChangeTimelineItem implements TimelineItem {
"""The hash of the source operation"""
hash: Hash!
- author: Person!
+ author: Identity!
date: Time!
added: [Label!]!
removed: [Label!]!
@@ -70,7 +70,7 @@ type LabelChangeTimelineItem implements TimelineItem {
type SetStatusTimelineItem implements TimelineItem {
"""The hash of the source operation"""
hash: Hash!
- author: Person!
+ author: Identity!
date: Time!
status: Status!
}
@@ -79,7 +79,7 @@ type SetStatusTimelineItem implements TimelineItem {
type SetTitleTimelineItem implements TimelineItem {
"""The hash of the source operation"""
hash: Hash!
- author: Person!
+ author: Identity!
date: Time!
title: String!
was: String!
diff --git a/identity/bare.go b/identity/bare.go
new file mode 100644
index 00000000..eec00e19
--- /dev/null
+++ b/identity/bare.go
@@ -0,0 +1,144 @@
+package identity
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "github.com/MichaelMure/git-bug/util/lamport"
+ "github.com/MichaelMure/git-bug/util/text"
+)
+
+type Bare struct {
+ name string
+ email string
+ login string
+ avatarUrl string
+}
+
+func NewBare(name string, email string) *Bare {
+ return &Bare{name: name, email: email}
+}
+
+func NewBareFull(name string, email string, login string, avatarUrl string) *Bare {
+ return &Bare{name: name, email: email, login: login, avatarUrl: avatarUrl}
+}
+
+type bareIdentityJson struct {
+ Name string `json:"name,omitempty"`
+ Email string `json:"email,omitempty"`
+ Login string `json:"login,omitempty"`
+ AvatarUrl string `json:"avatar_url,omitempty"`
+}
+
+func (i Bare) MarshalJSON() ([]byte, error) {
+ return json.Marshal(bareIdentityJson{
+ Name: i.name,
+ Email: i.email,
+ Login: i.login,
+ AvatarUrl: i.avatarUrl,
+ })
+}
+
+func (i Bare) UnmarshalJSON(data []byte) error {
+ aux := bareIdentityJson{}
+
+ if err := json.Unmarshal(data, &aux); err != nil {
+ return err
+ }
+
+ i.name = aux.Name
+ i.email = aux.Email
+ i.login = aux.Login
+ i.avatarUrl = aux.AvatarUrl
+
+ return nil
+}
+
+func (i Bare) Name() string {
+ return i.name
+}
+
+func (i Bare) Email() string {
+ return i.email
+}
+
+func (i Bare) Login() string {
+ return i.login
+}
+
+func (i Bare) AvatarUrl() string {
+ return i.avatarUrl
+}
+
+func (i Bare) Keys() []Key {
+ return []Key{}
+}
+
+func (i Bare) ValidKeysAtTime(time lamport.Time) []Key {
+ return []Key{}
+}
+
+// DisplayName return a non-empty string to display, representing the
+// identity, based on the non-empty values.
+func (i Bare) 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")
+}
+
+// Match tell is the Person match the given query string
+func (i Bare) Match(query string) bool {
+ query = strings.ToLower(query)
+
+ return strings.Contains(strings.ToLower(i.name), query) ||
+ strings.Contains(strings.ToLower(i.login), query)
+}
+
+// Validate check if the Identity data is valid
+func (i Bare) Validate() error {
+ if text.Empty(i.name) && text.Empty(i.login) {
+ return fmt.Errorf("either name or login should be set")
+ }
+
+ if strings.Contains(i.name, "\n") {
+ return fmt.Errorf("name should be a single line")
+ }
+
+ if !text.Safe(i.name) {
+ return fmt.Errorf("name is not fully printable")
+ }
+
+ if strings.Contains(i.login, "\n") {
+ return fmt.Errorf("login should be a single line")
+ }
+
+ if !text.Safe(i.login) {
+ return fmt.Errorf("login is not fully printable")
+ }
+
+ if strings.Contains(i.email, "\n") {
+ return fmt.Errorf("email should be a single line")
+ }
+
+ if !text.Safe(i.email) {
+ return fmt.Errorf("email is not fully printable")
+ }
+
+ if i.avatarUrl != "" && !text.ValidUrl(i.avatarUrl) {
+ return fmt.Errorf("avatarUrl is not a valid URL")
+ }
+
+ return nil
+}
+
+func (i Bare) IsProtected() bool {
+ return false
+}
diff --git a/identity/identity.go b/identity/identity.go
new file mode 100644
index 00000000..f65e2a86
--- /dev/null
+++ b/identity/identity.go
@@ -0,0 +1,285 @@
+// Package identity contains the identity data model and low-level related functions
+package identity
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/MichaelMure/git-bug/repository"
+ "github.com/MichaelMure/git-bug/util/git"
+ "github.com/MichaelMure/git-bug/util/lamport"
+)
+
+const identityRefPattern = "refs/identities/"
+const versionEntryName = "version"
+const identityConfigKey = "git-bug.identity"
+
+type Identity struct {
+ id string
+ Versions []Version
+}
+
+func NewIdentity(name string, email string) (*Identity, error) {
+ return &Identity{
+ Versions: []Version{
+ {
+ Name: name,
+ Email: email,
+ Nonce: makeNonce(20),
+ },
+ },
+ }, nil
+}
+
+type identityJson struct {
+ Id string `json:"id"`
+}
+
+// TODO: marshal/unmarshal identity + load/write from OpBase
+
+func Read(repo repository.Repo, id string) (*Identity, error) {
+ // Todo
+ return &Identity{}, nil
+}
+
+// NewFromGitUser will query the repository for user detail and
+// build the corresponding Identity
+/*func NewFromGitUser(repo repository.Repo) (*Identity, error) {
+ name, err := repo.GetUserName()
+ if err != nil {
+ return nil, err
+ }
+ if name == "" {
+ return nil, errors.New("User name is not configured in git yet. Please use `git config --global user.name \"John Doe\"`")
+ }
+
+ email, err := repo.GetUserEmail()
+ if err != nil {
+ return nil, err
+ }
+ if email == "" {
+ return nil, errors.New("User name is not configured in git yet. Please use `git config --global user.email johndoe@example.com`")
+ }
+
+ return NewIdentity(name, email)
+}*/
+
+//
+func BuildFromGit(repo repository.Repo) *Identity {
+ version := Version{}
+
+ name, err := repo.GetUserName()
+ if err == nil {
+ version.Name = name
+ }
+
+ email, err := repo.GetUserEmail()
+ if err == nil {
+ version.Email = email
+ }
+
+ return &Identity{
+ Versions: []Version{
+ version,
+ },
+ }
+}
+
+// SetIdentity store the user identity's id in the git config
+func SetIdentity(repo repository.RepoCommon, identity Identity) error {
+ return repo.StoreConfig(identityConfigKey, identity.Id())
+}
+
+// GetIdentity read the current user identity, set with a git config entry
+func GetIdentity(repo repository.Repo) (*Identity, error) {
+ configs, err := repo.ReadConfigs(identityConfigKey)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(configs) == 0 {
+ return nil, fmt.Errorf("no identity set")
+ }
+
+ if len(configs) > 1 {
+ return nil, fmt.Errorf("multiple identity config exist")
+ }
+
+ var id string
+ for _, val := range configs {
+ id = val
+ }
+
+ return Read(repo, id)
+}
+
+func (i *Identity) AddVersion(version Version) {
+ i.Versions = append(i.Versions, version)
+}
+
+func (i *Identity) Commit(repo repository.ClockedRepo) error {
+ // Todo: check for mismatch between memory and commited data
+
+ var lastCommit git.Hash = ""
+
+ for _, v := range i.Versions {
+ if v.commitHash != "" {
+ lastCommit = v.commitHash
+ // ignore already commited versions
+ continue
+ }
+
+ blobHash, err := v.Write(repo)
+ if err != nil {
+ return err
+ }
+
+ // Make a git tree referencing the blob
+ tree := []repository.TreeEntry{
+ {ObjectType: repository.Blob, Hash: blobHash, Name: versionEntryName},
+ }
+
+ treeHash, err := repo.StoreTree(tree)
+ if err != nil {
+ return err
+ }
+
+ var commitHash git.Hash
+ if lastCommit != "" {
+ commitHash, err = repo.StoreCommitWithParent(treeHash, lastCommit)
+ } else {
+ commitHash, err = repo.StoreCommit(treeHash)
+ }
+
+ if err != nil {
+ return err
+ }
+
+ lastCommit = commitHash
+
+ // if it was the first commit, use the commit hash as the Identity id
+ if i.id == "" {
+ i.id = string(commitHash)
+ }
+ }
+
+ if i.id == "" {
+ panic("identity with no id")
+ }
+
+ ref := fmt.Sprintf("%s%s", identityRefPattern, i.id)
+ err := repo.UpdateRef(ref, lastCommit)
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Validate check if the Identity data is valid
+func (i *Identity) Validate() error {
+ lastTime := lamport.Time(0)
+
+ for _, v := range i.Versions {
+ if err := v.Validate(); err != nil {
+ return err
+ }
+
+ if v.Time < lastTime {
+ return fmt.Errorf("non-chronological version (%d --> %d)", lastTime, v.Time)
+ }
+
+ lastTime = v.Time
+ }
+
+ return nil
+}
+
+func (i *Identity) LastVersion() Version {
+ if len(i.Versions) <= 0 {
+ panic("no version at all")
+ }
+
+ return i.Versions[len(i.Versions)-1]
+}
+
+// Id return the Identity identifier
+func (i *Identity) Id() string {
+ if i.id == "" {
+ // simply panic as it would be a coding error
+ // (using an id of an identity not stored yet)
+ panic("no id yet")
+ }
+ return i.id
+}
+
+// Name return the last version of the name
+func (i *Identity) Name() string {
+ return i.LastVersion().Name
+}
+
+// Email return the last version of the email
+func (i *Identity) Email() string {
+ return i.LastVersion().Email
+}
+
+// Login return the last version of the login
+func (i *Identity) Login() string {
+ return i.LastVersion().Login
+}
+
+// Login return the last version of the Avatar URL
+func (i *Identity) AvatarUrl() string {
+ return i.LastVersion().AvatarUrl
+}
+
+// Login return the last version of the valid keys
+func (i *Identity) Keys() []Key {
+ return i.LastVersion().Keys
+}
+
+// IsProtected return true if the chain of git commits started to be signed.
+// If that's the case, only signed commit with a valid key for this identity can be added.
+func (i *Identity) IsProtected() bool {
+ // Todo
+ return false
+}
+
+// ValidKeysAtTime return the set of keys valid at a given lamport time
+func (i *Identity) ValidKeysAtTime(time lamport.Time) []Key {
+ var result []Key
+
+ for _, v := range i.Versions {
+ if v.Time > time {
+ return result
+ }
+
+ result = v.Keys
+ }
+
+ return result
+}
+
+// Match tell is the Identity match the given query string
+func (i *Identity) Match(query string) bool {
+ query = strings.ToLower(query)
+
+ return strings.Contains(strings.ToLower(i.Name()), query) ||
+ strings.Contains(strings.ToLower(i.Login()), query)
+}
+
+// DisplayName return a non-empty string to display, representing the
+// identity, based on the non-empty values.
+func (i *Identity) DisplayName() string {
+ switch {
+ case i.Name() == "" && i.Login() != "":
+ return i.Login()
+ case i.Name() != "" && i.Login() == "":
+ return i.Name()
+ case i.Name() != "" && i.Login() != "":
+ return fmt.Sprintf("%s (%s)", i.Name(), i.Login())
+ }
+
+ panic("invalid person data")
+}
diff --git a/identity/identity_test.go b/identity/identity_test.go
new file mode 100644
index 00000000..161fd56f
--- /dev/null
+++ b/identity/identity_test.go
@@ -0,0 +1,145 @@
+package identity
+
+import (
+ "testing"
+
+ "github.com/MichaelMure/git-bug/repository"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestIdentityCommit(t *testing.T) {
+ mockRepo := repository.NewMockRepoForTest()
+
+ // single version
+
+ identity := Identity{
+ Versions: []Version{
+ {
+ Name: "René Descartes",
+ Email: "rene.descartes@example.com",
+ },
+ },
+ }
+
+ err := identity.Commit(mockRepo)
+
+ assert.Nil(t, err)
+ assert.NotEmpty(t, identity.id)
+
+ // multiple version
+
+ identity = Identity{
+ Versions: []Version{
+ {
+ Time: 100,
+ Name: "René Descartes",
+ Email: "rene.descartes@example.com",
+ Keys: []Key{
+ {PubKey: "pubkeyA"},
+ },
+ },
+ {
+ Time: 200,
+ Name: "René Descartes",
+ Email: "rene.descartes@example.com",
+ Keys: []Key{
+ {PubKey: "pubkeyB"},
+ },
+ },
+ {
+ Time: 201,
+ Name: "René Descartes",
+ Email: "rene.descartes@example.com",
+ Keys: []Key{
+ {PubKey: "pubkeyC"},
+ },
+ },
+ },
+ }
+
+ err = identity.Commit(mockRepo)
+
+ assert.Nil(t, err)
+ assert.NotEmpty(t, identity.id)
+
+ // add more version
+
+ identity.AddVersion(Version{
+ Time: 201,
+ Name: "René Descartes",
+ Email: "rene.descartes@example.com",
+ Keys: []Key{
+ {PubKey: "pubkeyD"},
+ },
+ })
+
+ identity.AddVersion(Version{
+ Time: 300,
+ Name: "René Descartes",
+ Email: "rene.descartes@example.com",
+ Keys: []Key{
+ {PubKey: "pubkeyE"},
+ },
+ })
+
+ err = identity.Commit(mockRepo)
+
+ assert.Nil(t, err)
+ assert.NotEmpty(t, identity.id)
+}
+
+func TestIdentity_ValidKeysAtTime(t *testing.T) {
+ identity := Identity{
+ Versions: []Version{
+ {
+ Time: 100,
+ Name: "René Descartes",
+ Email: "rene.descartes@example.com",
+ Keys: []Key{
+ {PubKey: "pubkeyA"},
+ },
+ },
+ {
+ Time: 200,
+ Name: "René Descartes",
+ Email: "rene.descartes@example.com",
+ Keys: []Key{
+ {PubKey: "pubkeyB"},
+ },
+ },
+ {
+ Time: 201,
+ Name: "René Descartes",
+ Email: "rene.descartes@example.com",
+ Keys: []Key{
+ {PubKey: "pubkeyC"},
+ },
+ },
+ {
+ Time: 201,
+ Name: "René Descartes",
+ Email: "rene.descartes@example.com",
+ Keys: []Key{
+ {PubKey: "pubkeyD"},
+ },
+ },
+ {
+ Time: 300,
+ Name: "René Descartes",
+ Email: "rene.descartes@example.com",
+ Keys: []Key{
+ {PubKey: "pubkeyE"},
+ },
+ },
+ },
+ }
+
+ assert.Nil(t, identity.ValidKeysAtTime(10))
+ assert.Equal(t, identity.ValidKeysAtTime(100), []Key{{PubKey: "pubkeyA"}})
+ assert.Equal(t, identity.ValidKeysAtTime(140), []Key{{PubKey: "pubkeyA"}})
+ assert.Equal(t, identity.ValidKeysAtTime(200), []Key{{PubKey: "pubkeyB"}})
+ assert.Equal(t, identity.ValidKeysAtTime(201), []Key{{PubKey: "pubkeyD"}})
+ assert.Equal(t, identity.ValidKeysAtTime(202), []Key{{PubKey: "pubkeyD"}})
+ assert.Equal(t, identity.ValidKeysAtTime(300), []Key{{PubKey: "pubkeyE"}})
+ assert.Equal(t, identity.ValidKeysAtTime(3000), []Key{{PubKey: "pubkeyE"}})
+}
diff --git a/identity/interface.go b/identity/interface.go
new file mode 100644
index 00000000..14287655
--- /dev/null
+++ b/identity/interface.go
@@ -0,0 +1,30 @@
+package identity
+
+import "github.com/MichaelMure/git-bug/util/lamport"
+
+type Interface interface {
+ Name() string
+ Email() string
+ Login() string
+ AvatarUrl() string
+
+ // Login return the last version of the valid keys
+ Keys() []Key
+
+ // ValidKeysAtTime return the set of keys valid at a given lamport time
+ ValidKeysAtTime(time lamport.Time) []Key
+
+ // DisplayName return a non-empty string to display, representing the
+ // identity, based on the non-empty values.
+ DisplayName() string
+
+ // Match tell is the Person match the given query string
+ Match(query string) bool
+
+ // Validate check if the Identity data is valid
+ Validate() error
+
+ // 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
+}
diff --git a/identity/key.go b/identity/key.go
new file mode 100644
index 00000000..c498ec09
--- /dev/null
+++ b/identity/key.go
@@ -0,0 +1,7 @@
+package identity
+
+type Key struct {
+ // The GPG fingerprint of the key
+ Fingerprint string `json:"fingerprint"`
+ PubKey string `json:"pub_key"`
+}
diff --git a/identity/version.go b/identity/version.go
new file mode 100644
index 00000000..f76ec4c5
--- /dev/null
+++ b/identity/version.go
@@ -0,0 +1,105 @@
+package identity
+
+import (
+ "crypto/rand"
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "github.com/MichaelMure/git-bug/repository"
+ "github.com/MichaelMure/git-bug/util/git"
+
+ "github.com/MichaelMure/git-bug/util/lamport"
+ "github.com/MichaelMure/git-bug/util/text"
+)
+
+type Version struct {
+ // Private field so not serialized
+ commitHash git.Hash
+
+ // The lamport time at which this version become effective
+ // The reference time is the bug edition lamport clock
+ Time lamport.Time `json:"time"`
+
+ Name string `json:"name"`
+ Email string `json:"email"`
+ Login string `json:"login"`
+ AvatarUrl string `json:"avatar_url"`
+
+ // 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 `json:"pub_keys"`
+
+ // This optional array is here to ensure a better randomness of the identity id to avoid collisions.
+ // It has no functional purpose and should be ignored.
+ // It is advised to fill this array if there is not enough entropy, e.g. if there is no keys.
+ Nonce []byte `json:"nonce,omitempty"`
+}
+
+func (v *Version) Validate() error {
+ if text.Empty(v.Name) && text.Empty(v.Login) {
+ return fmt.Errorf("either name or login should be set")
+ }
+
+ if strings.Contains(v.Name, "\n") {
+ return fmt.Errorf("name should be a single line")
+ }
+
+ if !text.Safe(v.Name) {
+ return fmt.Errorf("name is not fully printable")
+ }
+
+ if strings.Contains(v.Login, "\n") {
+ return fmt.Errorf("login should be a single line")
+ }
+
+ if !text.Safe(v.Login) {
+ return fmt.Errorf("login is not fully printable")
+ }
+
+ if strings.Contains(v.Email, "\n") {
+ return fmt.Errorf("email should be a single line")
+ }
+
+ if !text.Safe(v.Email) {
+ return fmt.Errorf("email is not fully printable")
+ }
+
+ 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")
+ }
+
+ return nil
+}
+
+// Write will serialize and store the Version as a git blob and return
+// its hash
+func (v *Version) Write(repo repository.Repo) (git.Hash, error) {
+ data, err := json.Marshal(v)
+
+ if err != nil {
+ return "", err
+ }
+
+ hash, err := repo.StoreData(data)
+
+ if err != nil {
+ return "", err
+ }
+
+ return hash, nil
+}
+
+func makeNonce(len int) []byte {
+ result := make([]byte, len)
+ _, err := rand.Read(result)
+ if err != nil {
+ panic(err)
+ }
+ return result
+}
diff --git a/misc/random_bugs/create_random_bugs.go b/misc/random_bugs/create_random_bugs.go
index 8faee200..f30a9d8a 100644
--- a/misc/random_bugs/create_random_bugs.go
+++ b/misc/random_bugs/create_random_bugs.go
@@ -6,11 +6,12 @@ import (
"time"
"github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
"github.com/icrowley/fake"
)
-type opsGenerator func(bug.Interface, bug.Person)
+type opsGenerator func(bug.Interface, identity.Interface)
type Options struct {
BugNumber int
@@ -136,18 +137,15 @@ func GenerateRandomOperationPacksWithSeed(packNumber int, opNumber int, seed int
return result
}
-func person() bug.Person {
- return bug.Person{
- Name: fake.FullName(),
- Email: fake.EmailAddress(),
- }
+func person() identity.Interface {
+ return identity.NewBare(fake.FullName(), fake.EmailAddress())
}
-var persons []bug.Person
+var persons []identity.Interface
-func randomPerson(personNumber int) bug.Person {
+func randomPerson(personNumber int) identity.Interface {
if len(persons) == 0 {
- persons = make([]bug.Person, personNumber)
+ persons = make([]identity.Interface, personNumber)
for i := range persons {
persons[i] = person()
}
@@ -162,25 +160,25 @@ func paragraphs() string {
return strings.Replace(p, "\t", "\n\n", -1)
}
-func comment(b bug.Interface, p bug.Person) {
+func comment(b bug.Interface, p identity.Interface) {
_, _ = bug.AddComment(b, p, time.Now().Unix(), paragraphs())
}
-func title(b bug.Interface, p bug.Person) {
+func title(b bug.Interface, p identity.Interface) {
_, _ = bug.SetTitle(b, p, time.Now().Unix(), fake.Sentence())
}
-func open(b bug.Interface, p bug.Person) {
+func open(b bug.Interface, p identity.Interface) {
_, _ = bug.Open(b, p, time.Now().Unix())
}
-func close(b bug.Interface, p bug.Person) {
+func close(b bug.Interface, p identity.Interface) {
_, _ = bug.Close(b, p, time.Now().Unix())
}
var addedLabels []string
-func labels(b bug.Interface, p bug.Person) {
+func labels(b bug.Interface, p identity.Interface) {
var removed []string
nbRemoved := rand.Intn(3)
for nbRemoved > 0 && len(addedLabels) > 0 {
diff --git a/termui/bug_table.go b/termui/bug_table.go
index 13d86aa7..ba946c86 100644
--- a/termui/bug_table.go
+++ b/termui/bug_table.go
@@ -6,6 +6,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/colors"
"github.com/MichaelMure/git-bug/util/text"
"github.com/MichaelMure/gocui"
@@ -289,7 +290,7 @@ func (bt *bugTable) render(v *gocui.View, maxX int) {
columnWidths := bt.getColumnWidths(maxX)
for _, b := range bt.bugs {
- person := bug.Person{}
+ person := &identity.Identity{}
snap := b.Snapshot()
if len(snap.Comments) > 0 {
create := snap.Comments[0]
diff --git a/tests/read_bugs_test.go b/tests/read_bugs_test.go
index e5de0cc2..80d6cc1f 100644
--- a/tests/read_bugs_test.go
+++ b/tests/read_bugs_test.go
@@ -1,60 +1,14 @@
package tests
import (
- "io/ioutil"
- "log"
"testing"
"github.com/MichaelMure/git-bug/bug"
- "github.com/MichaelMure/git-bug/misc/random_bugs"
- "github.com/MichaelMure/git-bug/repository"
+ "github.com/MichaelMure/git-bug/util/test"
)
-func createRepo(bare bool) *repository.GitRepo {
- dir, err := ioutil.TempDir("", "")
- if err != nil {
- log.Fatal(err)
- }
-
- // fmt.Println("Creating repo:", dir)
-
- var creator func(string) (*repository.GitRepo, error)
-
- if bare {
- creator = repository.InitBareGitRepo
- } else {
- creator = repository.InitGitRepo
- }
-
- repo, err := creator(dir)
- if err != nil {
- log.Fatal(err)
- }
-
- if err := repo.StoreConfig("user.name", "testuser"); err != nil {
- log.Fatal("failed to set user.name for test repository: ", err)
- }
- if err := repo.StoreConfig("user.email", "testuser@example.com"); err != nil {
- log.Fatal("failed to set user.email for test repository: ", err)
- }
-
- return repo
-}
-
-func createFilledRepo(bugNumber int) repository.ClockedRepo {
- repo := createRepo(false)
-
- var seed int64 = 42
- options := random_bugs.DefaultOptions()
-
- options.BugNumber = bugNumber
-
- random_bugs.CommitRandomBugsWithSeed(repo, options, seed)
- return repo
-}
-
func TestReadBugs(t *testing.T) {
- repo := createFilledRepo(15)
+ repo := test.CreateFilledRepo(15)
bugs := bug.ReadAllLocalBugs(repo)
for b := range bugs {
if b.Err != nil {
@@ -64,7 +18,7 @@ func TestReadBugs(t *testing.T) {
}
func benchmarkReadBugs(bugNumber int, t *testing.B) {
- repo := createFilledRepo(bugNumber)
+ repo := test.CreateFilledRepo(bugNumber)
t.ResetTimer()
for n := 0; n < t.N; n++ {
diff --git a/util/test/repo.go b/util/test/repo.go
new file mode 100644
index 00000000..8f0d2e5d
--- /dev/null
+++ b/util/test/repo.go
@@ -0,0 +1,52 @@
+package test
+
+import (
+ "io/ioutil"
+ "log"
+
+ "github.com/MichaelMure/git-bug/misc/random_bugs"
+ "github.com/MichaelMure/git-bug/repository"
+)
+
+func CreateRepo(bare bool) *repository.GitRepo {
+ dir, err := ioutil.TempDir("", "")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // fmt.Println("Creating repo:", dir)
+
+ var creator func(string) (*repository.GitRepo, error)
+
+ if bare {
+ creator = repository.InitBareGitRepo
+ } else {
+ creator = repository.InitGitRepo
+ }
+
+ repo, err := creator(dir)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if err := repo.StoreConfig("user.name", "testuser"); err != nil {
+ log.Fatal("failed to set user.name for test repository: ", err)
+ }
+ if err := repo.StoreConfig("user.email", "testuser@example.com"); err != nil {
+ log.Fatal("failed to set user.email for test repository: ", err)
+ }
+
+ return repo
+}
+
+func CreateFilledRepo(bugNumber int) repository.ClockedRepo {
+ repo := CreateRepo(false)
+
+ var seed int64 = 42
+ options := random_bugs.DefaultOptions()
+
+ options.BugNumber = bugNumber
+
+ random_bugs.CommitRandomBugsWithSeed(repo, options, seed)
+ return repo
+}