bug/bug.go 🔗
@@ -677,6 +677,3 @@ func (bug *Bug) Compile() Snapshot {
 
 	return snap
 }
-
-// Sign post method for gqlgen
-func (bug *Bug) IsAuthored() {}
  Michael Muré created
bug/bug.go                          |    3 
bug/op_add_comment.go               |    3 
bug/op_create.go                    |    3 
bug/op_edit_comment.go              |    3 
bug/op_label_change.go              |    3 
bug/op_noop.go                      |    3 
bug/op_set_metadata.go              |    3 
bug/op_set_status.go                |    3 
bug/op_set_title.go                 |    3 
bug/operation.go                    |    3 
cache/repo_cache.go                 |  108 +
go.mod                              |    2 
go.sum                              |   11 
graphql/connections/connections.go  |    2 
graphql/connections/edges.go        |   11 
graphql/connections/gen_identity.go |   11 
graphql/connections/lazy_bug.go     |   14 
graphql/gqlgen.yml                  |   15 
graphql/graph/gen_graph.go          | 1471 +++++++++++++-----------------
graphql/models/gen_models.go        |   35 
graphql/models/lazy_bug.go          |  214 ++++
graphql/models/lazy_identity.go     |  164 +++
graphql/resolvers/bug.go            |   66 
graphql/resolvers/color.go          |    6 
graphql/resolvers/comment.go        |   17 
graphql/resolvers/identity.go       |    6 
graphql/resolvers/mutation.go       |   32 
graphql/resolvers/operations.go     |   52 
graphql/resolvers/query.go          |    4 
graphql/resolvers/repo.go           |   50 
graphql/resolvers/root.go           |    4 
graphql/resolvers/timeline.go       |   48 
identity/identity.go                |   37 
33 files changed, 1,368 insertions(+), 1,042 deletions(-)
@@ -677,6 +677,3 @@ func (bug *Bug) Compile() Snapshot {
 
 	return snap
 }
-
-// Sign post method for gqlgen
-func (bug *Bug) IsAuthored() {}
  @@ -21,6 +21,9 @@ type AddCommentOperation struct {
 	Files []git.Hash `json:"files"`
 }
 
+// Sign-post method for gqlgen
+func (op *AddCommentOperation) IsOperation() {}
+
 func (op *AddCommentOperation) base() *OpBase {
 	return &op.OpBase
 }
  @@ -22,6 +22,9 @@ type CreateOperation struct {
 	Files   []git.Hash `json:"files"`
 }
 
+// Sign-post method for gqlgen
+func (op *CreateOperation) IsOperation() {}
+
 func (op *CreateOperation) base() *OpBase {
 	return &op.OpBase
 }
  @@ -24,6 +24,9 @@ type EditCommentOperation struct {
 	Files   []git.Hash `json:"files"`
 }
 
+// Sign-post method for gqlgen
+func (op *EditCommentOperation) IsOperation() {}
+
 func (op *EditCommentOperation) base() *OpBase {
 	return &op.OpBase
 }
  @@ -21,6 +21,9 @@ type LabelChangeOperation struct {
 	Removed []Label `json:"removed"`
 }
 
+// Sign-post method for gqlgen
+func (op *LabelChangeOperation) IsOperation() {}
+
 func (op *LabelChangeOperation) base() *OpBase {
 	return &op.OpBase
 }
  @@ -16,6 +16,9 @@ type NoOpOperation struct {
 	OpBase
 }
 
+// Sign-post method for gqlgen
+func (op *NoOpOperation) IsOperation() {}
+
 func (op *NoOpOperation) base() *OpBase {
 	return &op.OpBase
 }
  @@ -17,6 +17,9 @@ type SetMetadataOperation struct {
 	NewMetadata map[string]string `json:"new_metadata"`
 }
 
+// Sign-post method for gqlgen
+func (op *SetMetadataOperation) IsOperation() {}
+
 func (op *SetMetadataOperation) base() *OpBase {
 	return &op.OpBase
 }
  @@ -18,6 +18,9 @@ type SetStatusOperation struct {
 	Status Status `json:"status"`
 }
 
+// Sign-post method for gqlgen
+func (op *SetStatusOperation) IsOperation() {}
+
 func (op *SetStatusOperation) base() *OpBase {
 	return &op.OpBase
 }
  @@ -21,6 +21,9 @@ type SetTitleOperation struct {
 	Was   string `json:"was"`
 }
 
+// Sign-post method for gqlgen
+func (op *SetTitleOperation) IsOperation() {}
+
 func (op *SetTitleOperation) base() *OpBase {
 	return &op.OpBase
 }
  @@ -52,6 +52,9 @@ type Operation interface {
 	AllMetadata() map[string]string
 	// GetAuthor return the author identity
 	GetAuthor() identity.Interface
+
+	// sign-post method for gqlgen
+	IsOperation()
 }
 
 func deriveId(data []byte) entity.Id {
  @@ -378,6 +378,16 @@ func (c *RepoCache) buildCache() error {
 	return nil
 }
 
+// ResolveBugExcerpt retrieve a BugExcerpt matching the exact given id
+func (c *RepoCache) ResolveBugExcerpt(id entity.Id) (*BugExcerpt, error) {
+	e, ok := c.bugExcerpts[id]
+	if !ok {
+		return nil, bug.ErrBugNotExist
+	}
+
+	return e, nil
+}
+
 // ResolveBug retrieve a bug matching the exact given id
 func (c *RepoCache) ResolveBug(id entity.Id) (*BugCache, error) {
 	cached, ok := c.bugs[id]
@@ -396,14 +406,12 @@ func (c *RepoCache) ResolveBug(id entity.Id) (*BugCache, error) {
 	return cached, nil
 }
 
-// ResolveBugExcerpt retrieve a BugExcerpt matching the exact given id
-func (c *RepoCache) ResolveBugExcerpt(id entity.Id) (*BugExcerpt, error) {
-	e, ok := c.bugExcerpts[id]
-	if !ok {
-		return nil, bug.ErrBugNotExist
-	}
-
-	return e, nil
+// ResolveBugExcerptPrefix retrieve a BugExcerpt matching an id prefix. It fails if multiple
+// bugs match.
+func (c *RepoCache) ResolveBugExcerptPrefix(prefix string) (*BugExcerpt, error) {
+	return c.ResolveBugExcerptMatcher(func(excerpt *BugExcerpt) bool {
+		return excerpt.Id.HasPrefix(prefix)
+	})
 }
 
 // ResolveBugPrefix retrieve a bug matching an id prefix. It fails if multiple
@@ -423,7 +431,23 @@ func (c *RepoCache) ResolveBugCreateMetadata(key string, value string) (*BugCach
 	})
 }
 
+func (c *RepoCache) ResolveBugExcerptMatcher(f func(*BugExcerpt) bool) (*BugExcerpt, error) {
+	id, err := c.resolveBugMatcher(f)
+	if err != nil {
+		return nil, err
+	}
+	return c.ResolveBugExcerpt(id)
+}
+
 func (c *RepoCache) ResolveBugMatcher(f func(*BugExcerpt) bool) (*BugCache, error) {
+	id, err := c.resolveBugMatcher(f)
+	if err != nil {
+		return nil, err
+	}
+	return c.ResolveBug(id)
+}
+
+func (c *RepoCache) resolveBugMatcher(f func(*BugExcerpt) bool) (entity.Id, error) {
 	// preallocate but empty
 	matching := make([]entity.Id, 0, 5)
 
@@ -434,14 +458,14 @@ func (c *RepoCache) ResolveBugMatcher(f func(*BugExcerpt) bool) (*BugCache, erro
 	}
 
 	if len(matching) > 1 {
-		return nil, bug.NewErrMultipleMatchBug(matching)
+		return entity.UnsetId, bug.NewErrMultipleMatchBug(matching)
 	}
 
 	if len(matching) == 0 {
-		return nil, bug.ErrBugNotExist
+		return entity.UnsetId, bug.ErrBugNotExist
 	}
 
-	return c.ResolveBug(matching[0])
+	return matching[0], nil
 }
 
 // QueryBugs return the id of all Bug matching the given Query
@@ -745,6 +769,16 @@ func repoIsAvailable(repo repository.Repo) error {
 	return nil
 }
 
+// ResolveIdentityExcerpt retrieve a IdentityExcerpt matching the exact given id
+func (c *RepoCache) ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error) {
+	e, ok := c.identitiesExcerpts[id]
+	if !ok {
+		return nil, identity.ErrIdentityNotExist
+	}
+
+	return e, nil
+}
+
 // ResolveIdentity retrieve an identity matching the exact given id
 func (c *RepoCache) ResolveIdentity(id entity.Id) (*IdentityCache, error) {
 	cached, ok := c.identities[id]
@@ -763,14 +797,12 @@ func (c *RepoCache) ResolveIdentity(id entity.Id) (*IdentityCache, error) {
 	return cached, nil
 }
 
-// ResolveIdentityExcerpt retrieve a IdentityExcerpt matching the exact given id
-func (c *RepoCache) ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error) {
-	e, ok := c.identitiesExcerpts[id]
-	if !ok {
-		return nil, identity.ErrIdentityNotExist
-	}
-
-	return e, nil
+// ResolveIdentityExcerptPrefix retrieve a IdentityExcerpt matching an id prefix.
+// It fails if multiple identities match.
+func (c *RepoCache) ResolveIdentityExcerptPrefix(prefix string) (*IdentityExcerpt, error) {
+	return c.ResolveIdentityExcerptMatcher(func(excerpt *IdentityExcerpt) bool {
+		return excerpt.Id.HasPrefix(prefix)
+	})
 }
 
 // ResolveIdentityPrefix retrieve an Identity matching an id prefix.
@@ -789,7 +821,23 @@ func (c *RepoCache) ResolveIdentityImmutableMetadata(key string, value string) (
 	})
 }
 
+func (c *RepoCache) ResolveIdentityExcerptMatcher(f func(*IdentityExcerpt) bool) (*IdentityExcerpt, error) {
+	id, err := c.resolveIdentityMatcher(f)
+	if err != nil {
+		return nil, err
+	}
+	return c.ResolveIdentityExcerpt(id)
+}
+
 func (c *RepoCache) ResolveIdentityMatcher(f func(*IdentityExcerpt) bool) (*IdentityCache, error) {
+	id, err := c.resolveIdentityMatcher(f)
+	if err != nil {
+		return nil, err
+	}
+	return c.ResolveIdentity(id)
+}
+
+func (c *RepoCache) resolveIdentityMatcher(f func(*IdentityExcerpt) bool) (entity.Id, error) {
 	// preallocate but empty
 	matching := make([]entity.Id, 0, 5)
 
@@ -800,14 +848,14 @@ func (c *RepoCache) ResolveIdentityMatcher(f func(*IdentityExcerpt) bool) (*Iden
 	}
 
 	if len(matching) > 1 {
-		return nil, identity.NewErrMultipleMatch(matching)
+		return entity.UnsetId, identity.NewErrMultipleMatch(matching)
 	}
 
 	if len(matching) == 0 {
-		return nil, identity.ErrIdentityNotExist
+		return entity.UnsetId, identity.ErrIdentityNotExist
 	}
 
-	return c.ResolveIdentity(matching[0])
+	return matching[0], nil
 }
 
 // AllIdentityIds return all known identity ids
@@ -859,6 +907,22 @@ func (c *RepoCache) GetUserIdentity() (*IdentityCache, error) {
 	return cached, nil
 }
 
+func (c *RepoCache) GetUserIdentityExcerpt() (*IdentityExcerpt, error) {
+	if c.userIdentityId == "" {
+		id, err := identity.GetUserIdentityId(c.repo)
+		if err != nil {
+			return nil, err
+		}
+		c.userIdentityId = id
+	}
+
+	excerpt, ok := c.identitiesExcerpts[c.userIdentityId]
+	if !ok {
+		return nil, fmt.Errorf("cache: missing identity excerpt %v", c.userIdentityId)
+	}
+	return excerpt, nil
+}
+
 func (c *RepoCache) IsUserIdentitySet() (bool, error) {
 	return identity.IsUserIdentitySet(c.repo)
 }
  @@ -3,7 +3,7 @@ module github.com/MichaelMure/git-bug
 go 1.11
 
 require (
-	github.com/99designs/gqlgen v0.10.3-0.20200205113530-b941b970f0b6
+	github.com/99designs/gqlgen v0.10.3-0.20200208093655-ab8d62b67dd0
 	github.com/MichaelMure/go-term-text v0.2.6
 	github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195
 	github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986
  @@ -1,11 +1,9 @@
-github.com/99designs/gqlgen v0.10.3-0.20200205113530-b941b970f0b6 h1:WU+9Z7AoCyl+HlB2kE0O+4/3BaVqLQfnX5dHigV5p8A=
-github.com/99designs/gqlgen v0.10.3-0.20200205113530-b941b970f0b6/go.mod h1:28v/ATDVwPUriwNtAIrQEhRHXJjdi5dVGqxSqPna1I8=
+github.com/99designs/gqlgen v0.10.3-0.20200208093655-ab8d62b67dd0 h1:ADy3XJwhOYg6Pb90XeXazWvO+9gpOsgLuaM1buZUZOY=
+github.com/99designs/gqlgen v0.10.3-0.20200208093655-ab8d62b67dd0/go.mod h1:dfBhwZKMcSYiYRMTs8qWF+Oha6782e1xPfgRmVal9I8=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
 github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
-github.com/MichaelMure/go-term-text v0.2.4 h1:h+lAsjG5o3oNvaJeh7OzE8zdSiB/VyJo9JzSnncrZv4=
-github.com/MichaelMure/go-term-text v0.2.4/go.mod h1:o2Z5T3b28F4kwAojGvvNdbzjHf9t18vbQ7E2pmTe2Ww=
 github.com/MichaelMure/go-term-text v0.2.6 h1:dSmJSzk2iI5xWymSMrMbdVM1bxYWu3DjDFhdcJvAuqA=
 github.com/MichaelMure/go-term-text v0.2.6/go.mod h1:o2Z5T3b28F4kwAojGvvNdbzjHf9t18vbQ7E2pmTe2Ww=
 github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
@@ -36,8 +34,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -69,6 +65,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
 github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
@@ -135,6 +132,8 @@ github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqf
 github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
 github.com/xanzy/go-gitlab v0.22.1 h1:TVxgHmoa35jQL+9FCkG0nwPDxU9dQZXknBTDtGaSFno=
 github.com/xanzy/go-gitlab v0.22.1/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=
+github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU=
+github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
 github.com/xanzy/go-gitlab v0.24.0 h1:zP1zC4K76Gha0coN5GhygOLhsHTCvUjrnqGL3kHXkVU=
 github.com/xanzy/go-gitlab v0.24.0/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
  @@ -1,6 +1,6 @@
 //go:generate genny -in=connection_template.go -out=gen_lazy_bug.go gen "Name=LazyBug NodeType=entity.Id EdgeType=LazyBugEdge ConnectionType=models.BugConnection"
 //go:generate genny -in=connection_template.go -out=gen_lazy_identity.go gen "Name=LazyIdentity NodeType=entity.Id EdgeType=LazyIdentityEdge ConnectionType=models.IdentityConnection"
-//go:generate genny -in=connection_template.go -out=gen_identity.go gen "Name=Identity NodeType=identity.Interface EdgeType=models.IdentityEdge ConnectionType=models.IdentityConnection"
+//go:generate genny -in=connection_template.go -out=gen_identity.go gen "Name=Identity NodeType=models.IdentityWrapper EdgeType=models.IdentityEdge ConnectionType=models.IdentityConnection"
 //go:generate genny -in=connection_template.go -out=gen_operation.go gen "Name=Operation NodeType=bug.Operation EdgeType=models.OperationEdge ConnectionType=models.OperationConnection"
 //go:generate genny -in=connection_template.go -out=gen_comment.go gen "Name=Comment NodeType=bug.Comment EdgeType=models.CommentEdge ConnectionType=models.CommentConnection"
 //go:generate genny -in=connection_template.go -out=gen_timeline.go gen "Name=TimelineItem NodeType=bug.TimelineItem EdgeType=models.TimelineItemEdge ConnectionType=models.TimelineItemConnection"
  @@ -2,6 +2,17 @@ package connections
 
 import "github.com/MichaelMure/git-bug/entity"
 
+// LazyBugEdge is a special relay edge used to implement a lazy loading connection
+type LazyBugEdge struct {
+	Id     entity.Id
+	Cursor string
+}
+
+// GetCursor return the cursor of a LazyBugEdge
+func (lbe LazyBugEdge) GetCursor() string {
+	return lbe.Cursor
+}
+
 // LazyIdentityEdge is a special relay edge used to implement a lazy loading connection
 type LazyIdentityEdge struct {
 	Id     entity.Id
  @@ -8,23 +8,22 @@ import (
 	"fmt"
 
 	"github.com/MichaelMure/git-bug/graphql/models"
-	"github.com/MichaelMure/git-bug/identity"
 )
 
-// IdentityInterfaceEdgeMaker define a function that take a identity.Interface and an offset and
+// ModelsIdentityWrapperEdgeMaker define a function that take a models.IdentityWrapper and an offset and
 // create an Edge.
-type IdentityEdgeMaker func(value identity.Interface, offset int) Edge
+type IdentityEdgeMaker func(value models.IdentityWrapper, offset int) Edge
 
 // IdentityConMaker define a function that create a models.IdentityConnection
 type IdentityConMaker func(
 	edges []*models.IdentityEdge,
-	nodes []identity.Interface,
+	nodes []models.IdentityWrapper,
 	info *models.PageInfo,
 	totalCount int) (*models.IdentityConnection, error)
 
 // IdentityCon will paginate a source according to the input of a relay connection
-func IdentityCon(source []identity.Interface, edgeMaker IdentityEdgeMaker, conMaker IdentityConMaker, input models.ConnectionInput) (*models.IdentityConnection, error) {
-	var nodes []identity.Interface
+func IdentityCon(source []models.IdentityWrapper, edgeMaker IdentityEdgeMaker, conMaker IdentityConMaker, input models.ConnectionInput) (*models.IdentityConnection, error) {
+	var nodes []models.IdentityWrapper
 	var edges []*models.IdentityEdge
 	var cursors []string
 	var pageInfo = &models.PageInfo{}
  @@ -1,14 +0,0 @@
-package connections
-
-import "github.com/MichaelMure/git-bug/entity"
-
-// LazyBugEdge is a special relay edge used to implement a lazy loading connection
-type LazyBugEdge struct {
-	Id     entity.Id
-	Cursor string
-}
-
-// GetCursor return the cursor of a LazyBugEdge
-func (lbe LazyBugEdge) GetCursor() string {
-	return lbe.Cursor
-}
  @@ -10,13 +10,24 @@ models:
   RepositoryMutation:
     model: github.com/MichaelMure/git-bug/graphql/models.RepositoryMutation
   Bug:
-    model: github.com/MichaelMure/git-bug/bug.Snapshot
+    model: github.com/MichaelMure/git-bug/graphql/models.BugWrapper
+    fields:
+      actors:
+        resolver: true
+      participants:
+        resolver: true
+      comments:
+        resolver: true
+      timeline:
+        resolver: true
+      operations:
+        resolver: true
   Color:
     model: image/color.RGBA
   Comment:
     model: github.com/MichaelMure/git-bug/bug.Comment
   Identity:
-    model: github.com/MichaelMure/git-bug/identity.Interface
+    model: github.com/MichaelMure/git-bug/graphql/models.IdentityWrapper
   Label:
     model: github.com/MichaelMure/git-bug/bug.Label
   Hash:
  @@ -17,7 +17,6 @@ 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"
@@ -45,6 +44,7 @@ type ResolverRoot interface {
 	AddCommentTimelineItem() AddCommentTimelineItemResolver
 	Bug() BugResolver
 	Color() ColorResolver
+	Comment() CommentResolver
 	CommentHistoryStep() CommentHistoryStepResolver
 	CreateOperation() CreateOperationResolver
 	CreateTimelineItem() CreateTimelineItemResolver
@@ -370,55 +370,59 @@ type ComplexityRoot struct {
 
 type AddCommentOperationResolver interface {
 	ID(ctx context.Context, obj *bug.AddCommentOperation) (string, error)
-
+	Author(ctx context.Context, obj *bug.AddCommentOperation) (models.IdentityWrapper, error)
 	Date(ctx context.Context, obj *bug.AddCommentOperation) (*time.Time, error)
 }
 type AddCommentTimelineItemResolver interface {
 	ID(ctx context.Context, obj *bug.AddCommentTimelineItem) (string, error)
+	Author(ctx context.Context, obj *bug.AddCommentTimelineItem) (models.IdentityWrapper, error)
 
 	CreatedAt(ctx context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error)
 	LastEdit(ctx context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error)
 }
 type BugResolver interface {
-	ID(ctx context.Context, obj *bug.Snapshot) (string, error)
-	HumanID(ctx context.Context, obj *bug.Snapshot) (string, error)
-	Status(ctx context.Context, obj *bug.Snapshot) (models.Status, error)
+	ID(ctx context.Context, obj models.BugWrapper) (string, error)
+	HumanID(ctx context.Context, obj models.BugWrapper) (string, error)
+	Status(ctx context.Context, obj models.BugWrapper) (models.Status, error)
 
-	LastEdit(ctx context.Context, obj *bug.Snapshot) (*time.Time, error)
-	Actors(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error)
-	Participants(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error)
-	Comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.CommentConnection, error)
-	Timeline(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.TimelineItemConnection, error)
-	Operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.OperationConnection, error)
+	Actors(ctx context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error)
+	Participants(ctx context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error)
+	Comments(ctx context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.CommentConnection, error)
+	Timeline(ctx context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.TimelineItemConnection, error)
+	Operations(ctx context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.OperationConnection, error)
 }
 type ColorResolver interface {
 	R(ctx context.Context, obj *color.RGBA) (int, error)
 	G(ctx context.Context, obj *color.RGBA) (int, error)
 	B(ctx context.Context, obj *color.RGBA) (int, error)
 }
+type CommentResolver interface {
+	Author(ctx context.Context, obj *bug.Comment) (models.IdentityWrapper, error)
+}
 type CommentHistoryStepResolver interface {
 	Date(ctx context.Context, obj *bug.CommentHistoryStep) (*time.Time, error)
 }
 type CreateOperationResolver interface {
 	ID(ctx context.Context, obj *bug.CreateOperation) (string, error)
-
+	Author(ctx context.Context, obj *bug.CreateOperation) (models.IdentityWrapper, error)
 	Date(ctx context.Context, obj *bug.CreateOperation) (*time.Time, error)
 }
 type CreateTimelineItemResolver interface {
 	ID(ctx context.Context, obj *bug.CreateTimelineItem) (string, error)
+	Author(ctx context.Context, obj *bug.CreateTimelineItem) (models.IdentityWrapper, error)
 
 	CreatedAt(ctx context.Context, obj *bug.CreateTimelineItem) (*time.Time, error)
 	LastEdit(ctx context.Context, obj *bug.CreateTimelineItem) (*time.Time, error)
 }
 type EditCommentOperationResolver interface {
 	ID(ctx context.Context, obj *bug.EditCommentOperation) (string, error)
-
+	Author(ctx context.Context, obj *bug.EditCommentOperation) (models.IdentityWrapper, error)
 	Date(ctx context.Context, obj *bug.EditCommentOperation) (*time.Time, error)
 	Target(ctx context.Context, obj *bug.EditCommentOperation) (string, error)
 }
 type IdentityResolver interface {
-	ID(ctx context.Context, obj identity.Interface) (string, error)
-	HumanID(ctx context.Context, obj identity.Interface) (string, error)
+	ID(ctx context.Context, obj models.IdentityWrapper) (string, error)
+	HumanID(ctx context.Context, obj models.IdentityWrapper) (string, error)
 }
 type LabelResolver interface {
 	Name(ctx context.Context, obj *bug.Label) (string, error)
@@ -426,7 +430,7 @@ type LabelResolver interface {
 }
 type LabelChangeOperationResolver interface {
 	ID(ctx context.Context, obj *bug.LabelChangeOperation) (string, error)
-
+	Author(ctx context.Context, obj *bug.LabelChangeOperation) (models.IdentityWrapper, error)
 	Date(ctx context.Context, obj *bug.LabelChangeOperation) (*time.Time, error)
 }
 type LabelChangeResultResolver interface {
@@ -434,7 +438,7 @@ type LabelChangeResultResolver interface {
 }
 type LabelChangeTimelineItemResolver interface {
 	ID(ctx context.Context, obj *bug.LabelChangeTimelineItem) (string, error)
-
+	Author(ctx context.Context, obj *bug.LabelChangeTimelineItem) (models.IdentityWrapper, error)
 	Date(ctx context.Context, obj *bug.LabelChangeTimelineItem) (*time.Time, error)
 }
 type MutationResolver interface {
@@ -453,32 +457,32 @@ type QueryResolver interface {
 }
 type RepositoryResolver interface {
 	AllBugs(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, query *string) (*models.BugConnection, error)
-	Bug(ctx context.Context, obj *models.Repository, prefix string) (*bug.Snapshot, error)
+	Bug(ctx context.Context, obj *models.Repository, prefix string) (models.BugWrapper, error)
 	AllIdentities(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error)
-	Identity(ctx context.Context, obj *models.Repository, prefix string) (identity.Interface, error)
-	UserIdentity(ctx context.Context, obj *models.Repository) (identity.Interface, error)
+	Identity(ctx context.Context, obj *models.Repository, prefix string) (models.IdentityWrapper, error)
+	UserIdentity(ctx context.Context, obj *models.Repository) (models.IdentityWrapper, error)
 	ValidLabels(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.LabelConnection, error)
 }
 type SetStatusOperationResolver interface {
 	ID(ctx context.Context, obj *bug.SetStatusOperation) (string, error)
-
+	Author(ctx context.Context, obj *bug.SetStatusOperation) (models.IdentityWrapper, error)
 	Date(ctx context.Context, obj *bug.SetStatusOperation) (*time.Time, error)
 	Status(ctx context.Context, obj *bug.SetStatusOperation) (models.Status, error)
 }
 type SetStatusTimelineItemResolver interface {
 	ID(ctx context.Context, obj *bug.SetStatusTimelineItem) (string, error)
-
+	Author(ctx context.Context, obj *bug.SetStatusTimelineItem) (models.IdentityWrapper, error)
 	Date(ctx context.Context, obj *bug.SetStatusTimelineItem) (*time.Time, error)
 	Status(ctx context.Context, obj *bug.SetStatusTimelineItem) (models.Status, error)
 }
 type SetTitleOperationResolver interface {
 	ID(ctx context.Context, obj *bug.SetTitleOperation) (string, error)
-
+	Author(ctx context.Context, obj *bug.SetTitleOperation) (models.IdentityWrapper, error)
 	Date(ctx context.Context, obj *bug.SetTitleOperation) (*time.Time, error)
 }
 type SetTitleTimelineItemResolver interface {
 	ID(ctx context.Context, obj *bug.SetTitleTimelineItem) (string, error)
-
+	Author(ctx context.Context, obj *bug.SetTitleTimelineItem) (models.IdentityWrapper, error)
 	Date(ctx context.Context, obj *bug.SetTitleTimelineItem) (*time.Time, error)
 }
 
@@ -1873,877 +1877,642 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er
 	return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil
 }
 
-var parsedSchema = gqlparser.MustLoadSchema(
-	&ast.Source{Name: "schema.graphql", Input: `input AddCommentInput {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	"The name of the repository. If not set, the default repository is used.
-	"""
-	repoRef: String
-	"""
-	The bug ID's prefix.
-	"""
-	prefix: String!
-	"""
-	The first message of the new bug.
-	"""
-	message: String!
-	"""
-	The collection of file's hash required for the first message.
-	"""
-	files: [Hash!]
+var sources = []*ast.Source{
+	&ast.Source{Name: "schema/bug.graphql", Input: `"""Represents a comment on a bug."""
+type Comment implements Authored {
+  """The author of this comment."""
+  author: Identity!
+
+  """The message of this comment."""
+  message: String!
+
+  """All media's hash referenced in this comment"""
+  files: [Hash!]!
 }
-type AddCommentOperation implements Operation & Authored {
-	"""
-	The identifier of the operation
-	"""
-	id: String!
-	"""
-	The author of this object.
-	"""
-	author: Identity!
-	"""
-	The datetime when this operation was issued.
-	"""
-	date: Time!
-	message: String!
-	files: [Hash!]!
+
+type CommentConnection {
+  edges: [CommentEdge!]!
+  nodes: [Comment!]!
+  pageInfo: PageInfo!
+  totalCount: Int!
 }
-type AddCommentPayload {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	The affected bug.
-	"""
-	bug: Bug!
-	"""
-	The resulting operation.
-	"""
-	operation: AddCommentOperation!
-}
-"""
-AddCommentTimelineItem is a TimelineItem that represent a Comment and its edition history
-"""
-type AddCommentTimelineItem implements TimelineItem & Authored {
-	"""
-	The identifier of the source operation
-	"""
-	id: String!
-	author: Identity!
-	message: String!
-	messageIsEmpty: Boolean!
-	files: [Hash!]!
-	createdAt: Time!
-	lastEdit: Time!
-	edited: Boolean!
-	history: [CommentHistoryStep!]!
-}
-"""
-An object that has an author.
-"""
-interface Authored {
-	"""
-	The author of this object.
-	"""
-	author: Identity!
+
+type CommentEdge {
+  cursor: String!
+  node: Comment!
 }
+
+enum Status {
+  OPEN
+  CLOSED
+}
+
 type Bug implements Authored {
-	"""
-	The identifier for this bug
-	"""
-	id: String!
-	"""
-	The human version (truncated) identifier for this bug
-	"""
-	humanId: String!
-	status: Status!
-	title: String!
-	labels: [Label!]!
-	author: Identity!
-	createdAt: Time!
-	lastEdit: Time!
-	"""
-	The actors of the bug. Actors are Identity that have interacted with the bug.
-	"""
-	actors("""
-	Returns the elements in the list that come after the specified cursor.
-	"""
-	after: String, """
-	Returns the elements in the list that come before the specified cursor.
-	"""
-	before: String, """
-	Returns the first _n_ elements from the list.
-	"""
-	first: Int, """
-	Returns the last _n_ elements from the list.
-	"""
-	last: Int): IdentityConnection!
-	"""
-	The participants of the bug. Participants are Identity that have created or
-	  added a comment on the bug.
-	"""
-	participants("""
-	Returns the elements in the list that come after the specified cursor.
-	"""
-	after: String, """
-	Returns the elements in the list that come before the specified cursor.
-	"""
-	before: String, """
-	Returns the first _n_ elements from the list.
-	"""
-	first: Int, """
-	Returns the last _n_ elements from the list.
-	"""
-	last: Int): IdentityConnection!
-	comments("""
-	Returns the elements in the list that come after the specified cursor.
-	"""
-	after: String, """
-	Returns the elements in the list that come before the specified cursor.
-	"""
-	before: String, """
-	Returns the first _n_ elements from the list.
-	"""
-	first: Int, """
-	Returns the last _n_ elements from the list.
-	"""
-	last: Int): CommentConnection!
-	timeline("""
-	Returns the elements in the list that come after the specified cursor.
-	"""
-	after: String, """
-	Returns the elements in the list that come before the specified cursor.
-	"""
-	before: String, """
-	Returns the first _n_ elements from the list.
-	"""
-	first: Int, """
-	Returns the last _n_ elements from the list.
-	"""
-	last: Int): TimelineItemConnection!
-	operations("""
-	Returns the elements in the list that come after the specified cursor.
-	"""
-	after: String, """
-	Returns the elements in the list that come before the specified cursor.
-	"""
-	before: String, """
-	Returns the first _n_ elements from the list.
-	"""
-	first: Int, """
-	Returns the last _n_ elements from the list.
-	"""
-	last: Int): OperationConnection!
-}
-"""
-The connection type for Bug.
-"""
+  """The identifier for this bug"""
+  id: String!
+  """The human version (truncated) identifier for this bug"""
+  humanId: String!
+  status: Status!
+  title: String!
+  labels: [Label!]!
+  author: Identity!
+  createdAt: Time!
+  lastEdit: Time!
+
+  """The actors of the bug. Actors are Identity that have interacted with the bug."""
+  actors(
+    """Returns the elements in the list that come after the specified cursor."""
+    after: String
+    """Returns the elements in the list that come before the specified cursor."""
+    before: String
+    """Returns the first _n_ elements from the list."""
+    first: Int
+    """Returns the last _n_ elements from the list."""
+    last: Int
+  ): IdentityConnection!
+
+  """The participants of the bug. Participants are Identity that have created or
+  added a comment on the bug."""
+  participants(
+    """Returns the elements in the list that come after the specified cursor."""
+    after: String
+    """Returns the elements in the list that come before the specified cursor."""
+    before: String
+    """Returns the first _n_ elements from the list."""
+    first: Int
+    """Returns the last _n_ elements from the list."""
+    last: Int
+  ): IdentityConnection!
+
+  comments(
+    """Returns the elements in the list that come after the specified cursor."""
+    after: String
+    """Returns the elements in the list that come before the specified cursor."""
+    before: String
+    """Returns the first _n_ elements from the list."""
+    first: Int
+    """Returns the last _n_ elements from the list."""
+    last: Int
+  ): CommentConnection!
+
+  timeline(
+    """Returns the elements in the list that come after the specified cursor."""
+    after: String
+    """Returns the elements in the list that come before the specified cursor."""
+    before: String
+    """Returns the first _n_ elements from the list."""
+    first: Int
+    """Returns the last _n_ elements from the list."""
+    last: Int
+  ): TimelineItemConnection!
+
+  operations(
+    """Returns the elements in the list that come after the specified cursor."""
+    after: String
+    """Returns the elements in the list that come before the specified cursor."""
+    before: String
+    """Returns the first _n_ elements from the list."""
+    first: Int
+    """Returns the last _n_ elements from the list."""
+    last: Int
+  ): OperationConnection!
+}
+
+"""The connection type for Bug."""
 type BugConnection {
-	"""
-	A list of edges.
-	"""
-	edges: [BugEdge!]!
-	nodes: [Bug!]!
-	"""
-	Information to aid in pagination.
-	"""
-	pageInfo: PageInfo!
-	"""
-	Identifies the total count of items in the connection.
-	"""
-	totalCount: Int!
-}
-"""
-An edge in a connection.
-"""
+  """A list of edges."""
+  edges: [BugEdge!]!
+  nodes: [Bug!]!
+  """Information to aid in pagination."""
+  pageInfo: PageInfo!
+  """Identifies the total count of items in the connection."""
+  totalCount: Int!
+}
+
+"""An edge in a connection."""
 type BugEdge {
-	"""
-	A cursor for use in pagination.
-	"""
-	cursor: String!
-	"""
-	The item at the end of the edge.
-	"""
-	node: Bug!
+  """A cursor for use in pagination."""
+  cursor: String!
+  """The item at the end of the edge."""
+  node: Bug!
 }
+`, BuiltIn: false},
+	&ast.Source{Name: "schema/identity.graphql", Input: `"""Represents an identity"""
+type Identity {
+    """The identifier for this identity"""
+    id: String!
+    """The human version (truncated) identifier for this identity"""
+    humanId: String!
+    """The name of the person, if known."""
+    name: String
+    """The email of the person, if known."""
+    email: String
+    """A non-empty string to display, representing the identity, based on the non-empty values."""
+    displayName: String!
+    """An url to an avatar"""
+    avatarUrl: String
+    """isProtected is 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: Boolean!
+}
+
+type IdentityConnection {
+    edges: [IdentityEdge!]!
+    nodes: [Identity!]!
+    pageInfo: PageInfo!
+    totalCount: Int!
+}
+
+type IdentityEdge {
+    cursor: String!
+    node: Identity!
+}`, BuiltIn: false},
+	&ast.Source{Name: "schema/label.graphql", Input: `"""Label for a bug."""
+type Label {
+    """The name of the label."""
+    name: String!
+    """Color of the label."""
+    color: Color!
+}
+
+type LabelConnection {
+    edges: [LabelEdge!]!
+    nodes: [Label!]!
+    pageInfo: PageInfo!
+    totalCount: Int!
+}
+
+type LabelEdge {
+    cursor: String!
+    node: Label!
+}`, BuiltIn: false},
+	&ast.Source{Name: "schema/mutations.graphql", Input: `input NewBugInput {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """"The name of the repository. If not set, the default repository is used."""
+    repoRef: String
+    """The title of the new bug."""
+    title: String!
+    """The first message of the new bug."""
+    message: String!
+    """The collection of file's hash required for the first message."""
+    files: [Hash!]
+}
+
+type NewBugPayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The created bug."""
+    bug: Bug!
+    """The resulting operation."""
+    operation: CreateOperation!
+}
+
+input AddCommentInput {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """"The name of the repository. If not set, the default repository is used."""
+    repoRef: String
+    """The bug ID's prefix."""
+    prefix: String!
+    """The first message of the new bug."""
+    message: String!
+    """The collection of file's hash required for the first message."""
+    files: [Hash!]
+}
+
+type AddCommentPayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The affected bug."""
+    bug: Bug!
+    """The resulting operation."""
+    operation: AddCommentOperation!
+}
+
 input ChangeLabelInput {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	"The name of the repository. If not set, the default repository is used.
-	"""
-	repoRef: String
-	"""
-	The bug ID's prefix.
-	"""
-	prefix: String!
-	"""
-	The list of label to add.
-	"""
-	added: [String!]
-	"""
-	The list of label to remove.
-	"""
-	Removed: [String!]
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """"The name of the repository. If not set, the default repository is used."""
+    repoRef: String
+    """The bug ID's prefix."""
+    prefix: String!
+    """The list of label to add."""
+    added: [String!]
+    """The list of label to remove."""
+    Removed: [String!]
 }
-type ChangeLabelPayload {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	The affected bug.
-	"""
-	bug: Bug!
-	"""
-	The resulting operation.
-	"""
-	operation: LabelChangeOperation!
-	"""
-	The effect each source label had.
-	"""
-	results: [LabelChangeResult]!
+
+enum LabelChangeStatus {
+    ADDED
+    REMOVED
+    DUPLICATE_IN_OP
+    ALREADY_EXIST
+    DOESNT_EXIST
 }
-input CloseBugInput {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	"The name of the repository. If not set, the default repository is used.
-	"""
-	repoRef: String
-	"""
-	The bug ID's prefix.
-	"""
-	prefix: String!
+
+type LabelChangeResult {
+    """The source label."""
+    label: Label!
+    """The effect this label had."""
+    status: LabelChangeStatus!
 }
-type CloseBugPayload {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	The affected bug.
-	"""
-	bug: Bug!
-	"""
-	The resulting operation.
-	"""
-	operation: SetStatusOperation!
-}
-"""
-Defines a color by red, green and blue components.
-"""
-type Color {
-	"""
-	Red component of the color.
-	"""
-	R: Int!
-	"""
-	Green component of the color.
-	"""
-	G: Int!
-	"""
-	Blue component of the color.
-	"""
-	B: Int!
-}
-"""
-Represents a comment on a bug.
-"""
-type Comment implements Authored {
-	"""
-	The author of this comment.
-	"""
-	author: Identity!
-	"""
-	The message of this comment.
-	"""
-	message: String!
-	"""
-	All media's hash referenced in this comment
-	"""
-	files: [Hash!]!
+
+type ChangeLabelPayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The affected bug."""
+    bug: Bug!
+    """The resulting operation."""
+    operation: LabelChangeOperation!
+    """The effect each source label had."""
+    results: [LabelChangeResult]!
 }
-type CommentConnection {
-	edges: [CommentEdge!]!
-	nodes: [Comment!]!
-	pageInfo: PageInfo!
-	totalCount: Int!
+
+input OpenBugInput {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """"The name of the repository. If not set, the default repository is used."""
+    repoRef: String
+    """The bug ID's prefix."""
+    prefix: String!
 }
-type CommentEdge {
-	cursor: String!
-	node: Comment!
+
+type OpenBugPayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The affected bug."""
+    bug: Bug!
+    """The resulting operation."""
+    operation: SetStatusOperation!
 }
-"""
-CommentHistoryStep hold one version of a message in the history
-"""
-type CommentHistoryStep {
-	message: String!
-	date: Time!
+
+input CloseBugInput {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """"The name of the repository. If not set, the default repository is used."""
+    repoRef: String
+    """The bug ID's prefix."""
+    prefix: String!
 }
-input CommitAsNeededInput {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	"The name of the repository. If not set, the default repository is used.
-	"""
-	repoRef: String
-	"""
-	The bug ID's prefix.
-	"""
-	prefix: String!
+
+type CloseBugPayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The affected bug."""
+    bug: Bug!
+    """The resulting operation."""
+    operation: SetStatusOperation!
 }
-type CommitAsNeededPayload {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	The affected bug.
-	"""
-	bug: Bug!
+
+input SetTitleInput {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """"The name of the repository. If not set, the default repository is used."""
+    repoRef: String
+    """The bug ID's prefix."""
+    prefix: String!
+    """The new title."""
+    title: String!
+}
+
+type SetTitlePayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The affected bug."""
+    bug: Bug!
+    """The resulting operation"""
+    operation: SetTitleOperation!
 }
+
 input CommitInput {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	"The name of the repository. If not set, the default repository is used.
-	"""
-	repoRef: String
-	"""
-	The bug ID's prefix.
-	"""
-	prefix: String!
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """"The name of the repository. If not set, the default repository is used."""
+    repoRef: String
+    """The bug ID's prefix."""
+    prefix: String!
 }
+
 type CommitPayload {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	The affected bug.
-	"""
-	bug: Bug!
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The affected bug."""
+    bug: Bug!
 }
-type CreateOperation implements Operation & Authored {
-	"""
-	The identifier of the operation
-	"""
-	id: String!
-	"""
-	The author of this object.
-	"""
-	author: Identity!
-	"""
-	The datetime when this operation was issued.
-	"""
-	date: Time!
-	title: String!
-	message: String!
-	files: [Hash!]!
-}
-"""
-CreateTimelineItem is a TimelineItem that represent the creation of a bug and its message edition history
-"""
-type CreateTimelineItem implements TimelineItem & Authored {
-	"""
-	The identifier of the source operation
-	"""
-	id: String!
-	author: Identity!
-	message: String!
-	messageIsEmpty: Boolean!
-	files: [Hash!]!
-	createdAt: Time!
-	lastEdit: Time!
-	edited: Boolean!
-	history: [CommentHistoryStep!]!
+
+input CommitAsNeededInput {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """"The name of the repository. If not set, the default repository is used."""
+    repoRef: String
+    """The bug ID's prefix."""
+    prefix: String!
 }
-type EditCommentOperation implements Operation & Authored {
-	"""
-	The identifier of the operation
-	"""
-	id: String!
-	"""
-	The author of this object.
-	"""
-	author: Identity!
-	"""
-	The datetime when this operation was issued.
-	"""
-	date: Time!
-	target: String!
-	message: String!
-	files: [Hash!]!
+
+type CommitAsNeededPayload {
+    """A unique identifier for the client performing the mutation."""
+    clientMutationId: String
+    """The affected bug."""
+    bug: Bug!
 }
-scalar Hash
-"""
-Represents an identity
-"""
-type Identity {
-	"""
-	The identifier for this identity
-	"""
-	id: String!
-	"""
-	The human version (truncated) identifier for this identity
-	"""
-	humanId: String!
-	"""
-	The name of the person, if known.
-	"""
-	name: String
-	"""
-	The email of the person, if known.
-	"""
-	email: String
-	"""
-	A non-empty string to display, representing the identity, based on the non-empty values.
-	"""
-	displayName: String!
-	"""
-	An url to an avatar
-	"""
-	avatarUrl: String
-	"""
-	isProtected is 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: Boolean!
+`, BuiltIn: false},
+	&ast.Source{Name: "schema/operations.graphql", Input: `"""An operation applied to a bug."""
+interface Operation {
+    """The identifier of the operation"""
+    id: String!
+    """The operations author."""
+    author: Identity!
+    """The datetime when this operation was issued."""
+    date: Time!
 }
-type IdentityConnection {
-	edges: [IdentityEdge!]!
-	nodes: [Identity!]!
-	pageInfo: PageInfo!
-	totalCount: Int!
+
+# Connection
+
+"""The connection type for an Operation"""
+type OperationConnection {
+    edges: [OperationEdge!]!
+    nodes: [Operation!]!
+    pageInfo: PageInfo!
+    totalCount: Int!
 }
-type IdentityEdge {
-	cursor: String!
-	node: Identity!
+
+"""Represent an Operation"""
+type OperationEdge {
+    cursor: String!
+    node: Operation!
 }
-"""
-Label for a bug.
-"""
-type Label {
-	"""
-	The name of the label.
-	"""
-	name: String!
-	"""
-	Color of the label.
-	"""
-	color: Color!
+
+# Operations
+
+type CreateOperation implements Operation & Authored {
+    """The identifier of the operation"""
+    id: String!
+    """The author of this object."""
+    author: Identity!
+    """The datetime when this operation was issued."""
+    date: Time!
+
+    title: String!
+    message: String!
+    files: [Hash!]!
 }
-type LabelChangeOperation implements Operation & Authored {
-	"""
-	The identifier of the operation
-	"""
-	id: String!
-	"""
-	The author of this object.
-	"""
-	author: Identity!
-	"""
-	The datetime when this operation was issued.
-	"""
-	date: Time!
-	added: [Label!]!
-	removed: [Label!]!
+
+type SetTitleOperation implements Operation & Authored {
+    """The identifier of the operation"""
+    id: String!
+    """The author of this object."""
+    author: Identity!
+    """The datetime when this operation was issued."""
+    date: Time!
+
+    title: String!
+    was: String!
 }
-type LabelChangeResult {
-	"""
-	The source label.
-	"""
-	label: Label!
-	"""
-	The effect this label had.
-	"""
-	status: LabelChangeStatus!
+
+type AddCommentOperation implements Operation & Authored {
+    """The identifier of the operation"""
+    id: String!
+    """The author of this object."""
+    author: Identity!
+    """The datetime when this operation was issued."""
+    date: Time!
+
+    message: String!
+    files: [Hash!]!
 }
-enum LabelChangeStatus {
-	ADDED
-	REMOVED
-	DUPLICATE_IN_OP
-	ALREADY_EXIST
-	DOESNT_EXIST
-}
-"""
-LabelChangeTimelineItem is a TimelineItem that represent a change in the labels of a bug
-"""
-type LabelChangeTimelineItem implements TimelineItem & Authored {
-	"""
-	The identifier of the source operation
-	"""
-	id: String!
-	author: Identity!
-	date: Time!
-	added: [Label!]!
-	removed: [Label!]!
+
+type EditCommentOperation implements Operation & Authored {
+    """The identifier of the operation"""
+    id: String!
+    """The author of this object."""
+    author: Identity!
+    """The datetime when this operation was issued."""
+    date: Time!
+
+    target: String!
+    message: String!
+    files: [Hash!]!
 }
-type LabelConnection {
-	edges: [LabelEdge!]!
-	nodes: [Label!]!
-	pageInfo: PageInfo!
-	totalCount: Int!
+
+type SetStatusOperation implements Operation & Authored {
+    """The identifier of the operation"""
+    id: String!
+    """The author of this object."""
+    author: Identity!
+    """The datetime when this operation was issued."""
+    date: Time!
+
+    status: Status!
 }
-type LabelEdge {
-	cursor: String!
-	node: Label!
+
+type LabelChangeOperation implements Operation & Authored {
+    """The identifier of the operation"""
+    id: String!
+    """The author of this object."""
+    author: Identity!
+    """The datetime when this operation was issued."""
+    date: Time!
+
+    added: [Label!]!
+    removed: [Label!]!
+}
+`, BuiltIn: false},
+	&ast.Source{Name: "schema/repository.graphql", Input: `
+type Repository {
+    """All the bugs"""
+    allBugs(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the elements in the list that come before the specified cursor."""
+        before: String
+        """Returns the first _n_ elements from the list."""
+        first: Int
+        """Returns the last _n_ elements from the list."""
+        last: Int
+        """A query to select and order bugs"""
+        query: String
+    ): BugConnection!
+
+    bug(prefix: String!): Bug
+
+    """All the identities"""
+    allIdentities(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the elements in the list that come before the specified cursor."""
+        before: String
+        """Returns the first _n_ elements from the list."""
+        first: Int
+        """Returns the last _n_ elements from the list."""
+        last: Int
+    ): IdentityConnection!
+
+    identity(prefix: String!): Identity
+
+    """The identity created or selected by the user as its own"""
+    userIdentity: Identity
+
+    """List of valid labels."""
+    validLabels(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the elements in the list that come before the specified cursor."""
+        before: String
+        """Returns the first _n_ elements from the list."""
+        first: Int
+        """Returns the last _n_ elements from the list."""
+        last: Int
+    ): LabelConnection!
+}`, BuiltIn: false},
+	&ast.Source{Name: "schema/root.graphql", Input: `type Query {
+    """The default unnamend repository."""
+    defaultRepository: Repository
+    """Access a repository by reference/name."""
+    repository(ref: String!): Repository
+
+    #TODO: connection for all repositories
 }
+
 type Mutation {
-	"""
-	Create a new bug
-	"""
-	newBug(input: NewBugInput!): NewBugPayload!
-	"""
-	Add a new comment to a bug
-	"""
-	addComment(input: AddCommentInput!): AddCommentPayload!
-	"""
-	Add or remove a set of label on a bug
-	"""
-	changeLabels(input: ChangeLabelInput): ChangeLabelPayload!
-	"""
-	Change a bug's status to open
-	"""
-	openBug(input: OpenBugInput!): OpenBugPayload!
-	"""
-	Change a bug's status to closed
-	"""
-	closeBug(input: CloseBugInput!): CloseBugPayload!
-	"""
-	Change a bug's title
-	"""
-	setTitle(input: SetTitleInput!): SetTitlePayload!
-	"""
-	Commit write the pending operations into storage. This mutation fail if nothing is pending
-	"""
-	commit(input: CommitInput!): CommitPayload!
-	"""
-	Commit write the pending operations into storage. This mutation succed if nothing is pending
-	"""
-	commitAsNeeded(input: CommitAsNeededInput!): CommitAsNeededPayload!
-}
-input NewBugInput {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	"The name of the repository. If not set, the default repository is used.
-	"""
-	repoRef: String
-	"""
-	The title of the new bug.
-	"""
-	title: String!
-	"""
-	The first message of the new bug.
-	"""
-	message: String!
-	"""
-	The collection of file's hash required for the first message.
-	"""
-	files: [Hash!]
-}
-type NewBugPayload {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	The created bug.
-	"""
-	bug: Bug!
-	"""
-	The resulting operation.
-	"""
-	operation: CreateOperation!
+    """Create a new bug"""
+    newBug(input: NewBugInput!): NewBugPayload!
+    """Add a new comment to a bug"""
+    addComment(input: AddCommentInput!): AddCommentPayload!
+    """Add or remove a set of label on a bug"""
+    changeLabels(input: ChangeLabelInput): ChangeLabelPayload!
+    """Change a bug's status to open"""
+    openBug(input: OpenBugInput!): OpenBugPayload!
+    """Change a bug's status to closed"""
+    closeBug(input: CloseBugInput!): CloseBugPayload!
+    """Change a bug's title"""
+    setTitle(input: SetTitleInput!): SetTitlePayload!
+    """Commit write the pending operations into storage. This mutation fail if nothing is pending"""
+    commit(input: CommitInput!): CommitPayload!
+    """Commit write the pending operations into storage. This mutation succed if nothing is pending"""
+    commitAsNeeded(input: CommitAsNeededInput!): CommitAsNeededPayload!
+}
+`, BuiltIn: false},
+	&ast.Source{Name: "schema/timeline.graphql", Input: `"""An item in the timeline of events"""
+interface TimelineItem {
+    """The identifier of the source operation"""
+    id: String!
 }
-input OpenBugInput {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	"The name of the repository. If not set, the default repository is used.
-	"""
-	repoRef: String
-	"""
-	The bug ID's prefix.
-	"""
-	prefix: String!
+
+"""CommentHistoryStep hold one version of a message in the history"""
+type CommentHistoryStep {
+    message: String!
+    date: Time!
 }
-type OpenBugPayload {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	The affected bug.
-	"""
-	bug: Bug!
-	"""
-	The resulting operation.
-	"""
-	operation: SetStatusOperation!
-}
-"""
-An operation applied to a bug.
-"""
-interface Operation {
-	"""
-	The identifier of the operation
-	"""
-	id: String!
-	"""
-	The operations author.
-	"""
-	author: Identity!
-	"""
-	The datetime when this operation was issued.
-	"""
-	date: Time!
-}
-"""
-The connection type for an Operation
-"""
-type OperationConnection {
-	edges: [OperationEdge!]!
-	nodes: [Operation!]!
-	pageInfo: PageInfo!
-	totalCount: Int!
-}
-"""
-Represent an Operation
-"""
-type OperationEdge {
-	cursor: String!
-	node: Operation!
+
+# Connection
+
+"""The connection type for TimelineItem"""
+type TimelineItemConnection {
+    edges: [TimelineItemEdge!]!
+    nodes: [TimelineItem!]!
+    pageInfo: PageInfo!
+    totalCount: Int!
 }
-"""
-Information about pagination in a connection.
-"""
-type PageInfo {
-	"""
-	When paginating forwards, are there more items?
-	"""
-	hasNextPage: Boolean!
-	"""
-	When paginating backwards, are there more items?
-	"""
-	hasPreviousPage: Boolean!
-	"""
-	When paginating backwards, the cursor to continue.
-	"""
-	startCursor: String!
-	"""
-	When paginating forwards, the cursor to continue.
-	"""
-	endCursor: String!
-}
-type Query {
-	"""
-	The default unnamend repository.
-	"""
-	defaultRepository: Repository
-	"""
-	Access a repository by reference/name.
-	"""
-	repository(ref: String!): Repository
+
+"""Represent a TimelineItem"""
+type TimelineItemEdge {
+    cursor: String!
+    node: TimelineItem!
 }
-type Repository {
-	"""
-	All the bugs
-	"""
-	allBugs("""
-	Returns the elements in the list that come after the specified cursor.
-	"""
-	after: String, """
-	Returns the elements in the list that come before the specified cursor.
-	"""
-	before: String, """
-	Returns the first _n_ elements from the list.
-	"""
-	first: Int, """
-	Returns the last _n_ elements from the list.
-	"""
-	last: Int, """
-	A query to select and order bugs
-	"""
-	query: String): BugConnection!
-	bug(prefix: String!): Bug
-	"""
-	All the identities
-	"""
-	allIdentities("""
-	Returns the elements in the list that come after the specified cursor.
-	"""
-	after: String, """
-	Returns the elements in the list that come before the specified cursor.
-	"""
-	before: String, """
-	Returns the first _n_ elements from the list.
-	"""
-	first: Int, """
-	Returns the last _n_ elements from the list.
-	"""
-	last: Int): IdentityConnection!
-	identity(prefix: String!): Identity
-	"""
-	The identity created or selected by the user as its own
-	"""
-	userIdentity: Identity
-	"""
-	List of valid labels.
-	"""
-	validLabels("""
-	Returns the elements in the list that come after the specified cursor.
-	"""
-	after: String, """
-	Returns the elements in the list that come before the specified cursor.
-	"""
-	before: String, """
-	Returns the first _n_ elements from the list.
-	"""
-	first: Int, """
-	Returns the last _n_ elements from the list.
-	"""
-	last: Int): LabelConnection!
+
+# Items
+
+"""CreateTimelineItem is a TimelineItem that represent the creation of a bug and its message edition history"""
+type CreateTimelineItem implements TimelineItem & Authored {
+    """The identifier of the source operation"""
+    id: String!
+    author: Identity!
+    message: String!
+    messageIsEmpty: Boolean!
+    files: [Hash!]!
+    createdAt: Time!
+    lastEdit: Time!
+    edited: Boolean!
+    history: [CommentHistoryStep!]!
+}
+
+"""AddCommentTimelineItem is a TimelineItem that represent a Comment and its edition history"""
+type AddCommentTimelineItem implements TimelineItem & Authored {
+    """The identifier of the source operation"""
+    id: String!
+    author: Identity!
+    message: String!
+    messageIsEmpty: Boolean!
+    files: [Hash!]!
+    createdAt: Time!
+    lastEdit: Time!
+    edited: Boolean!
+    history: [CommentHistoryStep!]!
+}
+
+"""LabelChangeTimelineItem is a TimelineItem that represent a change in the labels of a bug"""
+type LabelChangeTimelineItem implements TimelineItem & Authored {
+    """The identifier of the source operation"""
+    id: String!
+    author: Identity!
+    date: Time!
+    added: [Label!]!
+    removed: [Label!]!
 }
-type SetStatusOperation implements Operation & Authored {
-	"""
-	The identifier of the operation
-	"""
-	id: String!
-	"""
-	The author of this object.
-	"""
-	author: Identity!
-	"""
-	The datetime when this operation was issued.
-	"""
-	date: Time!
-	status: Status!
-}
-"""
-SetStatusTimelineItem is a TimelineItem that represent a change in the status of a bug
-"""
+
+"""SetStatusTimelineItem is a TimelineItem that represent a change in the status of a bug"""
 type SetStatusTimelineItem implements TimelineItem & Authored {
-	"""
-	The identifier of the source operation
-	"""
-	id: String!
-	author: Identity!
-	date: Time!
-	status: Status!
-}
-input SetTitleInput {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	"The name of the repository. If not set, the default repository is used.
-	"""
-	repoRef: String
-	"""
-	The bug ID's prefix.
-	"""
-	prefix: String!
-	"""
-	The new title.
-	"""
-	title: String!
+    """The identifier of the source operation"""
+    id: String!
+    author: Identity!
+    date: Time!
+    status: Status!
 }
-type SetTitleOperation implements Operation & Authored {
-	"""
-	The identifier of the operation
-	"""
-	id: String!
-	"""
-	The author of this object.
-	"""
-	author: Identity!
-	"""
-	The datetime when this operation was issued.
-	"""
-	date: Time!
-	title: String!
-	was: String!
-}
-type SetTitlePayload {
-	"""
-	A unique identifier for the client performing the mutation.
-	"""
-	clientMutationId: String
-	"""
-	The affected bug.
-	"""
-	bug: Bug!
-	"""
-	The resulting operation
-	"""
-	operation: SetTitleOperation!
-}
-"""
-LabelChangeTimelineItem is a TimelineItem that represent a change in the title of a bug
-"""
+
+"""LabelChangeTimelineItem is a TimelineItem that represent a change in the title of a bug"""
 type SetTitleTimelineItem implements TimelineItem & Authored {
-	"""
-	The identifier of the source operation
-	"""
-	id: String!
-	author: Identity!
-	date: Time!
-	title: String!
-	was: String!
+    """The identifier of the source operation"""
+    id: String!
+    author: Identity!
+    date: Time!
+    title: String!
+    was: String!
+}
+`, BuiltIn: false},
+	&ast.Source{Name: "schema/types.graphql", Input: `scalar Time
+scalar Hash
+
+"""Defines a color by red, green and blue components."""
+type Color {
+    """Red component of the color."""
+    R: Int!
+    """Green component of the color."""
+    G: Int!
+    """Blue component of the color."""
+    B: Int!
 }
-enum Status {
-	OPEN
-	CLOSED
+
+"""Information about pagination in a connection."""
+type PageInfo {
+    """When paginating forwards, are there more items?"""
+    hasNextPage: Boolean!
+    """When paginating backwards, are there more items?"""
+    hasPreviousPage: Boolean!
+    """When paginating backwards, the cursor to continue."""
+    startCursor: String!
+    """When paginating forwards, the cursor to continue."""
+    endCursor: String!
 }
-scalar Time
-"""
-An item in the timeline of events
-"""
-interface TimelineItem {
-	"""
-	The identifier of the source operation
-	"""
-	id: String!
-}
-"""
-The connection type for TimelineItem
-"""
-type TimelineItemConnection {
-	edges: [TimelineItemEdge!]!
-	nodes: [TimelineItem!]!
-	pageInfo: PageInfo!
-	totalCount: Int!
-}
-"""
-Represent a TimelineItem
-"""
-type TimelineItemEdge {
-	cursor: String!
-	node: TimelineItem!
+
+"""An object that has an author."""
+interface Authored {
+    """The author of this object."""
+    author: Identity!
 }
-`},
-)
+`, BuiltIn: false},
+}
+var parsedSchema = gqlparser.MustLoadSchema(sources...)
 
 // endregion ************************** generated!.gotpl **************************
 
  @@ -8,7 +8,6 @@ import (
 	"strconv"
 
 	"github.com/MichaelMure/git-bug/bug"
-	"github.com/MichaelMure/git-bug/identity"
 	"github.com/MichaelMure/git-bug/util/git"
 )
 
@@ -34,7 +33,7 @@ type AddCommentPayload struct {
 	// A unique identifier for the client performing the mutation.
 	ClientMutationID *string `json:"clientMutationId"`
 	// The affected bug.
-	Bug *bug.Snapshot `json:"bug"`
+	Bug BugWrapper `json:"bug"`
 	// The resulting operation.
 	Operation *bug.AddCommentOperation `json:"operation"`
 }
@@ -42,8 +41,8 @@ type AddCommentPayload struct {
 // The connection type for Bug.
 type BugConnection struct {
 	// A list of edges.
-	Edges []*BugEdge      `json:"edges"`
-	Nodes []*bug.Snapshot `json:"nodes"`
+	Edges []*BugEdge   `json:"edges"`
+	Nodes []BugWrapper `json:"nodes"`
 	// Information to aid in pagination.
 	PageInfo *PageInfo `json:"pageInfo"`
 	// Identifies the total count of items in the connection.
@@ -55,7 +54,7 @@ type BugEdge struct {
 	// A cursor for use in pagination.
 	Cursor string `json:"cursor"`
 	// The item at the end of the edge.
-	Node *bug.Snapshot `json:"node"`
+	Node BugWrapper `json:"node"`
 }
 
 type ChangeLabelInput struct {
@@ -75,7 +74,7 @@ type ChangeLabelPayload struct {
 	// A unique identifier for the client performing the mutation.
 	ClientMutationID *string `json:"clientMutationId"`
 	// The affected bug.
-	Bug *bug.Snapshot `json:"bug"`
+	Bug BugWrapper `json:"bug"`
 	// The resulting operation.
 	Operation *bug.LabelChangeOperation `json:"operation"`
 	// The effect each source label had.
@@ -95,7 +94,7 @@ type CloseBugPayload struct {
 	// A unique identifier for the client performing the mutation.
 	ClientMutationID *string `json:"clientMutationId"`
 	// The affected bug.
-	Bug *bug.Snapshot `json:"bug"`
+	Bug BugWrapper `json:"bug"`
 	// The resulting operation.
 	Operation *bug.SetStatusOperation `json:"operation"`
 }
@@ -125,7 +124,7 @@ type CommitAsNeededPayload struct {
 	// A unique identifier for the client performing the mutation.
 	ClientMutationID *string `json:"clientMutationId"`
 	// The affected bug.
-	Bug *bug.Snapshot `json:"bug"`
+	Bug BugWrapper `json:"bug"`
 }
 
 type CommitInput struct {
@@ -141,19 +140,19 @@ type CommitPayload struct {
 	// A unique identifier for the client performing the mutation.
 	ClientMutationID *string `json:"clientMutationId"`
 	// The affected bug.
-	Bug *bug.Snapshot `json:"bug"`
+	Bug BugWrapper `json:"bug"`
 }
 
 type IdentityConnection struct {
-	Edges      []*IdentityEdge      `json:"edges"`
-	Nodes      []identity.Interface `json:"nodes"`
-	PageInfo   *PageInfo            `json:"pageInfo"`
-	TotalCount int                  `json:"totalCount"`
+	Edges      []*IdentityEdge   `json:"edges"`
+	Nodes      []IdentityWrapper `json:"nodes"`
+	PageInfo   *PageInfo         `json:"pageInfo"`
+	TotalCount int               `json:"totalCount"`
 }
 
 type IdentityEdge struct {
-	Cursor string             `json:"cursor"`
-	Node   identity.Interface `json:"node"`
+	Cursor string          `json:"cursor"`
+	Node   IdentityWrapper `json:"node"`
 }
 
 type LabelConnection struct {
@@ -185,7 +184,7 @@ type NewBugPayload struct {
 	// A unique identifier for the client performing the mutation.
 	ClientMutationID *string `json:"clientMutationId"`
 	// The created bug.
-	Bug *bug.Snapshot `json:"bug"`
+	Bug BugWrapper `json:"bug"`
 	// The resulting operation.
 	Operation *bug.CreateOperation `json:"operation"`
 }
@@ -203,7 +202,7 @@ type OpenBugPayload struct {
 	// A unique identifier for the client performing the mutation.
 	ClientMutationID *string `json:"clientMutationId"`
 	// The affected bug.
-	Bug *bug.Snapshot `json:"bug"`
+	Bug BugWrapper `json:"bug"`
 	// The resulting operation.
 	Operation *bug.SetStatusOperation `json:"operation"`
 }
@@ -249,7 +248,7 @@ type SetTitlePayload struct {
 	// A unique identifier for the client performing the mutation.
 	ClientMutationID *string `json:"clientMutationId"`
 	// The affected bug.
-	Bug *bug.Snapshot `json:"bug"`
+	Bug BugWrapper `json:"bug"`
 	// The resulting operation
 	Operation *bug.SetTitleOperation `json:"operation"`
 }
  @@ -0,0 +1,214 @@
+package models
+
+import (
+	"sync"
+	"time"
+
+	"github.com/MichaelMure/git-bug/bug"
+	"github.com/MichaelMure/git-bug/cache"
+	"github.com/MichaelMure/git-bug/entity"
+)
+
+type BugWrapper interface {
+	Id() entity.Id
+	LastEdit() time.Time
+	Status() bug.Status
+	Title() string
+	Comments() ([]bug.Comment, error)
+	Labels() []bug.Label
+	Author() (IdentityWrapper, error)
+	Actors() ([]IdentityWrapper, error)
+	Participants() ([]IdentityWrapper, error)
+	CreatedAt() time.Time
+	Timeline() ([]bug.TimelineItem, error)
+	Operations() ([]bug.Operation, error)
+
+	IsAuthored()
+}
+
+var _ BugWrapper = &LazyBug{}
+
+type LazyBug struct {
+	cache   *cache.RepoCache
+	excerpt *cache.BugExcerpt
+
+	mu   sync.Mutex
+	snap *bug.Snapshot
+}
+
+func NewLazyBug(cache *cache.RepoCache, excerpt *cache.BugExcerpt) *LazyBug {
+	return &LazyBug{
+		cache:   cache,
+		excerpt: excerpt,
+	}
+}
+
+func (lb *LazyBug) load() error {
+	if lb.snap != nil {
+		return nil
+	}
+
+	lb.mu.Lock()
+	defer lb.mu.Unlock()
+
+	b, err := lb.cache.ResolveBug(lb.excerpt.Id)
+	if err != nil {
+		return err
+	}
+
+	lb.snap = b.Snapshot()
+	return nil
+}
+
+func (lb *LazyBug) identity(id entity.Id) (IdentityWrapper, error) {
+	i, err := lb.cache.ResolveIdentityExcerpt(id)
+	if err != nil {
+		return nil, err
+	}
+	return &LazyIdentity{cache: lb.cache, excerpt: i}, nil
+}
+
+// Sign post method for gqlgen
+func (lb *LazyBug) IsAuthored() {}
+
+func (lb *LazyBug) Id() entity.Id {
+	return lb.excerpt.Id
+}
+
+func (lb *LazyBug) LastEdit() time.Time {
+	return time.Unix(lb.excerpt.EditUnixTime, 0)
+}
+
+func (lb *LazyBug) Status() bug.Status {
+	return lb.excerpt.Status
+}
+
+func (lb *LazyBug) Title() string {
+	return lb.excerpt.Title
+}
+
+func (lb *LazyBug) Comments() ([]bug.Comment, error) {
+	err := lb.load()
+	if err != nil {
+		return nil, err
+	}
+	return lb.snap.Comments, nil
+}
+
+func (lb *LazyBug) Labels() []bug.Label {
+	return lb.excerpt.Labels
+}
+
+func (lb *LazyBug) Author() (IdentityWrapper, error) {
+	return lb.identity(lb.excerpt.AuthorId)
+}
+
+func (lb *LazyBug) Actors() ([]IdentityWrapper, error) {
+	result := make([]IdentityWrapper, len(lb.excerpt.Actors))
+	for i, actorId := range lb.excerpt.Actors {
+		actor, err := lb.identity(actorId)
+		if err != nil {
+			return nil, err
+		}
+		result[i] = actor
+	}
+	return result, nil
+}
+
+func (lb *LazyBug) Participants() ([]IdentityWrapper, error) {
+	result := make([]IdentityWrapper, len(lb.excerpt.Participants))
+	for i, participantId := range lb.excerpt.Participants {
+		participant, err := lb.identity(participantId)
+		if err != nil {
+			return nil, err
+		}
+		result[i] = participant
+	}
+	return result, nil
+}
+
+func (lb *LazyBug) CreatedAt() time.Time {
+	return time.Unix(lb.excerpt.CreateUnixTime, 0)
+}
+
+func (lb *LazyBug) Timeline() ([]bug.TimelineItem, error) {
+	err := lb.load()
+	if err != nil {
+		return nil, err
+	}
+	return lb.snap.Timeline, nil
+}
+
+func (lb *LazyBug) Operations() ([]bug.Operation, error) {
+	err := lb.load()
+	if err != nil {
+		return nil, err
+	}
+	result := make([]bug.Operation, len(lb.snap.Operations))
+	for i, operation := range lb.snap.Operations {
+		result[i] = operation
+	}
+	return result, nil
+}
+
+var _ BugWrapper = &LoadedBug{}
+
+type LoadedBug struct {
+	*bug.Snapshot
+}
+
+func NewLoadedBug(snap *bug.Snapshot) *LoadedBug {
+	return &LoadedBug{Snapshot: snap}
+}
+
+func (l *LoadedBug) LastEdit() time.Time {
+	return l.Snapshot.LastEditTime()
+}
+
+func (l *LoadedBug) Status() bug.Status {
+	return l.Snapshot.Status
+}
+
+func (l *LoadedBug) Title() string {
+	return l.Snapshot.Title
+}
+
+func (l *LoadedBug) Comments() ([]bug.Comment, error) {
+	return l.Snapshot.Comments, nil
+}
+
+func (l *LoadedBug) Labels() []bug.Label {
+	return l.Snapshot.Labels
+}
+
+func (l *LoadedBug) Author() (IdentityWrapper, error) {
+	return NewLoadedIdentity(l.Snapshot.Author), nil
+}
+
+func (l *LoadedBug) Actors() ([]IdentityWrapper, error) {
+	res := make([]IdentityWrapper, len(l.Snapshot.Actors))
+	for i, actor := range l.Snapshot.Actors {
+		res[i] = NewLoadedIdentity(actor)
+	}
+	return res, nil
+}
+
+func (l *LoadedBug) Participants() ([]IdentityWrapper, error) {
+	res := make([]IdentityWrapper, len(l.Snapshot.Participants))
+	for i, participant := range l.Snapshot.Participants {
+		res[i] = NewLoadedIdentity(participant)
+	}
+	return res, nil
+}
+
+func (l *LoadedBug) CreatedAt() time.Time {
+	return l.Snapshot.CreatedAt
+}
+
+func (l *LoadedBug) Timeline() ([]bug.TimelineItem, error) {
+	return l.Snapshot.Timeline, nil
+}
+
+func (l *LoadedBug) Operations() ([]bug.Operation, error) {
+	return l.Snapshot.Operations, nil
+}
  @@ -0,0 +1,164 @@
+package models
+
+import (
+	"fmt"
+	"sync"
+
+	"github.com/MichaelMure/git-bug/cache"
+	"github.com/MichaelMure/git-bug/entity"
+	"github.com/MichaelMure/git-bug/identity"
+	"github.com/MichaelMure/git-bug/util/lamport"
+	"github.com/MichaelMure/git-bug/util/timestamp"
+)
+
+type IdentityWrapper interface {
+	Id() entity.Id
+	Name() string
+	Email() (string, error)
+	AvatarUrl() (string, error)
+	Keys() ([]*identity.Key, error)
+	ValidKeysAtTime(time lamport.Time) ([]*identity.Key, error)
+	DisplayName() string
+	IsProtected() (bool, error)
+	LastModificationLamport() (lamport.Time, error)
+	LastModification() (timestamp.Timestamp, error)
+}
+
+var _ IdentityWrapper = &LazyIdentity{}
+
+type LazyIdentity struct {
+	cache   *cache.RepoCache
+	excerpt *cache.IdentityExcerpt
+
+	mu sync.Mutex
+	id *cache.IdentityCache
+}
+
+func NewLazyIdentity(cache *cache.RepoCache, excerpt *cache.IdentityExcerpt) *LazyIdentity {
+	return &LazyIdentity{
+		cache:   cache,
+		excerpt: excerpt,
+	}
+}
+
+func (li *LazyIdentity) load() (*cache.IdentityCache, error) {
+	if li.id != nil {
+		return li.id, nil
+	}
+
+	li.mu.Lock()
+	defer li.mu.Unlock()
+
+	id, err := li.cache.ResolveIdentity(li.excerpt.Id)
+	if err != nil {
+		return nil, fmt.Errorf("cache: missing identity %v", li.excerpt.Id)
+	}
+	li.id = id
+	return id, nil
+}
+
+func (li *LazyIdentity) Id() entity.Id {
+	return li.excerpt.Id
+}
+
+func (li *LazyIdentity) Name() string {
+	return li.excerpt.Name
+}
+
+func (li *LazyIdentity) Email() (string, error) {
+	id, err := li.load()
+	if err != nil {
+		return "", err
+	}
+	return id.Email(), nil
+}
+
+func (li *LazyIdentity) AvatarUrl() (string, error) {
+	id, err := li.load()
+	if err != nil {
+		return "", err
+	}
+	return id.AvatarUrl(), nil
+}
+
+func (li *LazyIdentity) Keys() ([]*identity.Key, error) {
+	id, err := li.load()
+	if err != nil {
+		return nil, err
+	}
+	return id.Keys(), nil
+}
+
+func (li *LazyIdentity) ValidKeysAtTime(time lamport.Time) ([]*identity.Key, error) {
+	id, err := li.load()
+	if err != nil {
+		return nil, err
+	}
+	return id.ValidKeysAtTime(time), nil
+}
+
+func (li *LazyIdentity) DisplayName() string {
+	return li.excerpt.DisplayName()
+}
+
+func (li *LazyIdentity) IsProtected() (bool, error) {
+	id, err := li.load()
+	if err != nil {
+		return false, err
+	}
+	return id.IsProtected(), nil
+}
+
+func (li *LazyIdentity) LastModificationLamport() (lamport.Time, error) {
+	id, err := li.load()
+	if err != nil {
+		return 0, err
+	}
+	return id.LastModificationLamport(), nil
+}
+
+func (li *LazyIdentity) LastModification() (timestamp.Timestamp, error) {
+	id, err := li.load()
+	if err != nil {
+		return 0, err
+	}
+	return id.LastModification(), nil
+}
+
+var _ IdentityWrapper = &LoadedIdentity{}
+
+type LoadedIdentity struct {
+	identity.Interface
+}
+
+func NewLoadedIdentity(id identity.Interface) *LoadedIdentity {
+	return &LoadedIdentity{Interface: id}
+}
+
+func (l LoadedIdentity) Email() (string, error) {
+	return l.Interface.Email(), nil
+}
+
+func (l LoadedIdentity) AvatarUrl() (string, error) {
+	return l.Interface.AvatarUrl(), nil
+}
+
+func (l LoadedIdentity) Keys() ([]*identity.Key, error) {
+	return l.Interface.Keys(), nil
+}
+
+func (l LoadedIdentity) ValidKeysAtTime(time lamport.Time) ([]*identity.Key, error) {
+	return l.Interface.ValidKeysAtTime(time), nil
+}
+
+func (l LoadedIdentity) IsProtected() (bool, error) {
+	return l.Interface.IsProtected(), nil
+}
+
+func (l LoadedIdentity) LastModificationLamport() (lamport.Time, error) {
+	return l.Interface.LastModificationLamport(), nil
+}
+
+func (l LoadedIdentity) LastModification() (timestamp.Timestamp, error) {
+	return l.Interface.LastModification(), nil
+}
  @@ -2,32 +2,30 @@ package resolvers
 
 import (
 	"context"
-	"time"
 
 	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/graphql/connections"
 	"github.com/MichaelMure/git-bug/graphql/graph"
 	"github.com/MichaelMure/git-bug/graphql/models"
-	"github.com/MichaelMure/git-bug/identity"
 )
 
 var _ graph.BugResolver = &bugResolver{}
 
 type bugResolver struct{}
 
-func (bugResolver) ID(ctx context.Context, obj *bug.Snapshot) (string, error) {
+func (bugResolver) ID(_ context.Context, obj models.BugWrapper) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (bugResolver) HumanID(ctx context.Context, obj *bug.Snapshot) (string, error) {
+func (bugResolver) HumanID(_ context.Context, obj models.BugWrapper) (string, error) {
 	return obj.Id().Human(), nil
 }
 
-func (bugResolver) Status(ctx context.Context, obj *bug.Snapshot) (models.Status, error) {
-	return convertStatus(obj.Status)
+func (bugResolver) Status(_ context.Context, obj models.BugWrapper) (models.Status, error) {
+	return convertStatus(obj.Status())
 }
 
-func (bugResolver) Comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.CommentConnection, error) {
+func (bugResolver) Comments(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.CommentConnection, error) {
 	input := models.ConnectionInput{
 		Before: before,
 		After:  after,
@@ -55,10 +53,15 @@ func (bugResolver) Comments(ctx context.Context, obj *bug.Snapshot, after *strin
 		}, nil
 	}
 
-	return connections.CommentCon(obj.Comments, edger, conMaker, input)
+	comments, err := obj.Comments()
+	if err != nil {
+		return nil, err
+	}
+
+	return connections.CommentCon(comments, edger, conMaker, input)
 }
 
-func (bugResolver) Operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.OperationConnection, error) {
+func (bugResolver) Operations(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.OperationConnection, error) {
 	input := models.ConnectionInput{
 		Before: before,
 		After:  after,
@@ -82,10 +85,15 @@ func (bugResolver) Operations(ctx context.Context, obj *bug.Snapshot, after *str
 		}, nil
 	}
 
-	return connections.OperationCon(obj.Operations, edger, conMaker, input)
+	ops, err := obj.Operations()
+	if err != nil {
+		return nil, err
+	}
+
+	return connections.OperationCon(ops, edger, conMaker, input)
 }
 
-func (bugResolver) Timeline(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.TimelineItemConnection, error) {
+func (bugResolver) Timeline(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.TimelineItemConnection, error) {
 	input := models.ConnectionInput{
 		Before: before,
 		After:  after,
@@ -109,15 +117,15 @@ func (bugResolver) Timeline(ctx context.Context, obj *bug.Snapshot, after *strin
 		}, nil
 	}
 
-	return connections.TimelineItemCon(obj.Timeline, edger, conMaker, input)
-}
+	timeline, err := obj.Timeline()
+	if err != nil {
+		return nil, err
+	}
 
-func (bugResolver) LastEdit(ctx context.Context, obj *bug.Snapshot) (*time.Time, error) {
-	t := obj.LastEditTime()
-	return &t, nil
+	return connections.TimelineItemCon(timeline, edger, conMaker, input)
 }
 
-func (bugResolver) Actors(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) {
+func (bugResolver) Actors(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) {
 	input := models.ConnectionInput{
 		Before: before,
 		After:  after,
@@ -125,14 +133,14 @@ func (bugResolver) Actors(ctx context.Context, obj *bug.Snapshot, after *string,
 		Last:   last,
 	}
 
-	edger := func(actor identity.Interface, offset int) connections.Edge {
+	edger := func(actor models.IdentityWrapper, offset int) connections.Edge {
 		return models.IdentityEdge{
 			Node:   actor,
 			Cursor: connections.OffsetToCursor(offset),
 		}
 	}
 
-	conMaker := func(edges []*models.IdentityEdge, nodes []identity.Interface, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) {
+	conMaker := func(edges []*models.IdentityEdge, nodes []models.IdentityWrapper, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) {
 		return &models.IdentityConnection{
 			Edges:      edges,
 			Nodes:      nodes,
@@ -141,10 +149,15 @@ func (bugResolver) Actors(ctx context.Context, obj *bug.Snapshot, after *string,
 		}, nil
 	}
 
-	return connections.IdentityCon(obj.Actors, edger, conMaker, input)
+	actors, err := obj.Actors()
+	if err != nil {
+		return nil, err
+	}
+
+	return connections.IdentityCon(actors, edger, conMaker, input)
 }
 
-func (bugResolver) Participants(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) {
+func (bugResolver) Participants(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) {
 	input := models.ConnectionInput{
 		Before: before,
 		After:  after,
@@ -152,14 +165,14 @@ func (bugResolver) Participants(ctx context.Context, obj *bug.Snapshot, after *s
 		Last:   last,
 	}
 
-	edger := func(participant identity.Interface, offset int) connections.Edge {
+	edger := func(participant models.IdentityWrapper, offset int) connections.Edge {
 		return models.IdentityEdge{
 			Node:   participant,
 			Cursor: connections.OffsetToCursor(offset),
 		}
 	}
 
-	conMaker := func(edges []*models.IdentityEdge, nodes []identity.Interface, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) {
+	conMaker := func(edges []*models.IdentityEdge, nodes []models.IdentityWrapper, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) {
 		return &models.IdentityConnection{
 			Edges:      edges,
 			Nodes:      nodes,
@@ -168,5 +181,10 @@ func (bugResolver) Participants(ctx context.Context, obj *bug.Snapshot, after *s
 		}, nil
 	}
 
-	return connections.IdentityCon(obj.Participants, edger, conMaker, input)
+	participants, err := obj.Participants()
+	if err != nil {
+		return nil, err
+	}
+
+	return connections.IdentityCon(participants, edger, conMaker, input)
 }
  @@ -11,14 +11,14 @@ var _ graph.ColorResolver = &colorResolver{}
 
 type colorResolver struct{}
 
-func (colorResolver) R(ctx context.Context, obj *color.RGBA) (int, error) {
+func (colorResolver) R(_ context.Context, obj *color.RGBA) (int, error) {
 	return int(obj.R), nil
 }
 
-func (colorResolver) G(ctx context.Context, obj *color.RGBA) (int, error) {
+func (colorResolver) G(_ context.Context, obj *color.RGBA) (int, error) {
 	return int(obj.G), nil
 }
 
-func (colorResolver) B(ctx context.Context, obj *color.RGBA) (int, error) {
+func (colorResolver) B(_ context.Context, obj *color.RGBA) (int, error) {
 	return int(obj.B), nil
 }
  @@ -0,0 +1,17 @@
+package resolvers
+
+import (
+	"context"
+
+	"github.com/MichaelMure/git-bug/bug"
+	"github.com/MichaelMure/git-bug/graphql/graph"
+	"github.com/MichaelMure/git-bug/graphql/models"
+)
+
+var _ graph.CommentResolver = &commentResolver{}
+
+type commentResolver struct{}
+
+func (c commentResolver) Author(_ context.Context, obj *bug.Comment) (models.IdentityWrapper, error) {
+	return models.NewLoadedIdentity(obj.Author), nil
+}
  @@ -4,18 +4,18 @@ import (
 	"context"
 
 	"github.com/MichaelMure/git-bug/graphql/graph"
-	"github.com/MichaelMure/git-bug/identity"
+	"github.com/MichaelMure/git-bug/graphql/models"
 )
 
 var _ graph.IdentityResolver = &identityResolver{}
 
 type identityResolver struct{}
 
-func (identityResolver) ID(ctx context.Context, obj identity.Interface) (string, error) {
+func (identityResolver) ID(ctx context.Context, obj models.IdentityWrapper) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (r identityResolver) HumanID(ctx context.Context, obj identity.Interface) (string, error) {
+func (r identityResolver) HumanID(ctx context.Context, obj models.IdentityWrapper) (string, error) {
 	return obj.Id().Human(), nil
 
 }
  @@ -23,7 +23,7 @@ func (r mutationResolver) getRepo(ref *string) (*cache.RepoCache, error) {
 	return r.cache.DefaultRepo()
 }
 
-func (r mutationResolver) NewBug(ctx context.Context, input models.NewBugInput) (*models.NewBugPayload, error) {
+func (r mutationResolver) NewBug(_ context.Context, input models.NewBugInput) (*models.NewBugPayload, error) {
 	repo, err := r.getRepo(input.RepoRef)
 	if err != nil {
 		return nil, err
@@ -36,12 +36,12 @@ func (r mutationResolver) NewBug(ctx context.Context, input models.NewBugInput)
 
 	return &models.NewBugPayload{
 		ClientMutationID: input.ClientMutationID,
-		Bug:              b.Snapshot(),
+		Bug:              models.NewLoadedBug(b.Snapshot()),
 		Operation:        op,
 	}, nil
 }
 
-func (r mutationResolver) AddComment(ctx context.Context, input models.AddCommentInput) (*models.AddCommentPayload, error) {
+func (r mutationResolver) AddComment(_ context.Context, input models.AddCommentInput) (*models.AddCommentPayload, error) {
 	repo, err := r.getRepo(input.RepoRef)
 	if err != nil {
 		return nil, err
@@ -59,12 +59,12 @@ func (r mutationResolver) AddComment(ctx context.Context, input models.AddCommen
 
 	return &models.AddCommentPayload{
 		ClientMutationID: input.ClientMutationID,
-		Bug:              b.Snapshot(),
+		Bug:              models.NewLoadedBug(b.Snapshot()),
 		Operation:        op,
 	}, nil
 }
 
-func (r mutationResolver) ChangeLabels(ctx context.Context, input *models.ChangeLabelInput) (*models.ChangeLabelPayload, error) {
+func (r mutationResolver) ChangeLabels(_ context.Context, input *models.ChangeLabelInput) (*models.ChangeLabelPayload, error) {
 	repo, err := r.getRepo(input.RepoRef)
 	if err != nil {
 		return nil, err
@@ -87,13 +87,13 @@ func (r mutationResolver) ChangeLabels(ctx context.Context, input *models.Change
 
 	return &models.ChangeLabelPayload{
 		ClientMutationID: input.ClientMutationID,
-		Bug:              b.Snapshot(),
+		Bug:              models.NewLoadedBug(b.Snapshot()),
 		Operation:        op,
 		Results:          resultsPtr,
 	}, nil
 }
 
-func (r mutationResolver) OpenBug(ctx context.Context, input models.OpenBugInput) (*models.OpenBugPayload, error) {
+func (r mutationResolver) OpenBug(_ context.Context, input models.OpenBugInput) (*models.OpenBugPayload, error) {
 	repo, err := r.getRepo(input.RepoRef)
 	if err != nil {
 		return nil, err
@@ -111,12 +111,12 @@ func (r mutationResolver) OpenBug(ctx context.Context, input models.OpenBugInput
 
 	return &models.OpenBugPayload{
 		ClientMutationID: input.ClientMutationID,
-		Bug:              b.Snapshot(),
+		Bug:              models.NewLoadedBug(b.Snapshot()),
 		Operation:        op,
 	}, nil
 }
 
-func (r mutationResolver) CloseBug(ctx context.Context, input models.CloseBugInput) (*models.CloseBugPayload, error) {
+func (r mutationResolver) CloseBug(_ context.Context, input models.CloseBugInput) (*models.CloseBugPayload, error) {
 	repo, err := r.getRepo(input.RepoRef)
 	if err != nil {
 		return nil, err
@@ -134,12 +134,12 @@ func (r mutationResolver) CloseBug(ctx context.Context, input models.CloseBugInp
 
 	return &models.CloseBugPayload{
 		ClientMutationID: input.ClientMutationID,
-		Bug:              b.Snapshot(),
+		Bug:              models.NewLoadedBug(b.Snapshot()),
 		Operation:        op,
 	}, nil
 }
 
-func (r mutationResolver) SetTitle(ctx context.Context, input models.SetTitleInput) (*models.SetTitlePayload, error) {
+func (r mutationResolver) SetTitle(_ context.Context, input models.SetTitleInput) (*models.SetTitlePayload, error) {
 	repo, err := r.getRepo(input.RepoRef)
 	if err != nil {
 		return nil, err
@@ -157,12 +157,12 @@ func (r mutationResolver) SetTitle(ctx context.Context, input models.SetTitleInp
 
 	return &models.SetTitlePayload{
 		ClientMutationID: input.ClientMutationID,
-		Bug:              b.Snapshot(),
+		Bug:              models.NewLoadedBug(b.Snapshot()),
 		Operation:        op,
 	}, nil
 }
 
-func (r mutationResolver) Commit(ctx context.Context, input models.CommitInput) (*models.CommitPayload, error) {
+func (r mutationResolver) Commit(_ context.Context, input models.CommitInput) (*models.CommitPayload, error) {
 	repo, err := r.getRepo(input.RepoRef)
 	if err != nil {
 		return nil, err
@@ -180,11 +180,11 @@ func (r mutationResolver) Commit(ctx context.Context, input models.CommitInput)
 
 	return &models.CommitPayload{
 		ClientMutationID: input.ClientMutationID,
-		Bug:              b.Snapshot(),
+		Bug:              models.NewLoadedBug(b.Snapshot()),
 	}, nil
 }
 
-func (r mutationResolver) CommitAsNeeded(ctx context.Context, input models.CommitAsNeededInput) (*models.CommitAsNeededPayload, error) {
+func (r mutationResolver) CommitAsNeeded(_ context.Context, input models.CommitAsNeededInput) (*models.CommitAsNeededPayload, error) {
 	repo, err := r.getRepo(input.RepoRef)
 	if err != nil {
 		return nil, err
@@ -202,6 +202,6 @@ func (r mutationResolver) CommitAsNeeded(ctx context.Context, input models.Commi
 
 	return &models.CommitAsNeededPayload{
 		ClientMutationID: input.ClientMutationID,
-		Bug:              b.Snapshot(),
+		Bug:              models.NewLoadedBug(b.Snapshot()),
 	}, nil
 }
  @@ -14,11 +14,15 @@ var _ graph.CreateOperationResolver = createOperationResolver{}
 
 type createOperationResolver struct{}
 
-func (createOperationResolver) ID(ctx context.Context, obj *bug.CreateOperation) (string, error) {
+func (createOperationResolver) ID(_ context.Context, obj *bug.CreateOperation) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (createOperationResolver) Date(ctx context.Context, obj *bug.CreateOperation) (*time.Time, error) {
+func (createOperationResolver) Author(_ context.Context, obj *bug.CreateOperation) (models.IdentityWrapper, error) {
+	return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (createOperationResolver) Date(_ context.Context, obj *bug.CreateOperation) (*time.Time, error) {
 	t := obj.Time()
 	return &t, nil
 }
@@ -27,11 +31,15 @@ var _ graph.AddCommentOperationResolver = addCommentOperationResolver{}
 
 type addCommentOperationResolver struct{}
 
-func (addCommentOperationResolver) ID(ctx context.Context, obj *bug.AddCommentOperation) (string, error) {
+func (addCommentOperationResolver) ID(_ context.Context, obj *bug.AddCommentOperation) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (addCommentOperationResolver) Date(ctx context.Context, obj *bug.AddCommentOperation) (*time.Time, error) {
+func (addCommentOperationResolver) Author(_ context.Context, obj *bug.AddCommentOperation) (models.IdentityWrapper, error) {
+	return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (addCommentOperationResolver) Date(_ context.Context, obj *bug.AddCommentOperation) (*time.Time, error) {
 	t := obj.Time()
 	return &t, nil
 }
@@ -40,15 +48,19 @@ var _ graph.EditCommentOperationResolver = editCommentOperationResolver{}
 
 type editCommentOperationResolver struct{}
 
-func (editCommentOperationResolver) ID(ctx context.Context, obj *bug.EditCommentOperation) (string, error) {
+func (editCommentOperationResolver) ID(_ context.Context, obj *bug.EditCommentOperation) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (editCommentOperationResolver) Target(ctx context.Context, obj *bug.EditCommentOperation) (string, error) {
+func (editCommentOperationResolver) Target(_ context.Context, obj *bug.EditCommentOperation) (string, error) {
 	return obj.Target.String(), nil
 }
 
-func (editCommentOperationResolver) Date(ctx context.Context, obj *bug.EditCommentOperation) (*time.Time, error) {
+func (editCommentOperationResolver) Author(_ context.Context, obj *bug.EditCommentOperation) (models.IdentityWrapper, error) {
+	return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (editCommentOperationResolver) Date(_ context.Context, obj *bug.EditCommentOperation) (*time.Time, error) {
 	t := obj.Time()
 	return &t, nil
 }
@@ -57,11 +69,15 @@ var _ graph.LabelChangeOperationResolver = labelChangeOperationResolver{}
 
 type labelChangeOperationResolver struct{}
 
-func (labelChangeOperationResolver) ID(ctx context.Context, obj *bug.LabelChangeOperation) (string, error) {
+func (labelChangeOperationResolver) ID(_ context.Context, obj *bug.LabelChangeOperation) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (labelChangeOperationResolver) Date(ctx context.Context, obj *bug.LabelChangeOperation) (*time.Time, error) {
+func (labelChangeOperationResolver) Author(_ context.Context, obj *bug.LabelChangeOperation) (models.IdentityWrapper, error) {
+	return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (labelChangeOperationResolver) Date(_ context.Context, obj *bug.LabelChangeOperation) (*time.Time, error) {
 	t := obj.Time()
 	return &t, nil
 }
@@ -70,16 +86,20 @@ var _ graph.SetStatusOperationResolver = setStatusOperationResolver{}
 
 type setStatusOperationResolver struct{}
 
-func (setStatusOperationResolver) ID(ctx context.Context, obj *bug.SetStatusOperation) (string, error) {
+func (setStatusOperationResolver) ID(_ context.Context, obj *bug.SetStatusOperation) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (setStatusOperationResolver) Date(ctx context.Context, obj *bug.SetStatusOperation) (*time.Time, error) {
+func (setStatusOperationResolver) Author(_ context.Context, obj *bug.SetStatusOperation) (models.IdentityWrapper, error) {
+	return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (setStatusOperationResolver) Date(_ context.Context, obj *bug.SetStatusOperation) (*time.Time, error) {
 	t := obj.Time()
 	return &t, nil
 }
 
-func (setStatusOperationResolver) Status(ctx context.Context, obj *bug.SetStatusOperation) (models.Status, error) {
+func (setStatusOperationResolver) Status(_ context.Context, obj *bug.SetStatusOperation) (models.Status, error) {
 	return convertStatus(obj.Status)
 }
 
@@ -87,11 +107,15 @@ var _ graph.SetTitleOperationResolver = setTitleOperationResolver{}
 
 type setTitleOperationResolver struct{}
 
-func (setTitleOperationResolver) ID(ctx context.Context, obj *bug.SetTitleOperation) (string, error) {
+func (setTitleOperationResolver) ID(_ context.Context, obj *bug.SetTitleOperation) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (setTitleOperationResolver) Date(ctx context.Context, obj *bug.SetTitleOperation) (*time.Time, error) {
+func (setTitleOperationResolver) Author(_ context.Context, obj *bug.SetTitleOperation) (models.IdentityWrapper, error) {
+	return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (setTitleOperationResolver) Date(_ context.Context, obj *bug.SetTitleOperation) (*time.Time, error) {
 	t := obj.Time()
 	return &t, nil
 }
  @@ -14,7 +14,7 @@ type rootQueryResolver struct {
 	cache *cache.MultiRepoCache
 }
 
-func (r rootQueryResolver) DefaultRepository(ctx context.Context) (*models.Repository, error) {
+func (r rootQueryResolver) DefaultRepository(_ context.Context) (*models.Repository, error) {
 	repo, err := r.cache.DefaultRepo()
 
 	if err != nil {
@@ -27,7 +27,7 @@ func (r rootQueryResolver) DefaultRepository(ctx context.Context) (*models.Repos
 	}, nil
 }
 
-func (r rootQueryResolver) Repository(ctx context.Context, ref string) (*models.Repository, error) {
+func (r rootQueryResolver) Repository(_ context.Context, ref string) (*models.Repository, error) {
 	repo, err := r.cache.ResolveRepo(ref)
 
 	if err != nil {
  @@ -9,14 +9,13 @@ import (
 	"github.com/MichaelMure/git-bug/graphql/connections"
 	"github.com/MichaelMure/git-bug/graphql/graph"
 	"github.com/MichaelMure/git-bug/graphql/models"
-	"github.com/MichaelMure/git-bug/identity"
 )
 
 var _ graph.RepositoryResolver = &repoResolver{}
 
 type repoResolver struct{}
 
-func (repoResolver) AllBugs(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, queryStr *string) (*models.BugConnection, error) {
+func (repoResolver) AllBugs(_ context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, queryStr *string) (*models.BugConnection, error) {
 	input := models.ConnectionInput{
 		Before: before,
 		After:  after,
@@ -49,22 +48,21 @@ func (repoResolver) AllBugs(ctx context.Context, obj *models.Repository, after *
 	// The conMaker will finally load and compile bugs from git to replace the selected edges
 	conMaker := func(lazyBugEdges []*connections.LazyBugEdge, lazyNode []entity.Id, info *models.PageInfo, totalCount int) (*models.BugConnection, error) {
 		edges := make([]*models.BugEdge, len(lazyBugEdges))
-		nodes := make([]*bug.Snapshot, len(lazyBugEdges))
+		nodes := make([]models.BugWrapper, len(lazyBugEdges))
 
 		for i, lazyBugEdge := range lazyBugEdges {
-			b, err := obj.Repo.ResolveBug(lazyBugEdge.Id)
-
+			excerpt, err := obj.Repo.ResolveBugExcerpt(lazyBugEdge.Id)
 			if err != nil {
 				return nil, err
 			}
 
-			snap := b.Snapshot()
+			b := models.NewLazyBug(obj.Repo, excerpt)
 
 			edges[i] = &models.BugEdge{
 				Cursor: lazyBugEdge.Cursor,
-				Node:   snap,
+				Node:   b,
 			}
-			nodes[i] = snap
+			nodes[i] = b
 		}
 
 		return &models.BugConnection{
@@ -78,17 +76,16 @@ func (repoResolver) AllBugs(ctx context.Context, obj *models.Repository, after *
 	return connections.LazyBugCon(source, edger, conMaker, input)
 }
 
-func (repoResolver) Bug(ctx context.Context, obj *models.Repository, prefix string) (*bug.Snapshot, error) {
-	b, err := obj.Repo.ResolveBugPrefix(prefix)
-
+func (repoResolver) Bug(_ context.Context, obj *models.Repository, prefix string) (models.BugWrapper, error) {
+	excerpt, err := obj.Repo.ResolveBugExcerptPrefix(prefix)
 	if err != nil {
 		return nil, err
 	}
 
-	return b.Snapshot(), nil
+	return models.NewLazyBug(obj.Repo, excerpt), nil
 }
 
-func (repoResolver) AllIdentities(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) {
+func (repoResolver) AllIdentities(_ context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) {
 	input := models.ConnectionInput{
 		Before: before,
 		After:  after,
@@ -110,22 +107,21 @@ func (repoResolver) AllIdentities(ctx context.Context, obj *models.Repository, a
 	// The conMaker will finally load and compile identities from git to replace the selected edges
 	conMaker := func(lazyIdentityEdges []*connections.LazyIdentityEdge, lazyNode []entity.Id, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) {
 		edges := make([]*models.IdentityEdge, len(lazyIdentityEdges))
-		nodes := make([]identity.Interface, len(lazyIdentityEdges))
+		nodes := make([]models.IdentityWrapper, len(lazyIdentityEdges))
 
 		for k, lazyIdentityEdge := range lazyIdentityEdges {
-			i, err := obj.Repo.ResolveIdentity(lazyIdentityEdge.Id)
-
+			excerpt, err := obj.Repo.ResolveIdentityExcerpt(lazyIdentityEdge.Id)
 			if err != nil {
 				return nil, err
 			}
 
-			ii := identity.Interface(i.Identity)
+			i := models.NewLazyIdentity(obj.Repo, excerpt)
 
 			edges[k] = &models.IdentityEdge{
 				Cursor: lazyIdentityEdge.Cursor,
-				Node:   i.Identity,
+				Node:   i,
 			}
-			nodes[k] = ii
+			nodes[k] = i
 		}
 
 		return &models.IdentityConnection{
@@ -139,27 +135,25 @@ func (repoResolver) AllIdentities(ctx context.Context, obj *models.Repository, a
 	return connections.LazyIdentityCon(source, edger, conMaker, input)
 }
 
-func (repoResolver) Identity(ctx context.Context, obj *models.Repository, prefix string) (identity.Interface, error) {
-	i, err := obj.Repo.ResolveIdentityPrefix(prefix)
-
+func (repoResolver) Identity(_ context.Context, obj *models.Repository, prefix string) (models.IdentityWrapper, error) {
+	excerpt, err := obj.Repo.ResolveIdentityExcerptPrefix(prefix)
 	if err != nil {
 		return nil, err
 	}
 
-	return i.Identity, nil
+	return models.NewLazyIdentity(obj.Repo, excerpt), nil
 }
 
-func (repoResolver) UserIdentity(ctx context.Context, obj *models.Repository) (identity.Interface, error) {
-	i, err := obj.Repo.GetUserIdentity()
-
+func (repoResolver) UserIdentity(_ context.Context, obj *models.Repository) (models.IdentityWrapper, error) {
+	excerpt, err := obj.Repo.GetUserIdentityExcerpt()
 	if err != nil {
 		return nil, err
 	}
 
-	return i.Identity, nil
+	return models.NewLazyIdentity(obj.Repo, excerpt), nil
 }
 
-func (resolver repoResolver) ValidLabels(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.LabelConnection, error) {
+func (resolver repoResolver) ValidLabels(_ context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.LabelConnection, error) {
 	input := models.ConnectionInput{
 		Before: before,
 		After:  after,
  @@ -42,6 +42,10 @@ func (RootResolver) Color() graph.ColorResolver {
 	return &colorResolver{}
 }
 
+func (r RootResolver) Comment() graph.CommentResolver {
+	return &commentResolver{}
+}
+
 func (RootResolver) Label() graph.LabelResolver {
 	return &labelResolver{}
 }
  @@ -13,7 +13,7 @@ var _ graph.CommentHistoryStepResolver = commentHistoryStepResolver{}
 
 type commentHistoryStepResolver struct{}
 
-func (commentHistoryStepResolver) Date(ctx context.Context, obj *bug.CommentHistoryStep) (*time.Time, error) {
+func (commentHistoryStepResolver) Date(_ context.Context, obj *bug.CommentHistoryStep) (*time.Time, error) {
 	t := obj.UnixTime.Time()
 	return &t, nil
 }
@@ -22,16 +22,20 @@ var _ graph.AddCommentTimelineItemResolver = addCommentTimelineItemResolver{}
 
 type addCommentTimelineItemResolver struct{}
 
-func (addCommentTimelineItemResolver) ID(ctx context.Context, obj *bug.AddCommentTimelineItem) (string, error) {
+func (addCommentTimelineItemResolver) ID(_ context.Context, obj *bug.AddCommentTimelineItem) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (addCommentTimelineItemResolver) CreatedAt(ctx context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error) {
+func (addCommentTimelineItemResolver) Author(_ context.Context, obj *bug.AddCommentTimelineItem) (models.IdentityWrapper, error) {
+	return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (addCommentTimelineItemResolver) CreatedAt(_ context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error) {
 	t := obj.CreatedAt.Time()
 	return &t, nil
 }
 
-func (addCommentTimelineItemResolver) LastEdit(ctx context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error) {
+func (addCommentTimelineItemResolver) LastEdit(_ context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error) {
 	t := obj.LastEdit.Time()
 	return &t, nil
 }
@@ -40,16 +44,20 @@ var _ graph.CreateTimelineItemResolver = createTimelineItemResolver{}
 
 type createTimelineItemResolver struct{}
 
-func (createTimelineItemResolver) ID(ctx context.Context, obj *bug.CreateTimelineItem) (string, error) {
+func (createTimelineItemResolver) ID(_ context.Context, obj *bug.CreateTimelineItem) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (createTimelineItemResolver) CreatedAt(ctx context.Context, obj *bug.CreateTimelineItem) (*time.Time, error) {
+func (r createTimelineItemResolver) Author(_ context.Context, obj *bug.CreateTimelineItem) (models.IdentityWrapper, error) {
+	return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (createTimelineItemResolver) CreatedAt(_ context.Context, obj *bug.CreateTimelineItem) (*time.Time, error) {
 	t := obj.CreatedAt.Time()
 	return &t, nil
 }
 
-func (createTimelineItemResolver) LastEdit(ctx context.Context, obj *bug.CreateTimelineItem) (*time.Time, error) {
+func (createTimelineItemResolver) LastEdit(_ context.Context, obj *bug.CreateTimelineItem) (*time.Time, error) {
 	t := obj.LastEdit.Time()
 	return &t, nil
 }
@@ -58,11 +66,15 @@ var _ graph.LabelChangeTimelineItemResolver = labelChangeTimelineItem{}
 
 type labelChangeTimelineItem struct{}
 
-func (labelChangeTimelineItem) ID(ctx context.Context, obj *bug.LabelChangeTimelineItem) (string, error) {
+func (labelChangeTimelineItem) ID(_ context.Context, obj *bug.LabelChangeTimelineItem) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (labelChangeTimelineItem) Date(ctx context.Context, obj *bug.LabelChangeTimelineItem) (*time.Time, error) {
+func (i labelChangeTimelineItem) Author(_ context.Context, obj *bug.LabelChangeTimelineItem) (models.IdentityWrapper, error) {
+	return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (labelChangeTimelineItem) Date(_ context.Context, obj *bug.LabelChangeTimelineItem) (*time.Time, error) {
 	t := obj.UnixTime.Time()
 	return &t, nil
 }
@@ -71,16 +83,20 @@ var _ graph.SetStatusTimelineItemResolver = setStatusTimelineItem{}
 
 type setStatusTimelineItem struct{}
 
-func (setStatusTimelineItem) ID(ctx context.Context, obj *bug.SetStatusTimelineItem) (string, error) {
+func (setStatusTimelineItem) ID(_ context.Context, obj *bug.SetStatusTimelineItem) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (setStatusTimelineItem) Date(ctx context.Context, obj *bug.SetStatusTimelineItem) (*time.Time, error) {
+func (i setStatusTimelineItem) Author(_ context.Context, obj *bug.SetStatusTimelineItem) (models.IdentityWrapper, error) {
+	return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (setStatusTimelineItem) Date(_ context.Context, obj *bug.SetStatusTimelineItem) (*time.Time, error) {
 	t := obj.UnixTime.Time()
 	return &t, nil
 }
 
-func (setStatusTimelineItem) Status(ctx context.Context, obj *bug.SetStatusTimelineItem) (models.Status, error) {
+func (setStatusTimelineItem) Status(_ context.Context, obj *bug.SetStatusTimelineItem) (models.Status, error) {
 	return convertStatus(obj.Status)
 }
 
@@ -88,11 +104,15 @@ var _ graph.SetTitleTimelineItemResolver = setTitleTimelineItem{}
 
 type setTitleTimelineItem struct{}
 
-func (setTitleTimelineItem) ID(ctx context.Context, obj *bug.SetTitleTimelineItem) (string, error) {
+func (setTitleTimelineItem) ID(_ context.Context, obj *bug.SetTitleTimelineItem) (string, error) {
 	return obj.Id().String(), nil
 }
 
-func (setTitleTimelineItem) Date(ctx context.Context, obj *bug.SetTitleTimelineItem) (*time.Time, error) {
+func (i setTitleTimelineItem) Author(_ context.Context, obj *bug.SetTitleTimelineItem) (models.IdentityWrapper, error) {
+	return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (setTitleTimelineItem) Date(_ context.Context, obj *bug.SetTitleTimelineItem) (*time.Time, error) {
 	t := obj.UnixTime.Time()
 	return &t, nil
 }
  @@ -227,17 +227,35 @@ func SetUserIdentity(repo repository.RepoConfig, identity *Identity) error {
 
 // GetUserIdentity read the current user identity, set with a git config entry
 func GetUserIdentity(repo repository.Repo) (*Identity, error) {
-	configs, err := repo.LocalConfig().ReadAll(identityConfigKey)
+	id, err := GetUserIdentityId(repo)
 	if err != nil {
 		return nil, err
 	}
 
+	i, err := ReadLocal(repo, id)
+	if err == ErrIdentityNotExist {
+		innerErr := repo.LocalConfig().RemoveAll(identityConfigKey)
+		if innerErr != nil {
+			_, _ = fmt.Fprintln(os.Stderr, errors.Wrap(innerErr, "can't clear user identity").Error())
+		}
+		return nil, err
+	}
+
+	return i, nil
+}
+
+func GetUserIdentityId(repo repository.Repo) (entity.Id, error) {
+	configs, err := repo.LocalConfig().ReadAll(identityConfigKey)
+	if err != nil {
+		return entity.UnsetId, err
+	}
+
 	if len(configs) == 0 {
-		return nil, ErrNoIdentitySet
+		return entity.UnsetId, ErrNoIdentitySet
 	}
 
 	if len(configs) > 1 {
-		return nil, ErrMultipleIdentitiesSet
+		return entity.UnsetId, ErrMultipleIdentitiesSet
 	}
 
 	var id entity.Id
@@ -246,19 +264,10 @@ func GetUserIdentity(repo repository.Repo) (*Identity, error) {
 	}
 
 	if err := id.Validate(); err != nil {
-		return nil, err
-	}
-
-	i, err := ReadLocal(repo, id)
-	if err == ErrIdentityNotExist {
-		innerErr := repo.LocalConfig().RemoveAll(identityConfigKey)
-		if innerErr != nil {
-			_, _ = fmt.Fprintln(os.Stderr, errors.Wrap(innerErr, "can't clear user identity").Error())
-		}
-		return nil, err
+		return entity.UnsetId, err
 	}
 
-	return i, nil
+	return id, nil
 }
 
 // IsUserIdentitySet say if the user has set his identity