diff --git a/bridge/core/export.go b/bridge/core/export.go index 2bcf008706cb60455f6eea08cc4108861876c820..09566b62dc650e355729937721a1c55921fa914a 100644 --- a/bridge/core/export.go +++ b/bridge/core/export.go @@ -1,6 +1,10 @@ package core -import "fmt" +import ( + "fmt" + + "github.com/MichaelMure/git-bug/entity" +) type ExportEvent int @@ -21,7 +25,7 @@ const ( type ExportResult struct { Err error Event ExportEvent - ID string + ID entity.Id Reason string } @@ -46,14 +50,14 @@ func (er ExportResult) String() string { } } -func NewExportError(err error, reason string) ExportResult { +func NewExportError(err error, id entity.Id) ExportResult { return ExportResult{ - Err: err, - Reason: reason, + ID: id, + Err: err, } } -func NewExportNothing(id string, reason string) ExportResult { +func NewExportNothing(id entity.Id, reason string) ExportResult { return ExportResult{ ID: id, Reason: reason, @@ -61,42 +65,42 @@ func NewExportNothing(id string, reason string) ExportResult { } } -func NewExportBug(id string) ExportResult { +func NewExportBug(id entity.Id) ExportResult { return ExportResult{ ID: id, Event: ExportEventBug, } } -func NewExportComment(id string) ExportResult { +func NewExportComment(id entity.Id) ExportResult { return ExportResult{ ID: id, Event: ExportEventComment, } } -func NewExportCommentEdition(id string) ExportResult { +func NewExportCommentEdition(id entity.Id) ExportResult { return ExportResult{ ID: id, Event: ExportEventCommentEdition, } } -func NewExportStatusChange(id string) ExportResult { +func NewExportStatusChange(id entity.Id) ExportResult { return ExportResult{ ID: id, Event: ExportEventStatusChange, } } -func NewExportLabelChange(id string) ExportResult { +func NewExportLabelChange(id entity.Id) ExportResult { return ExportResult{ ID: id, Event: ExportEventLabelChange, } } -func NewExportTitleEdition(id string) ExportResult { +func NewExportTitleEdition(id entity.Id) ExportResult { return ExportResult{ ID: id, Event: ExportEventTitleEdition, diff --git a/bridge/github/export.go b/bridge/github/export.go index b4351bdb437490c3188f5c9464f3f0ac5a0dc3fb..34c883100c111986c5b37ac97305a8950d80fe52 100644 --- a/bridge/github/export.go +++ b/bridge/github/export.go @@ -17,7 +17,7 @@ import ( "github.com/MichaelMure/git-bug/bridge/core" "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/util/git" + "github.com/MichaelMure/git-bug/entity" ) var ( @@ -29,17 +29,17 @@ type githubExporter struct { conf core.Configuration // cache identities clients - identityClient map[string]*githubv4.Client + identityClient map[entity.Id]*githubv4.Client // map identities with their tokens - identityToken map[string]string + identityToken map[entity.Id]string // github repository ID repositoryID string // cache identifiers used to speed up exporting operations // cleared for each bug - cachedOperationIDs map[string]string + cachedOperationIDs map[entity.Id]string // cache labels used to speed up exporting labels events cachedLabels map[string]string @@ -49,16 +49,16 @@ type githubExporter struct { func (ge *githubExporter) Init(conf core.Configuration) error { ge.conf = conf //TODO: initialize with multiple tokens - ge.identityToken = make(map[string]string) - ge.identityClient = make(map[string]*githubv4.Client) - ge.cachedOperationIDs = make(map[string]string) + ge.identityToken = make(map[entity.Id]string) + ge.identityClient = make(map[entity.Id]*githubv4.Client) + ge.cachedOperationIDs = make(map[entity.Id]string) ge.cachedLabels = make(map[string]string) return nil } // getIdentityClient return a githubv4 API client configured with the access token of the given identity. // if no client were found it will initialize it from the known tokens map and cache it for next use -func (ge *githubExporter) getIdentityClient(id string) (*githubv4.Client, error) { +func (ge *githubExporter) getIdentityClient(id entity.Id) (*githubv4.Client, error) { client, ok := ge.identityClient[id] if ok { return client, nil @@ -103,7 +103,7 @@ func (ge *githubExporter) ExportAll(repo *cache.RepoCache, since time.Time) (<-c go func() { defer close(out) - var allIdentitiesIds []string + var allIdentitiesIds []entity.Id for id := range ge.identityToken { allIdentitiesIds = append(allIdentitiesIds, id) } @@ -113,7 +113,7 @@ func (ge *githubExporter) ExportAll(repo *cache.RepoCache, since time.Time) (<-c for _, id := range allBugsIds { b, err := repo.ResolveBug(id) if err != nil { - out <- core.NewExportError(err, id) + out <- core.NewExportError(errors.Wrap(err, "can't load bug"), id) return } @@ -144,7 +144,6 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan var bugGithubID string var bugGithubURL string - var bugCreationHash string // Special case: // if a user try to export a bug that is not already exported to Github (or imported @@ -167,7 +166,7 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan githubURL, ok := snapshot.GetCreateMetadata(keyGithubUrl) if !ok { // if we find github ID, github URL must be found too - err := fmt.Errorf("expected to find github issue URL") + err := fmt.Errorf("incomplete Github metadata: expected to find issue URL") out <- core.NewExportError(err, b.Id()) } @@ -209,15 +208,8 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan out <- core.NewExportBug(b.Id()) - hash, err := createOp.Hash() - if err != nil { - err := errors.Wrap(err, "comment hash") - out <- core.NewExportError(err, b.Id()) - return - } - // mark bug creation operation as exported - if err := markOperationAsExported(b, hash, id, url); err != nil { + if err := markOperationAsExported(b, createOp.Id(), id, url); err != nil { err := errors.Wrap(err, "marking operation as exported") out <- core.NewExportError(err, b.Id()) return @@ -235,17 +227,8 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan bugGithubURL = url } - // get createOp hash - hash, err := createOp.Hash() - if err != nil { - out <- core.NewExportError(err, b.Id()) - return - } - - bugCreationHash = hash.String() - // cache operation github id - ge.cachedOperationIDs[bugCreationHash] = bugGithubID + ge.cachedOperationIDs[createOp.Id()] = bugGithubID for _, op := range snapshot.Operations[1:] { // ignore SetMetadata operations @@ -253,26 +236,18 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan continue } - // get operation hash - hash, err := op.Hash() - if err != nil { - err := errors.Wrap(err, "operation hash") - out <- core.NewExportError(err, b.Id()) - return - } - // ignore operations already existing in github (due to import or export) // cache the ID of already exported or imported issues and events from Github if id, ok := op.GetMetadata(keyGithubId); ok { - ge.cachedOperationIDs[hash.String()] = id - out <- core.NewExportNothing(hash.String(), "already exported operation") + ge.cachedOperationIDs[op.Id()] = id + out <- core.NewExportNothing(op.Id(), "already exported operation") continue } opAuthor := op.GetAuthor() client, err := ge.getIdentityClient(opAuthor.Id()) if err != nil { - out <- core.NewExportNothing(hash.String(), "missing operation author token") + out <- core.NewExportNothing(op.Id(), "missing operation author token") continue } @@ -289,18 +264,17 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan return } - out <- core.NewExportComment(hash.String()) + out <- core.NewExportComment(op.Id()) // cache comment id - ge.cachedOperationIDs[hash.String()] = id + ge.cachedOperationIDs[op.Id()] = id case *bug.EditCommentOperation: opr := op.(*bug.EditCommentOperation) - targetHash := opr.Target.String() // Since github doesn't consider the issue body as a comment - if targetHash == bugCreationHash { + if opr.Target == createOp.Id() { // case bug creation operation: we need to edit the Github issue if err := updateGithubIssueBody(client, bugGithubID, opr.Message); err != nil { @@ -309,7 +283,7 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan return } - out <- core.NewExportCommentEdition(hash.String()) + out <- core.NewExportCommentEdition(op.Id()) id = bugGithubID url = bugGithubURL @@ -317,7 +291,7 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan } else { // case comment edition operation: we need to edit the Github comment - commentID, ok := ge.cachedOperationIDs[targetHash] + commentID, ok := ge.cachedOperationIDs[opr.Target] if !ok { panic("unexpected error: comment id not found") } @@ -329,7 +303,7 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan return } - out <- core.NewExportCommentEdition(hash.String()) + out <- core.NewExportCommentEdition(op.Id()) // use comment id/url instead of issue id/url id = eid @@ -344,7 +318,7 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan return } - out <- core.NewExportStatusChange(hash.String()) + out <- core.NewExportStatusChange(op.Id()) id = bugGithubID url = bugGithubURL @@ -357,7 +331,7 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan return } - out <- core.NewExportTitleEdition(hash.String()) + out <- core.NewExportTitleEdition(op.Id()) id = bugGithubID url = bugGithubURL @@ -370,7 +344,7 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan return } - out <- core.NewExportLabelChange(hash.String()) + out <- core.NewExportLabelChange(op.Id()) id = bugGithubID url = bugGithubURL @@ -380,7 +354,7 @@ func (ge *githubExporter) exportBug(b *cache.BugCache, since time.Time, out chan } // mark operation as exported - if err := markOperationAsExported(b, hash, id, url); err != nil { + if err := markOperationAsExported(b, op.Id(), id, url); err != nil { err := errors.Wrap(err, "marking operation as exported") out <- core.NewExportError(err, b.Id()) return @@ -438,7 +412,7 @@ func getRepositoryNodeID(owner, project, token string) (string, error) { return aux.NodeID, nil } -func markOperationAsExported(b *cache.BugCache, target git.Hash, githubID, githubURL string) error { +func markOperationAsExported(b *cache.BugCache, target entity.Id, githubID, githubURL string) error { _, err := b.SetMetadata( target, map[string]string{ diff --git a/bridge/github/export_test.go b/bridge/github/export_test.go index 80660e774beb4ebf1b232d4c0f91ff965ee7f20b..107fe63b6e45456ef74780756c3e02c3e408707a 100644 --- a/bridge/github/export_test.go +++ b/bridge/github/export_test.go @@ -58,19 +58,13 @@ func testCases(t *testing.T, repo *cache.RepoCache, identity *cache.IdentityCach bugWithCommentEditions, createOp, err := repo.NewBug("bug with comments editions", "new bug") require.NoError(t, err) - createOpHash, err := createOp.Hash() - require.NoError(t, err) - - _, err = bugWithCommentEditions.EditComment(createOpHash, "first comment edited") + _, err = bugWithCommentEditions.EditComment(createOp.Id(), "first comment edited") require.NoError(t, err) commentOp, err := bugWithCommentEditions.AddComment("first comment") require.NoError(t, err) - commentOpHash, err := commentOp.Hash() - require.NoError(t, err) - - _, err = bugWithCommentEditions.EditComment(commentOpHash, "first comment edited") + _, err = bugWithCommentEditions.EditComment(commentOp.Id(), "first comment edited") require.NoError(t, err) // bug status changed diff --git a/bridge/github/import.go b/bridge/github/import.go index c162d70e2239caaa26ef4cfc5977057aeb00bde6..dcaf2d0590a00bcbf984fc652ea6c9b9c56d4bbb 100644 --- a/bridge/github/import.go +++ b/bridge/github/import.go @@ -10,8 +10,7 @@ import ( "github.com/MichaelMure/git-bug/bridge/core" "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/util/git" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/util/text" ) @@ -93,7 +92,7 @@ func (gi *githubImporter) ensureIssue(repo *cache.RepoCache, issue issueTimeline } // get issue edits - issueEdits := []userContentEdit{} + var issueEdits []userContentEdit for gi.iterator.NextIssueEdit() { issueEdits = append(issueEdits, gi.iterator.IssueEditValue()) } @@ -307,15 +306,14 @@ func (gi *githubImporter) ensureTimelineComment(repo *cache.RepoCache, b *cache. return err } - var target git.Hash - target, err = b.ResolveOperationWithMetadata(keyGithubId, parseId(item.Id)) + targetOpID, err := b.ResolveOperationWithMetadata(keyGithubId, parseId(item.Id)) if err != nil && err != cache.ErrNoMatchingOp { // real error return err } + // if no edits are given we create the comment if len(edits) == 0 { - // if comment doesn't exist if err == cache.ErrNoMatchingOp { cleanText, err := text.Cleanup(string(item.Body)) @@ -324,7 +322,7 @@ func (gi *githubImporter) ensureTimelineComment(repo *cache.RepoCache, b *cache. } // add comment operation - op, err := b.AddCommentRaw( + _, err = b.AddCommentRaw( author, item.CreatedAt.Unix(), cleanText, @@ -334,19 +332,11 @@ func (gi *githubImporter) ensureTimelineComment(repo *cache.RepoCache, b *cache. keyGithubUrl: parseId(item.Url.String()), }, ) - if err != nil { - return err - } - - // set hash - target, err = op.Hash() - if err != nil { - return err - } + return err } } else { for i, edit := range edits { - if i == 0 && target != "" { + if i == 0 && targetOpID != "" { // The first edit in the github result is the comment creation itself, we already have that continue } @@ -358,7 +348,7 @@ func (gi *githubImporter) ensureTimelineComment(repo *cache.RepoCache, b *cache. } // create comment when target is empty - if target == "" { + if targetOpID == "" { cleanText, err := text.Cleanup(string(*edit.Diff)) if err != nil { return err @@ -378,16 +368,13 @@ func (gi *githubImporter) ensureTimelineComment(repo *cache.RepoCache, b *cache. return err } - // set hash - target, err = op.Hash() - if err != nil { - return err - } + // set target for the nexr edit now that the comment is created + targetOpID = op.Id() continue } - err = gi.ensureCommentEdit(repo, b, target, edit) + err = gi.ensureCommentEdit(repo, b, targetOpID, edit) if err != nil { return err } @@ -396,7 +383,7 @@ func (gi *githubImporter) ensureTimelineComment(repo *cache.RepoCache, b *cache. return nil } -func (gi *githubImporter) ensureCommentEdit(repo *cache.RepoCache, b *cache.BugCache, target git.Hash, edit userContentEdit) error { +func (gi *githubImporter) ensureCommentEdit(repo *cache.RepoCache, b *cache.BugCache, target entity.Id, edit userContentEdit) error { _, err := b.ResolveOperationWithMetadata(keyGithubId, parseId(edit.Id)) if err == nil { // already imported @@ -458,7 +445,7 @@ func (gi *githubImporter) ensurePerson(repo *cache.RepoCache, actor *actor) (*ca if err == nil { return i, nil } - if _, ok := err.(identity.ErrMultipleMatch); ok { + if _, ok := err.(entity.ErrMultipleMatch); ok { return nil, err } @@ -501,7 +488,7 @@ func (gi *githubImporter) getGhost(repo *cache.RepoCache) (*cache.IdentityCache, if err == nil { return i, nil } - if _, ok := err.(identity.ErrMultipleMatch); ok { + if _, ok := err.(entity.ErrMultipleMatch); ok { return nil, err } diff --git a/bridge/gitlab/import.go b/bridge/gitlab/import.go index 360a585f683d678044475331cdd81df08c2943ab..e135b8bce79b9fae3005a4d939fddb6936e1cf9d 100644 --- a/bridge/gitlab/import.go +++ b/bridge/gitlab/import.go @@ -10,8 +10,7 @@ import ( "github.com/MichaelMure/git-bug/bridge/core" "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/util/git" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/util/text" ) @@ -130,9 +129,9 @@ func (gi *gitlabImporter) ensureIssue(repo *cache.RepoCache, issue *gitlab.Issue } func (gi *gitlabImporter) ensureNote(repo *cache.RepoCache, b *cache.BugCache, note *gitlab.Note) error { - id := parseID(note.ID) + gitlabID := parseID(note.ID) - hash, errResolve := b.ResolveOperationWithMetadata(keyGitlabId, id) + id, errResolve := b.ResolveOperationWithMetadata(keyGitlabId, gitlabID) if errResolve != nil && errResolve != cache.ErrNoMatchingOp { return errResolve } @@ -154,7 +153,7 @@ func (gi *gitlabImporter) ensureNote(repo *cache.RepoCache, b *cache.BugCache, n author, note.CreatedAt.Unix(), map[string]string{ - keyGitlabId: id, + keyGitlabId: gitlabID, }, ) return err @@ -168,7 +167,7 @@ func (gi *gitlabImporter) ensureNote(repo *cache.RepoCache, b *cache.BugCache, n author, note.CreatedAt.Unix(), map[string]string{ - keyGitlabId: id, + keyGitlabId: gitlabID, }, ) return err @@ -185,10 +184,10 @@ func (gi *gitlabImporter) ensureNote(repo *cache.RepoCache, b *cache.BugCache, n _, err = b.EditCommentRaw( author, note.UpdatedAt.Unix(), - git.Hash(firstComment.Id()), + firstComment.Id(), issue.Description, map[string]string{ - keyGitlabId: id, + keyGitlabId: gitlabID, }, ) @@ -211,7 +210,7 @@ func (gi *gitlabImporter) ensureNote(repo *cache.RepoCache, b *cache.BugCache, n cleanText, nil, map[string]string{ - keyGitlabId: id, + keyGitlabId: gitlabID, }, ) @@ -221,7 +220,7 @@ func (gi *gitlabImporter) ensureNote(repo *cache.RepoCache, b *cache.BugCache, n // if comment was already exported // search for last comment update - comment, err := b.Snapshot().SearchComment(hash) + comment, err := b.Snapshot().SearchComment(id) if err != nil { return err } @@ -232,7 +231,7 @@ func (gi *gitlabImporter) ensureNote(repo *cache.RepoCache, b *cache.BugCache, n _, err = b.EditCommentRaw( author, note.UpdatedAt.Unix(), - git.Hash(comment.Id()), + comment.Id(), cleanText, nil, ) @@ -253,7 +252,7 @@ func (gi *gitlabImporter) ensureNote(repo *cache.RepoCache, b *cache.BugCache, n note.CreatedAt.Unix(), body, map[string]string{ - keyGitlabId: id, + keyGitlabId: gitlabID, }, ) @@ -327,7 +326,7 @@ func (gi *gitlabImporter) ensurePerson(repo *cache.RepoCache, id int) (*cache.Id if err == nil { return i, nil } - if _, ok := err.(identity.ErrMultipleMatch); ok { + if _, ok := err.(entity.ErrMultipleMatch); ok { return nil, err } diff --git a/bridge/launchpad/import.go b/bridge/launchpad/import.go index 63101d9cb1a913e128fae334a0ca5b35a63456de..7ef11416c9f65742a858fda8b465160f8b3a312d 100644 --- a/bridge/launchpad/import.go +++ b/bridge/launchpad/import.go @@ -4,11 +4,12 @@ import ( "fmt" "time" + "github.com/pkg/errors" + "github.com/MichaelMure/git-bug/bridge/core" "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/identity" - "github.com/pkg/errors" + "github.com/MichaelMure/git-bug/entity" ) type launchpadImporter struct { @@ -29,7 +30,7 @@ func (li *launchpadImporter) ensurePerson(repo *cache.RepoCache, owner LPPerson) if err == nil { return i, nil } - if _, ok := err.(identity.ErrMultipleMatch); ok { + if _, ok := err.(entity.ErrMultipleMatch); ok { return nil, err } diff --git a/bug/bug.go b/bug/bug.go index 7e28954f1aec9f93b6380385113565d4014d3128..eb0337a421d926afd596ee5b09ce16581f129518 100644 --- a/bug/bug.go +++ b/bug/bug.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/git" @@ -26,20 +27,18 @@ const createClockEntryPattern = "create-clock-%d" const editClockEntryPrefix = "edit-clock-" const editClockEntryPattern = "edit-clock-%d" -const idLength = 40 -const humanIdLength = 7 - var ErrBugNotExist = errors.New("bug doesn't exist") -type ErrMultipleMatch struct { - Matching []string +func NewErrMultipleMatchBug(matching []entity.Id) *entity.ErrMultipleMatch { + return entity.NewErrMultipleMatch("bug", matching) } -func (e ErrMultipleMatch) Error() string { - return fmt.Sprintf("Multiple matching bug found:\n%s", strings.Join(e.Matching, "\n")) +func NewErrMultipleMatchOp(matching []entity.Id) *entity.ErrMultipleMatch { + return entity.NewErrMultipleMatch("operation", matching) } var _ Interface = &Bug{} +var _ entity.Interface = &Bug{} // Bug hold the data of a bug thread, organized in a way close to // how it will be persisted inside Git. This is the data structure @@ -53,7 +52,7 @@ type Bug struct { editTime lamport.Time // Id used as unique identifier - id string + id entity.Id lastCommit git.Hash rootPack git.Hash @@ -82,10 +81,10 @@ func FindLocalBug(repo repository.ClockedRepo, prefix string) (*Bug, error) { } // preallocate but empty - matching := make([]string, 0, 5) + matching := make([]entity.Id, 0, 5) for _, id := range ids { - if strings.HasPrefix(id, prefix) { + if id.HasPrefix(prefix) { matching = append(matching, id) } } @@ -95,15 +94,15 @@ func FindLocalBug(repo repository.ClockedRepo, prefix string) (*Bug, error) { } if len(matching) > 1 { - return nil, ErrMultipleMatch{Matching: matching} + return nil, NewErrMultipleMatchBug(matching) } return ReadLocalBug(repo, matching[0]) } // ReadLocalBug will read a local bug from its hash -func ReadLocalBug(repo repository.ClockedRepo, id string) (*Bug, error) { - ref := bugsRefPattern + id +func ReadLocalBug(repo repository.ClockedRepo, id entity.Id) (*Bug, error) { + ref := bugsRefPattern + id.String() return readBug(repo, ref) } @@ -116,10 +115,10 @@ func ReadRemoteBug(repo repository.ClockedRepo, remote string, id string) (*Bug, // readBug will read and parse a Bug from git func readBug(repo repository.ClockedRepo, ref string) (*Bug, error) { refSplit := strings.Split(ref, "/") - id := refSplit[len(refSplit)-1] + id := entity.Id(refSplit[len(refSplit)-1]) - if len(id) != idLength { - return nil, fmt.Errorf("invalid ref length") + if err := id.Validate(); err != nil { + return nil, errors.Wrap(err, "invalid ref ") } hashes, err := repo.ListCommits(ref) @@ -278,7 +277,7 @@ func readAllBugs(repo repository.ClockedRepo, refPrefix string) <-chan StreamedB } // ListLocalIds list all the available local bug ids -func ListLocalIds(repo repository.Repo) ([]string, error) { +func ListLocalIds(repo repository.Repo) ([]entity.Id, error) { refs, err := repo.ListRefs(bugsRefPattern) if err != nil { return nil, err @@ -287,12 +286,12 @@ func ListLocalIds(repo repository.Repo) ([]string, error) { return refsToIds(refs), nil } -func refsToIds(refs []string) []string { - ids := make([]string, len(refs)) +func refsToIds(refs []string) []entity.Id { + ids := make([]entity.Id, len(refs)) for i, ref := range refs { split := strings.Split(ref, "/") - ids[i] = split[len(split)-1] + ids[i] = entity.Id(split[len(split)-1]) } return ids @@ -325,8 +324,8 @@ func (bug *Bug) Validate() error { return fmt.Errorf("first operation should be a Create op") } - // The bug ID should be the hash of the first commit - if len(bug.packs) > 0 && string(bug.packs[0].commitHash) != bug.id { + // The bug Id should be the hash of the first commit + if len(bug.packs) > 0 && string(bug.packs[0].commitHash) != bug.id.String() { return fmt.Errorf("bug id should be the first commit hash") } @@ -456,7 +455,7 @@ func (bug *Bug) Commit(repo repository.ClockedRepo) error { // if it was the first commit, use the commit hash as bug id if bug.id == "" { - bug.id = string(hash) + bug.id = entity.Id(hash) } // Create or update the Git reference for this bug @@ -594,7 +593,7 @@ func (bug *Bug) Merge(repo repository.Repo, other Interface) (bool, error) { } // Update the git ref - err = repo.UpdateRef(bugsRefPattern+bug.id, bug.lastCommit) + err = repo.UpdateRef(bugsRefPattern+bug.id.String(), bug.lastCommit) if err != nil { return false, err } @@ -603,7 +602,7 @@ func (bug *Bug) Merge(repo repository.Repo, other Interface) (bool, error) { } // Id return the Bug identifier -func (bug *Bug) Id() string { +func (bug *Bug) Id() entity.Id { if bug.id == "" { // simply panic as it would be a coding error // (using an id of a bug not stored yet) @@ -612,16 +611,6 @@ func (bug *Bug) Id() string { return bug.id } -// HumanId return the Bug identifier truncated for human consumption -func (bug *Bug) HumanId() string { - return FormatHumanID(bug.Id()) -} - -func FormatHumanID(id string) string { - format := fmt.Sprintf("%%.%ds", humanIdLength) - return fmt.Sprintf(format, id) -} - // CreateLamportTime return the Lamport time of creation func (bug *Bug) CreateLamportTime() lamport.Time { return bug.createTime diff --git a/bug/bug_actions.go b/bug/bug_actions.go index b26d080f90f5e0e1ff4eb58069d922440e885164..cb0d0f7df9ce8d113fbcd2a92d96b8860a0561d7 100644 --- a/bug/bug_actions.go +++ b/bug/bug_actions.go @@ -65,8 +65,13 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeRes } for _, remoteRef := range remoteRefs { - refSplitted := strings.Split(remoteRef, "/") - id := refSplitted[len(refSplitted)-1] + refSplit := strings.Split(remoteRef, "/") + id := entity.Id(refSplit[len(refSplit)-1]) + + if err := id.Validate(); err != nil { + out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error()) + continue + } remoteBug, err := readBug(repo, remoteRef) @@ -81,7 +86,7 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeRes continue } - localRef := bugsRefPattern + remoteBug.Id() + localRef := bugsRefPattern + remoteBug.Id().String() localExist, err := repo.RefExist(localRef) if err != nil { diff --git a/bug/bug_test.go b/bug/bug_test.go index 4c3952ebe210499928414b0e60cf65f4c78ec85f..4e8a94409bca5cebb8dad736951cbc11da65ab12 100644 --- a/bug/bug_test.go +++ b/bug/bug_test.go @@ -103,7 +103,7 @@ func equivalentBug(t *testing.T, expected, actual *Bug) { for i := range expected.packs { for j := range expected.packs[i].Operations { - actual.packs[i].Operations[j].base().hash = expected.packs[i].Operations[j].base().hash + actual.packs[i].Operations[j].base().id = expected.packs[i].Operations[j].base().id } } diff --git a/bug/comment.go b/bug/comment.go index 5db0b18d8a11df3fe2170650149f52dea3807b0c..47c1ff0533f30def5dcd6075be7b762b880cc614 100644 --- a/bug/comment.go +++ b/bug/comment.go @@ -1,6 +1,7 @@ package bug import ( + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/timestamp" @@ -9,7 +10,7 @@ import ( // Comment represent a comment in a Bug type Comment struct { - id string + id entity.Id Author identity.Interface Message string Files []git.Hash @@ -20,7 +21,7 @@ type Comment struct { } // Id return the Comment identifier -func (c Comment) Id() string { +func (c Comment) Id() entity.Id { if c.id == "" { // simply panic as it would be a coding error // (using an id of an identity not stored yet) @@ -29,11 +30,6 @@ func (c Comment) Id() string { return c.id } -// HumanId return the Comment identifier truncated for human consumption -func (c Comment) HumanId() string { - return FormatHumanID(c.Id()) -} - // FormatTimeRel format the UnixTime of the comment for human consumption func (c Comment) FormatTimeRel() string { return humanize.Time(c.UnixTime.Time()) diff --git a/bug/interface.go b/bug/interface.go index 186c26fc09694a4c14f8442459a4992a970c334b..8266e99e3dea77821626d670d477e2c09b8f86f5 100644 --- a/bug/interface.go +++ b/bug/interface.go @@ -1,16 +1,14 @@ package bug import ( + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/lamport" ) type Interface interface { // Id return the Bug identifier - Id() string - - // HumanId return the Bug identifier truncated for human consumption - HumanId() string + Id() entity.Id // Validate check if the Bug data is valid Validate() error diff --git a/bug/op_add_comment.go b/bug/op_add_comment.go index 095b7f4370a1c4c6ebfaf1cb508834151553c078..e16ea0ddff5c84c7ee856fa3f7acf398a2817973 100644 --- a/bug/op_add_comment.go +++ b/bug/op_add_comment.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/text" @@ -15,32 +16,25 @@ var _ Operation = &AddCommentOperation{} // AddCommentOperation will add a new comment in the bug type AddCommentOperation struct { OpBase - Message string + Message string `json:"message"` // TODO: change for a map[string]util.hash to store the filename ? - Files []git.Hash + Files []git.Hash `json:"files"` } func (op *AddCommentOperation) base() *OpBase { return &op.OpBase } -func (op *AddCommentOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *AddCommentOperation) Id() entity.Id { + return idOperation(op) } func (op *AddCommentOperation) Apply(snapshot *Snapshot) { snapshot.addActor(op.Author) snapshot.addParticipant(op.Author) - hash, err := op.Hash() - if err != nil { - // Should never error unless a programming error happened - // (covered in OpBase.Validate()) - panic(err) - } - comment := Comment{ - id: string(hash), + id: op.Id(), Message: op.Message, Author: op.Author, Files: op.Files, @@ -50,7 +44,7 @@ func (op *AddCommentOperation) Apply(snapshot *Snapshot) { snapshot.Comments = append(snapshot.Comments, comment) item := &AddCommentTimelineItem{ - CommentTimelineItem: NewCommentTimelineItem(hash, comment), + CommentTimelineItem: NewCommentTimelineItem(op.Id(), comment), } snapshot.Timeline = append(snapshot.Timeline, item) @@ -72,28 +66,9 @@ func (op *AddCommentOperation) Validate() error { return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *AddCommentOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["message"] = op.Message - data["files"] = op.Files - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *AddCommentOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately diff --git a/bug/op_add_comment_test.go b/bug/op_add_comment_test.go index a38d0228e9c4cb91bcfedc4c26c02ca895f57dbc..60364cf132818ac6d45bcbe4bcfe5cb654fa9e18 100644 --- a/bug/op_add_comment_test.go +++ b/bug/op_add_comment_test.go @@ -5,8 +5,9 @@ import ( "testing" "time" - "github.com/MichaelMure/git-bug/identity" "github.com/stretchr/testify/assert" + + "github.com/MichaelMure/git-bug/identity" ) func TestAddCommentSerialize(t *testing.T) { @@ -21,5 +22,9 @@ func TestAddCommentSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_create.go b/bug/op_create.go index e52e62547dfba5c25a358ba160c1b2094ebb30df..0da95d4dc902fb4bdf6fbcd67f91fb54e85d44c2 100644 --- a/bug/op_create.go +++ b/bug/op_create.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/text" @@ -16,34 +17,27 @@ var _ Operation = &CreateOperation{} // CreateOperation define the initial creation of a bug type CreateOperation struct { OpBase - Title string - Message string - Files []git.Hash + Title string `json:"title"` + Message string `json:"message"` + Files []git.Hash `json:"files"` } func (op *CreateOperation) base() *OpBase { return &op.OpBase } -func (op *CreateOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *CreateOperation) Id() entity.Id { + return idOperation(op) } func (op *CreateOperation) Apply(snapshot *Snapshot) { snapshot.addActor(op.Author) snapshot.addParticipant(op.Author) - hash, err := op.Hash() - if err != nil { - // Should never error unless a programming error happened - // (covered in OpBase.Validate()) - panic(err) - } - snapshot.Title = op.Title comment := Comment{ - id: string(hash), + id: op.Id(), Message: op.Message, Author: op.Author, UnixTime: timestamp.Timestamp(op.UnixTime), @@ -55,7 +49,7 @@ func (op *CreateOperation) Apply(snapshot *Snapshot) { snapshot.Timeline = []TimelineItem{ &CreateTimelineItem{ - CommentTimelineItem: NewCommentTimelineItem(hash, comment), + CommentTimelineItem: NewCommentTimelineItem(op.Id(), comment), }, } } @@ -88,29 +82,9 @@ func (op *CreateOperation) Validate() error { return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *CreateOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["title"] = op.Title - data["message"] = op.Message - data["files"] = op.Files - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *CreateOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately diff --git a/bug/op_create_test.go b/bug/op_create_test.go index 2a88f25650cd3ce84ef45c371d7dbacbc40e58ab..ec53b04b4b036b45cec1088509a271e5fb8f5498 100644 --- a/bug/op_create_test.go +++ b/bug/op_create_test.go @@ -20,11 +20,11 @@ func TestCreate(t *testing.T) { create.Apply(&snapshot) - hash, err := create.Hash() - assert.NoError(t, err) + id := create.Id() + assert.NoError(t, id.Validate()) comment := Comment{ - id: string(hash), + id: id, Author: rene, Message: "message", UnixTime: timestamp.Timestamp(create.UnixTime), @@ -41,7 +41,7 @@ func TestCreate(t *testing.T) { CreatedAt: create.Time(), Timeline: []TimelineItem{ &CreateTimelineItem{ - CommentTimelineItem: NewCommentTimelineItem(hash, comment), + CommentTimelineItem: NewCommentTimelineItem(id, comment), }, }, } @@ -61,5 +61,9 @@ func TestCreateSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_edit_comment.go b/bug/op_edit_comment.go index 01832959cdda0d2b9d046954d0c7259ef4542ebe..a37ce8f7fd10a630ef74679f73cec1920609261f 100644 --- a/bug/op_edit_comment.go +++ b/bug/op_edit_comment.go @@ -4,6 +4,9 @@ import ( "encoding/json" "fmt" + "github.com/pkg/errors" + + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/timestamp" @@ -16,17 +19,17 @@ var _ Operation = &EditCommentOperation{} // EditCommentOperation will change a comment in the bug type EditCommentOperation struct { OpBase - Target git.Hash - Message string - Files []git.Hash + Target entity.Id `json:"target"` + Message string `json:"message"` + Files []git.Hash `json:"files"` } func (op *EditCommentOperation) base() *OpBase { return &op.OpBase } -func (op *EditCommentOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *EditCommentOperation) Id() entity.Id { + return idOperation(op) } func (op *EditCommentOperation) Apply(snapshot *Snapshot) { @@ -38,9 +41,7 @@ func (op *EditCommentOperation) Apply(snapshot *Snapshot) { var target TimelineItem for i, item := range snapshot.Timeline { - h := item.Hash() - - if h == op.Target { + if item.Id() == op.Target { target = snapshot.Timeline[i] break } @@ -52,7 +53,7 @@ func (op *EditCommentOperation) Apply(snapshot *Snapshot) { } comment := Comment{ - id: string(op.Target), + id: op.Target, Message: op.Message, Files: op.Files, UnixTime: timestamp.Timestamp(op.UnixTime), @@ -71,7 +72,7 @@ func (op *EditCommentOperation) Apply(snapshot *Snapshot) { // Updating the corresponding comment for i := range snapshot.Comments { - if snapshot.Comments[i].Id() == string(op.Target) { + if snapshot.Comments[i].Id() == op.Target { snapshot.Comments[i].Message = op.Message snapshot.Comments[i].Files = op.Files break @@ -88,8 +89,8 @@ func (op *EditCommentOperation) Validate() error { return err } - if !op.Target.IsValid() { - return fmt.Errorf("target hash is invalid") + if err := op.Target.Validate(); err != nil { + return errors.Wrap(err, "target hash is invalid") } if !text.Safe(op.Message) { @@ -99,29 +100,9 @@ func (op *EditCommentOperation) Validate() error { return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *EditCommentOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["target"] = op.Target - data["message"] = op.Message - data["files"] = op.Files - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *EditCommentOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately @@ -132,7 +113,7 @@ func (op *EditCommentOperation) UnmarshalJSON(data []byte) error { } aux := struct { - Target git.Hash `json:"target"` + Target entity.Id `json:"target"` Message string `json:"message"` Files []git.Hash `json:"files"` }{} @@ -153,7 +134,7 @@ func (op *EditCommentOperation) UnmarshalJSON(data []byte) error { // Sign post method for gqlgen func (op *EditCommentOperation) IsAuthored() {} -func NewEditCommentOp(author identity.Interface, unixTime int64, target git.Hash, message string, files []git.Hash) *EditCommentOperation { +func NewEditCommentOp(author identity.Interface, unixTime int64, target entity.Id, message string, files []git.Hash) *EditCommentOperation { return &EditCommentOperation{ OpBase: newOpBase(EditCommentOp, author, unixTime), Target: target, @@ -163,11 +144,11 @@ func NewEditCommentOp(author identity.Interface, unixTime int64, target git.Hash } // Convenience function to apply the operation -func EditComment(b Interface, author identity.Interface, unixTime int64, target git.Hash, message string) (*EditCommentOperation, error) { +func EditComment(b Interface, author identity.Interface, unixTime int64, target entity.Id, message string) (*EditCommentOperation, error) { return EditCommentWithFiles(b, author, unixTime, target, message, nil) } -func EditCommentWithFiles(b Interface, author identity.Interface, unixTime int64, target git.Hash, message string, files []git.Hash) (*EditCommentOperation, error) { +func EditCommentWithFiles(b Interface, author identity.Interface, unixTime int64, target entity.Id, message string, files []git.Hash) (*EditCommentOperation, error) { editCommentOp := NewEditCommentOp(author, unixTime, target, message, files) if err := editCommentOp.Validate(); err != nil { return nil, err diff --git a/bug/op_edit_comment_test.go b/bug/op_edit_comment_test.go index ab0f2d2172f24fcdbb23d8e7308db56ad1dc28f4..abd550cb520aa59b6a17158c517120450172dc8a 100644 --- a/bug/op_edit_comment_test.go +++ b/bug/op_edit_comment_test.go @@ -20,14 +20,14 @@ func TestEdit(t *testing.T) { create := NewCreateOp(rene, unix, "title", "create", nil) create.Apply(&snapshot) - hash1, err := create.Hash() - require.NoError(t, err) + id1 := create.Id() + require.NoError(t, id1.Validate()) comment1 := NewAddCommentOp(rene, unix, "comment 1", nil) comment1.Apply(&snapshot) - hash2, err := comment1.Hash() - require.NoError(t, err) + id2 := comment1.Id() + require.NoError(t, id2.Validate()) // add another unrelated op in between setTitle := NewSetTitleOp(rene, unix, "edited title", "title") @@ -36,10 +36,10 @@ func TestEdit(t *testing.T) { comment2 := NewAddCommentOp(rene, unix, "comment 2", nil) comment2.Apply(&snapshot) - hash3, err := comment2.Hash() - require.NoError(t, err) + id3 := comment2.Id() + require.NoError(t, id3.Validate()) - edit := NewEditCommentOp(rene, unix, hash1, "create edited", nil) + edit := NewEditCommentOp(rene, unix, id1, "create edited", nil) edit.Apply(&snapshot) assert.Equal(t, len(snapshot.Timeline), 4) @@ -50,7 +50,7 @@ func TestEdit(t *testing.T) { assert.Equal(t, snapshot.Comments[1].Message, "comment 1") assert.Equal(t, snapshot.Comments[2].Message, "comment 2") - edit2 := NewEditCommentOp(rene, unix, hash2, "comment 1 edited", nil) + edit2 := NewEditCommentOp(rene, unix, id2, "comment 1 edited", nil) edit2.Apply(&snapshot) assert.Equal(t, len(snapshot.Timeline), 4) @@ -61,7 +61,7 @@ func TestEdit(t *testing.T) { assert.Equal(t, snapshot.Comments[1].Message, "comment 1 edited") assert.Equal(t, snapshot.Comments[2].Message, "comment 2") - edit3 := NewEditCommentOp(rene, unix, hash3, "comment 2 edited", nil) + edit3 := NewEditCommentOp(rene, unix, id3, "comment 2 edited", nil) edit3.Apply(&snapshot) assert.Equal(t, len(snapshot.Timeline), 4) @@ -85,5 +85,9 @@ func TestEditCommentSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_label_change.go b/bug/op_label_change.go index 4c019d6735e475eb97a376866d1ea562938a5b4b..c911de260f08e1ca30a0180f312eeb476caed9d9 100644 --- a/bug/op_label_change.go +++ b/bug/op_label_change.go @@ -5,11 +5,11 @@ import ( "fmt" "sort" + "github.com/pkg/errors" + + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/timestamp" - - "github.com/MichaelMure/git-bug/util/git" - "github.com/pkg/errors" ) var _ Operation = &LabelChangeOperation{} @@ -17,16 +17,16 @@ var _ Operation = &LabelChangeOperation{} // LabelChangeOperation define a Bug operation to add or remove labels type LabelChangeOperation struct { OpBase - Added []Label - Removed []Label + Added []Label `json:"added"` + Removed []Label `json:"removed"` } func (op *LabelChangeOperation) base() *OpBase { return &op.OpBase } -func (op *LabelChangeOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *LabelChangeOperation) Id() entity.Id { + return idOperation(op) } // Apply apply the operation @@ -61,15 +61,8 @@ AddLoop: return string(snapshot.Labels[i]) < string(snapshot.Labels[j]) }) - hash, err := op.Hash() - if err != nil { - // Should never error unless a programming error happened - // (covered in OpBase.Validate()) - panic(err) - } - item := &LabelChangeTimelineItem{ - hash: hash, + id: op.Id(), Author: op.Author, UnixTime: timestamp.Timestamp(op.UnixTime), Added: op.Added, @@ -103,28 +96,9 @@ func (op *LabelChangeOperation) Validate() error { return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *LabelChangeOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["added"] = op.Added - data["removed"] = op.Removed - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *LabelChangeOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately @@ -163,15 +137,15 @@ func NewLabelChangeOperation(author identity.Interface, unixTime int64, added, r } type LabelChangeTimelineItem struct { - hash git.Hash + id entity.Id Author identity.Interface UnixTime timestamp.Timestamp Added []Label Removed []Label } -func (l LabelChangeTimelineItem) Hash() git.Hash { - return l.hash +func (l LabelChangeTimelineItem) Id() entity.Id { + return l.id } // Sign post method for gqlgen diff --git a/bug/op_label_change_test.go b/bug/op_label_change_test.go index f5550b7296f08626090120ff9ba0c3e887233409..2a93e362657a22fc66fd050fdeb4a90b310fa37f 100644 --- a/bug/op_label_change_test.go +++ b/bug/op_label_change_test.go @@ -21,5 +21,9 @@ func TestLabelChangeSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_noop.go b/bug/op_noop.go index 3cd9f39adf7b9659cb3ad17cf2fc112d663c1717..16d32297894f92dde4add1916f8d4b9ba98244f9 100644 --- a/bug/op_noop.go +++ b/bug/op_noop.go @@ -3,8 +3,8 @@ package bug import ( "encoding/json" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/util/git" ) var _ Operation = &NoOpOperation{} @@ -20,8 +20,8 @@ func (op *NoOpOperation) base() *OpBase { return &op.OpBase } -func (op *NoOpOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *NoOpOperation) Id() entity.Id { + return idOperation(op) } func (op *NoOpOperation) Apply(snapshot *Snapshot) { @@ -32,25 +32,9 @@ func (op *NoOpOperation) Validate() error { return opBaseValidate(op, NoOpOp) } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *NoOpOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *NoOpOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately diff --git a/bug/op_noop_test.go b/bug/op_noop_test.go index 385bc9145d8bf28e6457d3135eb4d47b86226be0..ea815948083a5e873131f982b176ffa438f81829 100644 --- a/bug/op_noop_test.go +++ b/bug/op_noop_test.go @@ -21,5 +21,9 @@ func TestNoopSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_set_metadata.go b/bug/op_set_metadata.go index 7616a59126152e9118f76cde634392c004ee0c51..f99f836bc2f588c0e34c729d5c7f376ec65d6640 100644 --- a/bug/op_set_metadata.go +++ b/bug/op_set_metadata.go @@ -2,38 +2,32 @@ package bug import ( "encoding/json" - "fmt" + "github.com/pkg/errors" + + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/util/git" ) var _ Operation = &SetMetadataOperation{} type SetMetadataOperation struct { OpBase - Target git.Hash - NewMetadata map[string]string + Target entity.Id `json:"target"` + NewMetadata map[string]string `json:"new_metadata"` } func (op *SetMetadataOperation) base() *OpBase { return &op.OpBase } -func (op *SetMetadataOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *SetMetadataOperation) Id() entity.Id { + return idOperation(op) } func (op *SetMetadataOperation) Apply(snapshot *Snapshot) { for _, target := range snapshot.Operations { - hash, err := target.Hash() - if err != nil { - // Should never error unless a programming error happened - // (covered in OpBase.Validate()) - panic(err) - } - - if hash == op.Target { + if target.Id() == op.Target { base := target.base() if base.extraMetadata == nil { @@ -56,35 +50,16 @@ func (op *SetMetadataOperation) Validate() error { return err } - if !op.Target.IsValid() { - return fmt.Errorf("target hash is invalid") + if err := op.Target.Validate(); err != nil { + return errors.Wrap(err, "target invalid") } return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *SetMetadataOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["target"] = op.Target - data["new_metadata"] = op.NewMetadata - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *SetMetadataOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately @@ -95,7 +70,7 @@ func (op *SetMetadataOperation) UnmarshalJSON(data []byte) error { } aux := struct { - Target git.Hash `json:"target"` + Target entity.Id `json:"target"` NewMetadata map[string]string `json:"new_metadata"` }{} @@ -114,7 +89,7 @@ func (op *SetMetadataOperation) UnmarshalJSON(data []byte) error { // Sign post method for gqlgen func (op *SetMetadataOperation) IsAuthored() {} -func NewSetMetadataOp(author identity.Interface, unixTime int64, target git.Hash, newMetadata map[string]string) *SetMetadataOperation { +func NewSetMetadataOp(author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *SetMetadataOperation { return &SetMetadataOperation{ OpBase: newOpBase(SetMetadataOp, author, unixTime), Target: target, @@ -123,7 +98,7 @@ func NewSetMetadataOp(author identity.Interface, unixTime int64, target git.Hash } // Convenience function to apply the operation -func SetMetadata(b Interface, author identity.Interface, unixTime int64, target git.Hash, newMetadata map[string]string) (*SetMetadataOperation, error) { +func SetMetadata(b Interface, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) (*SetMetadataOperation, error) { SetMetadataOp := NewSetMetadataOp(author, unixTime, target, newMetadata) if err := SetMetadataOp.Validate(); err != nil { return nil, err diff --git a/bug/op_set_metadata_test.go b/bug/op_set_metadata_test.go index a7f9f313a5e2831b3e4eb078b76f0b486ab0f608..389e91acebfb6ed62e4d6c080ac3b84950f112b2 100644 --- a/bug/op_set_metadata_test.go +++ b/bug/op_set_metadata_test.go @@ -21,18 +21,18 @@ func TestSetMetadata(t *testing.T) { create.Apply(&snapshot) snapshot.Operations = append(snapshot.Operations, create) - hash1, err := create.Hash() - require.NoError(t, err) + id1 := create.Id() + require.NoError(t, id1.Validate()) comment := NewAddCommentOp(rene, unix, "comment", nil) comment.SetMetadata("key2", "value2") comment.Apply(&snapshot) snapshot.Operations = append(snapshot.Operations, comment) - hash2, err := comment.Hash() - require.NoError(t, err) + id2 := comment.Id() + require.NoError(t, id2.Validate()) - op1 := NewSetMetadataOp(rene, unix, hash1, map[string]string{ + op1 := NewSetMetadataOp(rene, unix, id1, map[string]string{ "key": "override", "key2": "value", }) @@ -51,7 +51,7 @@ func TestSetMetadata(t *testing.T) { assert.Equal(t, len(commentMetadata), 1) assert.Equal(t, commentMetadata["key2"], "value2") - op2 := NewSetMetadataOp(rene, unix, hash2, map[string]string{ + op2 := NewSetMetadataOp(rene, unix, id2, map[string]string{ "key2": "value", "key3": "value3", }) @@ -71,7 +71,7 @@ func TestSetMetadata(t *testing.T) { // new key is set assert.Equal(t, commentMetadata["key3"], "value3") - op3 := NewSetMetadataOp(rene, unix, hash1, map[string]string{ + op3 := NewSetMetadataOp(rene, unix, id1, map[string]string{ "key": "override", "key2": "override", }) @@ -107,5 +107,9 @@ func TestSetMetadataSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_set_status.go b/bug/op_set_status.go index 52ba8135c8a197d7a902245f8ee3e78652ddb0e4..8a245184dc12b55f7bd1c633617905edf39b3000 100644 --- a/bug/op_set_status.go +++ b/bug/op_set_status.go @@ -3,10 +3,11 @@ package bug import ( "encoding/json" + "github.com/pkg/errors" + + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/timestamp" - "github.com/pkg/errors" ) var _ Operation = &SetStatusOperation{} @@ -14,30 +15,23 @@ var _ Operation = &SetStatusOperation{} // SetStatusOperation will change the status of a bug type SetStatusOperation struct { OpBase - Status Status + Status Status `json:"status"` } func (op *SetStatusOperation) base() *OpBase { return &op.OpBase } -func (op *SetStatusOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *SetStatusOperation) Id() entity.Id { + return idOperation(op) } func (op *SetStatusOperation) Apply(snapshot *Snapshot) { snapshot.Status = op.Status snapshot.addActor(op.Author) - hash, err := op.Hash() - if err != nil { - // Should never error unless a programming error happened - // (covered in OpBase.Validate()) - panic(err) - } - item := &SetStatusTimelineItem{ - hash: hash, + id: op.Id(), Author: op.Author, UnixTime: timestamp.Timestamp(op.UnixTime), Status: op.Status, @@ -58,27 +52,9 @@ func (op *SetStatusOperation) Validate() error { return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *SetStatusOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["status"] = op.Status - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *SetStatusOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately @@ -114,14 +90,14 @@ func NewSetStatusOp(author identity.Interface, unixTime int64, status Status) *S } type SetStatusTimelineItem struct { - hash git.Hash + id entity.Id Author identity.Interface UnixTime timestamp.Timestamp Status Status } -func (s SetStatusTimelineItem) Hash() git.Hash { - return s.hash +func (s SetStatusTimelineItem) Id() entity.Id { + return s.id } // Sign post method for gqlgen diff --git a/bug/op_set_status_test.go b/bug/op_set_status_test.go index 2506b94787e80392b4581723873508ed22c1603c..ea0321849c0514fff309fc71f53a7c17684bdab8 100644 --- a/bug/op_set_status_test.go +++ b/bug/op_set_status_test.go @@ -21,5 +21,9 @@ func TestSetStatusSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/op_set_title.go b/bug/op_set_title.go index 311139432a1223d7165484ae0ac8e8a2ab5b1f79..fadd29a98a090dcd9a90b0f6a9950240aee25bc4 100644 --- a/bug/op_set_title.go +++ b/bug/op_set_title.go @@ -5,10 +5,10 @@ import ( "fmt" "strings" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/timestamp" - "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/text" ) @@ -17,31 +17,24 @@ var _ Operation = &SetTitleOperation{} // SetTitleOperation will change the title of a bug type SetTitleOperation struct { OpBase - Title string - Was string + Title string `json:"title"` + Was string `json:"was"` } func (op *SetTitleOperation) base() *OpBase { return &op.OpBase } -func (op *SetTitleOperation) Hash() (git.Hash, error) { - return hashOperation(op) +func (op *SetTitleOperation) Id() entity.Id { + return idOperation(op) } func (op *SetTitleOperation) Apply(snapshot *Snapshot) { snapshot.Title = op.Title snapshot.addActor(op.Author) - hash, err := op.Hash() - if err != nil { - // Should never error unless a programming error happened - // (covered in OpBase.Validate()) - panic(err) - } - item := &SetTitleTimelineItem{ - hash: hash, + id: op.Id(), Author: op.Author, UnixTime: timestamp.Timestamp(op.UnixTime), Title: op.Title, @@ -79,28 +72,9 @@ func (op *SetTitleOperation) Validate() error { return nil } -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON -func (op *SetTitleOperation) MarshalJSON() ([]byte, error) { - base, err := json.Marshal(op.OpBase) - if err != nil { - return nil, err - } - - // revert back to a flat map to be able to add our own fields - var data map[string]interface{} - if err := json.Unmarshal(base, &data); err != nil { - return nil, err - } - - data["title"] = op.Title - data["was"] = op.Was - - return json.Marshal(data) -} - -// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op -// MarshalJSON +// UnmarshalJSON is a two step JSON unmarshaling +// This workaround is necessary to avoid the inner OpBase.MarshalJSON +// overriding the outer op's MarshalJSON func (op *SetTitleOperation) UnmarshalJSON(data []byte) error { // Unmarshal OpBase and the op separately @@ -139,15 +113,15 @@ func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was } type SetTitleTimelineItem struct { - hash git.Hash + id entity.Id Author identity.Interface UnixTime timestamp.Timestamp Title string Was string } -func (s SetTitleTimelineItem) Hash() git.Hash { - return s.hash +func (s SetTitleTimelineItem) Id() entity.Id { + return s.id } // Sign post method for gqlgen diff --git a/bug/op_set_title_test.go b/bug/op_set_title_test.go index 1f7305962299cb80aa81da0eeb95bde9e71ca96f..19cbb12b0d95e69058bd70c85e6853f8eae1b368 100644 --- a/bug/op_set_title_test.go +++ b/bug/op_set_title_test.go @@ -21,5 +21,9 @@ func TestSetTitleSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the IDs + before.Id() + rene.Id() + assert.Equal(t, before, &after) } diff --git a/bug/operation.go b/bug/operation.go index daef7b8c33f24d5fb5b7f94419a5a7a9aca3357b..dd95e096765649b292f12db5ff1447698c8d9c97 100644 --- a/bug/operation.go +++ b/bug/operation.go @@ -6,10 +6,11 @@ import ( "fmt" "time" - "github.com/MichaelMure/git-bug/identity" + "github.com/pkg/errors" + "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/git" - "github.com/pkg/errors" ) // OperationType is an operation type identifier @@ -31,8 +32,8 @@ const ( type Operation interface { // base return the OpBase of the Operation, for package internal use base() *OpBase - // Hash return the hash of the operation, to be used for back references - Hash() (git.Hash, error) + // Id return the identifier of the operation, to be used for back references + Id() entity.Id // Time return the time when the operation was added Time() time.Time // GetUnixTime return the unix timestamp when the operation was added @@ -53,42 +54,42 @@ type Operation interface { GetAuthor() identity.Interface } -func hashRaw(data []byte) git.Hash { - hasher := sha256.New() - // Write can't fail - _, _ = hasher.Write(data) - return git.Hash(fmt.Sprintf("%x", hasher.Sum(nil))) +func deriveId(data []byte) entity.Id { + sum := sha256.Sum256(data) + return entity.Id(fmt.Sprintf("%x", sum)) } -// hash compute the hash of the serialized operation -func hashOperation(op Operation) (git.Hash, error) { - // TODO: this might not be the best idea: if a single bit change in the output of json.Marshal, this will break. - // Idea: hash the segment of serialized data (= immutable) instead of the go object in memory - +func idOperation(op Operation) entity.Id { base := op.base() - if base.hash != "" { - return base.hash, nil + if base.id == "" { + // something went really wrong + panic("op's id not set") } + if base.id == entity.UnsetId { + // This means we are trying to get the op's Id *before* it has been stored, for instance when + // adding multiple ops in one go in an OperationPack. + // As the Id is computed based on the actual bytes written on the disk, we are going to predict + // those and then get the Id. This is safe as it will be the exact same code writing on disk later. + + data, err := json.Marshal(op) + if err != nil { + panic(err) + } - data, err := json.Marshal(op) - if err != nil { - return "", err + base.id = deriveId(data) } - - base.hash = hashRaw(data) - - return base.hash, nil + return base.id } // OpBase implement the common code for all operations type OpBase struct { - OperationType OperationType - Author identity.Interface - UnixTime int64 - Metadata map[string]string - // Not serialized. Store the op's hash in memory. - hash git.Hash + OperationType OperationType `json:"type"` + Author identity.Interface `json:"author"` + UnixTime int64 `json:"timestamp"` + Metadata map[string]string `json:"metadata,omitempty"` + // Not serialized. Store the op's id in memory. + id entity.Id // Not serialized. Store the extra metadata in memory, // compiled from SetMetadataOperation. extraMetadata map[string]string @@ -100,24 +101,14 @@ func newOpBase(opType OperationType, author identity.Interface, unixTime int64) OperationType: opType, Author: author, UnixTime: unixTime, + id: entity.UnsetId, } } -func (op OpBase) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - OperationType OperationType `json:"type"` - Author identity.Interface `json:"author"` - UnixTime int64 `json:"timestamp"` - Metadata map[string]string `json:"metadata,omitempty"` - }{ - OperationType: op.OperationType, - Author: op.Author, - UnixTime: op.UnixTime, - Metadata: op.Metadata, - }) -} - func (op *OpBase) UnmarshalJSON(data []byte) error { + // Compute the Id when loading the op from disk. + op.id = deriveId(data) + aux := struct { OperationType OperationType `json:"type"` Author json.RawMessage `json:"author"` @@ -192,7 +183,7 @@ func (op *OpBase) SetMetadata(key string, value string) { } op.Metadata[key] = value - op.hash = "" + op.id = entity.UnsetId } // GetMetadata retrieve arbitrary metadata about the operation diff --git a/bug/operation_pack.go b/bug/operation_pack.go index 5f3e9da8a10881eb846796fe339d2cdab2ffb7c6..86e4178ecbb13604dad9aa6d812de3b4da00fff6 100644 --- a/bug/operation_pack.go +++ b/bug/operation_pack.go @@ -63,9 +63,6 @@ func (opp *OperationPack) UnmarshalJSON(data []byte) error { return err } - // Compute the hash of the operation - op.base().hash = hashRaw(raw) - opp.Operations = append(opp.Operations, op) } diff --git a/bug/operation_pack_test.go b/bug/operation_pack_test.go index 09d159af978ac205e584813d40adfd603d240823..21ac0a001207a1fe449441c14081bc413c280e61 100644 --- a/bug/operation_pack_test.go +++ b/bug/operation_pack_test.go @@ -48,14 +48,16 @@ func TestOperationPackSerialize(t *testing.T) { err = json.Unmarshal(data, &opp2) assert.NoError(t, err) - ensureHash(t, opp) + ensureIDs(t, opp) assert.Equal(t, opp, opp2) } -func ensureHash(t *testing.T, opp *OperationPack) { +func ensureIDs(t *testing.T, opp *OperationPack) { for _, op := range opp.Operations { - _, err := op.Hash() - require.NoError(t, err) + id := op.Id() + require.NoError(t, id.Validate()) + id = op.GetAuthor().Id() + require.NoError(t, id.Validate()) } } diff --git a/bug/operation_test.go b/bug/operation_test.go index 0ddb61c2d1524b082781379d36775ade7cecaa0c..69c66dc8ed1df4b9982b1ec4d25502d759a874ec 100644 --- a/bug/operation_test.go +++ b/bug/operation_test.go @@ -79,7 +79,7 @@ func TestMetadata(t *testing.T) { require.Equal(t, val, "value") } -func TestHash(t *testing.T) { +func TestID(t *testing.T) { repo := repository.CreateTestRepo(false) defer repository.CleanupTestRepos(t, repo) @@ -94,27 +94,27 @@ func TestHash(t *testing.T) { b, op, err := Create(rene, time.Now().Unix(), "title", "message") require.Nil(t, err) - h1, err := op.Hash() - require.Nil(t, err) + id1 := op.Id() + require.NoError(t, id1.Validate()) err = b.Commit(repo) require.Nil(t, err) op2 := b.FirstOp() - h2, err := op2.Hash() - require.Nil(t, err) + id2 := op2.Id() + require.NoError(t, id2.Validate()) - require.Equal(t, h1, h2) + require.Equal(t, id1, id2) - b2, err := ReadLocalBug(repo, b.id) + b2, err := ReadLocalBug(repo, b.Id()) require.Nil(t, err) op3 := b2.FirstOp() - h3, err := op3.Hash() - require.Nil(t, err) + id3 := op3.Id() + require.NoError(t, id3.Validate()) - require.Equal(t, h1, h3) + require.Equal(t, id1, id3) } } diff --git a/bug/snapshot.go b/bug/snapshot.go index f1da809917cd861f334b9d0ec250fbdc62e0f432..39366c6d868215ae17bae8cfcdd9e22316511967 100644 --- a/bug/snapshot.go +++ b/bug/snapshot.go @@ -4,13 +4,13 @@ import ( "fmt" "time" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" - "github.com/MichaelMure/git-bug/util/git" ) // Snapshot is a compiled form of the Bug data structure used for storage and merge type Snapshot struct { - id string + id entity.Id Status Status Title string @@ -27,15 +27,10 @@ type Snapshot struct { } // Return the Bug identifier -func (snap *Snapshot) Id() string { +func (snap *Snapshot) Id() entity.Id { return snap.id } -// Return the Bug identifier truncated for human consumption -func (snap *Snapshot) HumanId() string { - return FormatHumanID(snap.id) -} - // Return the last time a bug was modified func (snap *Snapshot) LastEditTime() time.Time { if len(snap.Operations) == 0 { @@ -60,9 +55,9 @@ func (snap *Snapshot) GetCreateMetadata(key string) (string, bool) { } // SearchTimelineItem will search in the timeline for an item matching the given hash -func (snap *Snapshot) SearchTimelineItem(hash git.Hash) (TimelineItem, error) { +func (snap *Snapshot) SearchTimelineItem(id entity.Id) (TimelineItem, error) { for i := range snap.Timeline { - if snap.Timeline[i].Hash() == hash { + if snap.Timeline[i].Id() == id { return snap.Timeline[i], nil } } @@ -71,9 +66,9 @@ func (snap *Snapshot) SearchTimelineItem(hash git.Hash) (TimelineItem, error) { } // SearchComment will search for a comment matching the given hash -func (snap *Snapshot) SearchComment(hash git.Hash) (*Comment, error) { +func (snap *Snapshot) SearchComment(id entity.Id) (*Comment, error) { for _, c := range snap.Comments { - if c.id == hash.String() { + if c.id == id { return &c, nil } } @@ -104,7 +99,7 @@ func (snap *Snapshot) addParticipant(participant identity.Interface) { } // HasParticipant return true if the id is a participant -func (snap *Snapshot) HasParticipant(id string) bool { +func (snap *Snapshot) HasParticipant(id entity.Id) bool { for _, p := range snap.Participants { if p.Id() == id { return true @@ -114,7 +109,7 @@ func (snap *Snapshot) HasParticipant(id string) bool { } // HasAnyParticipant return true if one of the ids is a participant -func (snap *Snapshot) HasAnyParticipant(ids ...string) bool { +func (snap *Snapshot) HasAnyParticipant(ids ...entity.Id) bool { for _, id := range ids { if snap.HasParticipant(id) { return true @@ -124,7 +119,7 @@ func (snap *Snapshot) HasAnyParticipant(ids ...string) bool { } // HasActor return true if the id is a actor -func (snap *Snapshot) HasActor(id string) bool { +func (snap *Snapshot) HasActor(id entity.Id) bool { for _, p := range snap.Actors { if p.Id() == id { return true @@ -134,7 +129,7 @@ func (snap *Snapshot) HasActor(id string) bool { } // HasAnyActor return true if one of the ids is a actor -func (snap *Snapshot) HasAnyActor(ids ...string) bool { +func (snap *Snapshot) HasAnyActor(ids ...entity.Id) bool { for _, id := range ids { if snap.HasActor(id) { return true diff --git a/bug/timeline.go b/bug/timeline.go index d8ee2c6b0ed84bc439b7ff5712d80da63c4e8399..4af1b92ac1fee2b7849f9b21109d66021432dcac 100644 --- a/bug/timeline.go +++ b/bug/timeline.go @@ -3,14 +3,15 @@ package bug import ( "strings" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/timestamp" ) type TimelineItem interface { - // Hash return the hash of the item - Hash() git.Hash + // ID return the identifier of the item + Id() entity.Id } // CommentHistoryStep hold one version of a message in the history @@ -25,7 +26,7 @@ type CommentHistoryStep struct { // CommentTimelineItem is a TimelineItem that holds a Comment and its edition history type CommentTimelineItem struct { - hash git.Hash + id entity.Id Author identity.Interface Message string Files []git.Hash @@ -34,9 +35,9 @@ type CommentTimelineItem struct { History []CommentHistoryStep } -func NewCommentTimelineItem(hash git.Hash, comment Comment) CommentTimelineItem { +func NewCommentTimelineItem(ID entity.Id, comment Comment) CommentTimelineItem { return CommentTimelineItem{ - hash: hash, + id: ID, Author: comment.Author, Message: comment.Message, Files: comment.Files, @@ -51,8 +52,8 @@ func NewCommentTimelineItem(hash git.Hash, comment Comment) CommentTimelineItem } } -func (c *CommentTimelineItem) Hash() git.Hash { - return c.hash +func (c *CommentTimelineItem) Id() entity.Id { + return c.id } // Append will append a new comment in the history and update the other values diff --git a/cache/bug_cache.go b/cache/bug_cache.go index 758fb0b7feefc1560a0f35e94e5cacfef924bb7c..6a220f49f61c686a9068de510734bcbd03d72a21 100644 --- a/cache/bug_cache.go +++ b/cache/bug_cache.go @@ -2,13 +2,15 @@ package cache import ( "fmt" - "strings" "time" "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/util/git" ) +var ErrNoMatchingOp = fmt.Errorf("no matching operation found") + // BugCache is a wrapper around a Bug. It provide multiple functions: // // 1. Provide a higher level API to use than the raw API from Bug. @@ -29,49 +31,25 @@ func (c *BugCache) Snapshot() *bug.Snapshot { return c.bug.Snapshot() } -func (c *BugCache) Id() string { +func (c *BugCache) Id() entity.Id { return c.bug.Id() } -func (c *BugCache) HumanId() string { - return c.bug.HumanId() -} - func (c *BugCache) notifyUpdated() error { return c.repoCache.bugUpdated(c.bug.Id()) } -var ErrNoMatchingOp = fmt.Errorf("no matching operation found") - -type ErrMultipleMatchOp struct { - Matching []git.Hash -} - -func (e ErrMultipleMatchOp) Error() string { - casted := make([]string, len(e.Matching)) - - for i := range e.Matching { - casted[i] = string(e.Matching[i]) - } - - return fmt.Sprintf("Multiple matching operation found:\n%s", strings.Join(casted, "\n")) -} - // ResolveOperationWithMetadata will find an operation that has the matching metadata -func (c *BugCache) ResolveOperationWithMetadata(key string, value string) (git.Hash, error) { +func (c *BugCache) ResolveOperationWithMetadata(key string, value string) (entity.Id, error) { // preallocate but empty - matching := make([]git.Hash, 0, 5) + matching := make([]entity.Id, 0, 5) it := bug.NewOperationIterator(c.bug) for it.Next() { op := it.Value() opValue, ok := op.GetMetadata(key) if ok && value == opValue { - h, err := op.Hash() - if err != nil { - return "", err - } - matching = append(matching, h) + matching = append(matching, op.Id()) } } @@ -80,7 +58,7 @@ func (c *BugCache) ResolveOperationWithMetadata(key string, value string) (git.H } if len(matching) > 1 { - return "", ErrMultipleMatchOp{Matching: matching} + return "", bug.NewErrMultipleMatchOp(matching) } return matching[0], nil @@ -232,7 +210,7 @@ func (c *BugCache) SetTitleRaw(author *IdentityCache, unixTime int64, title stri return op, c.notifyUpdated() } -func (c *BugCache) EditComment(target git.Hash, message string) (*bug.EditCommentOperation, error) { +func (c *BugCache) EditComment(target entity.Id, message string) (*bug.EditCommentOperation, error) { author, err := c.repoCache.GetUserIdentity() if err != nil { return nil, err @@ -241,7 +219,7 @@ func (c *BugCache) EditComment(target git.Hash, message string) (*bug.EditCommen return c.EditCommentRaw(author, time.Now().Unix(), target, message, nil) } -func (c *BugCache) EditCommentRaw(author *IdentityCache, unixTime int64, target git.Hash, message string, metadata map[string]string) (*bug.EditCommentOperation, error) { +func (c *BugCache) EditCommentRaw(author *IdentityCache, unixTime int64, target entity.Id, message string, metadata map[string]string) (*bug.EditCommentOperation, error) { op, err := bug.EditComment(c.bug, author.Identity, unixTime, target, message) if err != nil { return nil, err @@ -254,7 +232,7 @@ func (c *BugCache) EditCommentRaw(author *IdentityCache, unixTime int64, target return op, c.notifyUpdated() } -func (c *BugCache) SetMetadata(target git.Hash, newMetadata map[string]string) (*bug.SetMetadataOperation, error) { +func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) { author, err := c.repoCache.GetUserIdentity() if err != nil { return nil, err @@ -263,7 +241,7 @@ func (c *BugCache) SetMetadata(target git.Hash, newMetadata map[string]string) ( return c.SetMetadataRaw(author, time.Now().Unix(), target, newMetadata) } -func (c *BugCache) SetMetadataRaw(author *IdentityCache, unixTime int64, target git.Hash, newMetadata map[string]string) (*bug.SetMetadataOperation, error) { +func (c *BugCache) SetMetadataRaw(author *IdentityCache, unixTime int64, target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) { op, err := bug.SetMetadata(c.bug, author.Identity, unixTime, target, newMetadata) if err != nil { return nil, err diff --git a/cache/bug_excerpt.go b/cache/bug_excerpt.go index 8e9e5e37e7848a48b1403b4b2d8a93180a5d288a..e053f9e4fa479b6d81e9c3b93e538bd73f7b6fe6 100644 --- a/cache/bug_excerpt.go +++ b/cache/bug_excerpt.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/util/lamport" ) @@ -17,7 +18,7 @@ func init() { // BugExcerpt hold a subset of the bug values to be able to sort and filter bugs // efficiently without having to read and compile each raw bugs. type BugExcerpt struct { - Id string + Id entity.Id CreateLamportTime lamport.Time EditLamportTime lamport.Time @@ -28,14 +29,14 @@ type BugExcerpt struct { Labels []bug.Label Title string LenComments int - Actors []string - Participants []string + Actors []entity.Id + Participants []entity.Id // If author is identity.Bare, LegacyAuthor is set // If author is identity.Identity, AuthorId is set and data is deported // in a IdentityExcerpt LegacyAuthor LegacyAuthorExcerpt - AuthorId string + AuthorId entity.Id CreateMetadata map[string]string } @@ -60,12 +61,12 @@ func (l LegacyAuthorExcerpt) DisplayName() string { } func NewBugExcerpt(b bug.Interface, snap *bug.Snapshot) *BugExcerpt { - participantsIds := make([]string, len(snap.Participants)) + participantsIds := make([]entity.Id, len(snap.Participants)) for i, participant := range snap.Participants { participantsIds[i] = participant.Id() } - actorsIds := make([]string, len(snap.Actors)) + actorsIds := make([]entity.Id, len(snap.Actors)) for i, actor := range snap.Actors { actorsIds[i] = actor.Id() } @@ -100,10 +101,6 @@ func NewBugExcerpt(b bug.Interface, snap *bug.Snapshot) *BugExcerpt { return e } -func (b *BugExcerpt) HumanId() string { - return bug.FormatHumanID(b.Id) -} - /* * Sorting */ diff --git a/cache/identity_excerpt.go b/cache/identity_excerpt.go index 3ac1390367312842ce2d31b0179cd3b05f7515a1..18514e9ab08f562bef06d7aa680e6c1832828c52 100644 --- a/cache/identity_excerpt.go +++ b/cache/identity_excerpt.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" ) @@ -17,7 +18,7 @@ func init() { // filter identities efficiently without having to read and compile each raw // identity. type IdentityExcerpt struct { - Id string + Id entity.Id Name string Login string @@ -33,10 +34,6 @@ func NewIdentityExcerpt(i *identity.Identity) *IdentityExcerpt { } } -func (i *IdentityExcerpt) HumanId() string { - return identity.FormatHumanID(i.Id) -} - // DisplayName return a non-empty string to display, representing the // identity, based on the non-empty values. func (i *IdentityExcerpt) DisplayName() string { @@ -54,7 +51,7 @@ func (i *IdentityExcerpt) DisplayName() string { // Match matches a query with the identity name, login and ID prefixes func (i *IdentityExcerpt) Match(query string) bool { - return strings.HasPrefix(i.Id, query) || + return i.Id.HasPrefix(query) || strings.Contains(strings.ToLower(i.Name), query) || strings.Contains(strings.ToLower(i.Login), query) } diff --git a/cache/repo_cache.go b/cache/repo_cache.go index a80bc7c978ed90518f2348079a50c4d5518c91f2..d6e8857dfbfd1128404a5d80a7f11547fab0961e 100644 --- a/cache/repo_cache.go +++ b/cache/repo_cache.go @@ -10,16 +10,16 @@ import ( "path" "sort" "strconv" - "strings" "time" + "github.com/pkg/errors" + "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/process" - "github.com/pkg/errors" ) const bugCacheFile = "bug-cache" @@ -58,24 +58,24 @@ type RepoCache struct { repo repository.ClockedRepo // excerpt of bugs data for all bugs - bugExcerpts map[string]*BugExcerpt + bugExcerpts map[entity.Id]*BugExcerpt // bug loaded in memory - bugs map[string]*BugCache + bugs map[entity.Id]*BugCache // excerpt of identities data for all identities - identitiesExcerpts map[string]*IdentityExcerpt + identitiesExcerpts map[entity.Id]*IdentityExcerpt // identities loaded in memory - identities map[string]*IdentityCache + identities map[entity.Id]*IdentityCache // the user identity's id, if known - userIdentityId string + userIdentityId entity.Id } func NewRepoCache(r repository.ClockedRepo) (*RepoCache, error) { c := &RepoCache{ repo: r, - bugs: make(map[string]*BugCache), - identities: make(map[string]*IdentityCache), + bugs: make(map[entity.Id]*BugCache), + identities: make(map[entity.Id]*IdentityCache), } err := c.lock() @@ -191,7 +191,7 @@ func (c *RepoCache) Close() error { // bugUpdated is a callback to trigger when the excerpt of a bug changed, // that is each time a bug is updated -func (c *RepoCache) bugUpdated(id string) error { +func (c *RepoCache) bugUpdated(id entity.Id) error { b, ok := c.bugs[id] if !ok { panic("missing bug in the cache") @@ -205,7 +205,7 @@ func (c *RepoCache) bugUpdated(id string) error { // identityUpdated is a callback to trigger when the excerpt of an identity // changed, that is each time an identity is updated -func (c *RepoCache) identityUpdated(id string) error { +func (c *RepoCache) identityUpdated(id entity.Id) error { i, ok := c.identities[id] if !ok { panic("missing identity in the cache") @@ -237,7 +237,7 @@ func (c *RepoCache) loadBugCache() error { aux := struct { Version uint - Excerpts map[string]*BugExcerpt + Excerpts map[entity.Id]*BugExcerpt }{} err = decoder.Decode(&aux) @@ -266,7 +266,7 @@ func (c *RepoCache) loadIdentityCache() error { aux := struct { Version uint - Excerpts map[string]*IdentityExcerpt + Excerpts map[entity.Id]*IdentityExcerpt }{} err = decoder.Decode(&aux) @@ -299,7 +299,7 @@ func (c *RepoCache) writeBugCache() error { aux := struct { Version uint - Excerpts map[string]*BugExcerpt + Excerpts map[entity.Id]*BugExcerpt }{ Version: formatVersion, Excerpts: c.bugExcerpts, @@ -331,7 +331,7 @@ func (c *RepoCache) writeIdentityCache() error { aux := struct { Version uint - Excerpts map[string]*IdentityExcerpt + Excerpts map[entity.Id]*IdentityExcerpt }{ Version: formatVersion, Excerpts: c.identitiesExcerpts, @@ -368,7 +368,7 @@ func identityCacheFilePath(repo repository.Repo) string { func (c *RepoCache) buildCache() error { _, _ = fmt.Fprintf(os.Stderr, "Building identity cache... ") - c.identitiesExcerpts = make(map[string]*IdentityExcerpt) + c.identitiesExcerpts = make(map[entity.Id]*IdentityExcerpt) allIdentities := identity.ReadAllLocalIdentities(c.repo) @@ -384,7 +384,7 @@ func (c *RepoCache) buildCache() error { _, _ = fmt.Fprintf(os.Stderr, "Building bug cache... ") - c.bugExcerpts = make(map[string]*BugExcerpt) + c.bugExcerpts = make(map[entity.Id]*BugExcerpt) allBugs := bug.ReadAllLocalBugs(c.repo) @@ -402,7 +402,7 @@ func (c *RepoCache) buildCache() error { } // ResolveBug retrieve a bug matching the exact given id -func (c *RepoCache) ResolveBug(id string) (*BugCache, error) { +func (c *RepoCache) ResolveBug(id entity.Id) (*BugCache, error) { cached, ok := c.bugs[id] if ok { return cached, nil @@ -420,7 +420,7 @@ func (c *RepoCache) ResolveBug(id string) (*BugCache, error) { } // ResolveBugExcerpt retrieve a BugExcerpt matching the exact given id -func (c *RepoCache) ResolveBugExcerpt(id string) (*BugExcerpt, error) { +func (c *RepoCache) ResolveBugExcerpt(id entity.Id) (*BugExcerpt, error) { e, ok := c.bugExcerpts[id] if !ok { return nil, bug.ErrBugNotExist @@ -433,16 +433,16 @@ func (c *RepoCache) ResolveBugExcerpt(id string) (*BugExcerpt, error) { // bugs match. func (c *RepoCache) ResolveBugPrefix(prefix string) (*BugCache, error) { // preallocate but empty - matching := make([]string, 0, 5) + matching := make([]entity.Id, 0, 5) for id := range c.bugExcerpts { - if strings.HasPrefix(id, prefix) { + if id.HasPrefix(prefix) { matching = append(matching, id) } } if len(matching) > 1 { - return nil, bug.ErrMultipleMatch{Matching: matching} + return nil, bug.NewErrMultipleMatchBug(matching) } if len(matching) == 0 { @@ -457,7 +457,7 @@ func (c *RepoCache) ResolveBugPrefix(prefix string) (*BugCache, error) { // match. func (c *RepoCache) ResolveBugCreateMetadata(key string, value string) (*BugCache, error) { // preallocate but empty - matching := make([]string, 0, 5) + matching := make([]entity.Id, 0, 5) for id, excerpt := range c.bugExcerpts { if excerpt.CreateMetadata[key] == value { @@ -466,7 +466,7 @@ func (c *RepoCache) ResolveBugCreateMetadata(key string, value string) (*BugCach } if len(matching) > 1 { - return nil, bug.ErrMultipleMatch{Matching: matching} + return nil, bug.NewErrMultipleMatchBug(matching) } if len(matching) == 0 { @@ -477,7 +477,7 @@ func (c *RepoCache) ResolveBugCreateMetadata(key string, value string) (*BugCach } // QueryBugs return the id of all Bug matching the given Query -func (c *RepoCache) QueryBugs(query *Query) []string { +func (c *RepoCache) QueryBugs(query *Query) []entity.Id { if query == nil { return c.AllBugsIds() } @@ -509,7 +509,7 @@ func (c *RepoCache) QueryBugs(query *Query) []string { sort.Sort(sorter) - result := make([]string, len(filtered)) + result := make([]entity.Id, len(filtered)) for i, val := range filtered { result[i] = val.Id @@ -519,8 +519,8 @@ func (c *RepoCache) QueryBugs(query *Query) []string { } // AllBugsIds return all known bug ids -func (c *RepoCache) AllBugsIds() []string { - result := make([]string, len(c.bugExcerpts)) +func (c *RepoCache) AllBugsIds() []entity.Id { + result := make([]entity.Id, len(c.bugExcerpts)) i := 0 for _, excerpt := range c.bugExcerpts { @@ -778,7 +778,7 @@ func repoIsAvailable(repo repository.Repo) error { } // ResolveIdentity retrieve an identity matching the exact given id -func (c *RepoCache) ResolveIdentity(id string) (*IdentityCache, error) { +func (c *RepoCache) ResolveIdentity(id entity.Id) (*IdentityCache, error) { cached, ok := c.identities[id] if ok { return cached, nil @@ -796,7 +796,7 @@ func (c *RepoCache) ResolveIdentity(id string) (*IdentityCache, error) { } // ResolveIdentityExcerpt retrieve a IdentityExcerpt matching the exact given id -func (c *RepoCache) ResolveIdentityExcerpt(id string) (*IdentityExcerpt, error) { +func (c *RepoCache) ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error) { e, ok := c.identitiesExcerpts[id] if !ok { return nil, identity.ErrIdentityNotExist @@ -809,16 +809,16 @@ func (c *RepoCache) ResolveIdentityExcerpt(id string) (*IdentityExcerpt, error) // It fails if multiple identities match. func (c *RepoCache) ResolveIdentityPrefix(prefix string) (*IdentityCache, error) { // preallocate but empty - matching := make([]string, 0, 5) + matching := make([]entity.Id, 0, 5) for id := range c.identitiesExcerpts { - if strings.HasPrefix(id, prefix) { + if id.HasPrefix(prefix) { matching = append(matching, id) } } if len(matching) > 1 { - return nil, identity.ErrMultipleMatch{Matching: matching} + return nil, identity.NewErrMultipleMatch(matching) } if len(matching) == 0 { @@ -832,7 +832,7 @@ func (c *RepoCache) ResolveIdentityPrefix(prefix string) (*IdentityCache, error) // one of it's version. If multiple version have the same key, the first defined take precedence. func (c *RepoCache) ResolveIdentityImmutableMetadata(key string, value string) (*IdentityCache, error) { // preallocate but empty - matching := make([]string, 0, 5) + matching := make([]entity.Id, 0, 5) for id, i := range c.identitiesExcerpts { if i.ImmutableMetadata[key] == value { @@ -841,7 +841,7 @@ func (c *RepoCache) ResolveIdentityImmutableMetadata(key string, value string) ( } if len(matching) > 1 { - return nil, identity.ErrMultipleMatch{Matching: matching} + return nil, identity.NewErrMultipleMatch(matching) } if len(matching) == 0 { @@ -852,8 +852,8 @@ func (c *RepoCache) ResolveIdentityImmutableMetadata(key string, value string) ( } // AllIdentityIds return all known identity ids -func (c *RepoCache) AllIdentityIds() []string { - result := make([]string, len(c.identitiesExcerpts)) +func (c *RepoCache) AllIdentityIds() []entity.Id { + result := make([]entity.Id, len(c.identitiesExcerpts)) i := 0 for _, excerpt := range c.identitiesExcerpts { diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go index 3e81674a86917a27949b816a249ddb2fa9ad7c8c..c3bd3cc48ec2350890cd29e3eb39d56585bb6647 100644 --- a/cache/repo_cache_test.go +++ b/cache/repo_cache_test.go @@ -57,14 +57,14 @@ func TestCache(t *testing.T) { require.NoError(t, err) _, err = cache.ResolveIdentityExcerpt(iden1.Id()) require.NoError(t, err) - _, err = cache.ResolveIdentityPrefix(iden1.Id()[:10]) + _, err = cache.ResolveIdentityPrefix(iden1.Id().String()[:10]) require.NoError(t, err) _, err = cache.ResolveBug(bug1.Id()) require.NoError(t, err) _, err = cache.ResolveBugExcerpt(bug1.Id()) require.NoError(t, err) - _, err = cache.ResolveBugPrefix(bug1.Id()[:10]) + _, err = cache.ResolveBugPrefix(bug1.Id().String()[:10]) require.NoError(t, err) // Querying @@ -91,14 +91,14 @@ func TestCache(t *testing.T) { require.NoError(t, err) _, err = cache.ResolveIdentityExcerpt(iden1.Id()) require.NoError(t, err) - _, err = cache.ResolveIdentityPrefix(iden1.Id()[:10]) + _, err = cache.ResolveIdentityPrefix(iden1.Id().String()[:10]) require.NoError(t, err) _, err = cache.ResolveBug(bug1.Id()) require.NoError(t, err) _, err = cache.ResolveBugExcerpt(bug1.Id()) require.NoError(t, err) - _, err = cache.ResolveBugPrefix(bug1.Id()[:10]) + _, err = cache.ResolveBugPrefix(bug1.Id().String()[:10]) require.NoError(t, err) } diff --git a/commands/add.go b/commands/add.go index d199b4c74ff679867a693a1097edbf682d7195cb..ff4f9529cd81633eb4b11d62725f04b4884f50e6 100644 --- a/commands/add.go +++ b/commands/add.go @@ -49,7 +49,7 @@ func runAddBug(cmd *cobra.Command, args []string) error { return err } - fmt.Printf("%s created\n", b.HumanId()) + fmt.Printf("%s created\n", b.Id().Human()) return nil } diff --git a/commands/comment.go b/commands/comment.go index cb9f36916da899dafe57f901efa945f2cf261c06..33bae65d1a521733bb1eaad0c8c6334fd6595ffa 100644 --- a/commands/comment.go +++ b/commands/comment.go @@ -39,7 +39,7 @@ func commentsTextOutput(comments []bug.Comment) { } fmt.Printf("Author: %s\n", colors.Magenta(comment.Author.DisplayName())) - fmt.Printf("Id: %s\n", colors.Cyan(comment.HumanId())) + fmt.Printf("Id: %s\n", colors.Cyan(comment.Id().Human())) fmt.Printf("Date: %s\n\n", comment.FormatTime()) fmt.Println(text.LeftPad(comment.Message, 4)) } diff --git a/commands/ls-id.go b/commands/ls-id.go index 47f29792e79b3887ed4dccceb006116fa4c7513d..22357eb4834d9c45348de5835bfc53ae06a9ee03 100644 --- a/commands/ls-id.go +++ b/commands/ls-id.go @@ -2,11 +2,11 @@ package commands import ( "fmt" - "strings" + + "github.com/spf13/cobra" "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) func runLsID(cmd *cobra.Command, args []string) error { @@ -24,7 +24,7 @@ func runLsID(cmd *cobra.Command, args []string) error { } for _, id := range backend.AllBugsIds() { - if prefix == "" || strings.HasPrefix(id, prefix) { + if prefix == "" || id.HasPrefix(prefix) { fmt.Println(id) } } diff --git a/commands/ls.go b/commands/ls.go index 94f0d0d6855c5ba406845a0a3988032b73f20ac1..9c32642e1ae9e5da2e1c63b28c274515e3879030 100644 --- a/commands/ls.go +++ b/commands/ls.go @@ -70,7 +70,7 @@ func runLsBug(cmd *cobra.Command, args []string) error { authorFmt := text.LeftPadMaxLine(name, 15, 0) fmt.Printf("%s %s\t%s\t%s\tC:%d L:%d\n", - colors.Cyan(b.HumanId()), + colors.Cyan(b.Id.Human()), colors.Yellow(b.Status), titleFmt, colors.Magenta(authorFmt), diff --git a/commands/pull.go b/commands/pull.go index 5df10dca8db575420812ecb6c36744cb734991fe..0439ab41cdbaf1f1c78286d31bb13500b622b825 100644 --- a/commands/pull.go +++ b/commands/pull.go @@ -4,11 +4,11 @@ import ( "errors" "fmt" - "github.com/MichaelMure/git-bug/bug" + "github.com/spf13/cobra" + "github.com/MichaelMure/git-bug/cache" "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/util/interrupt" - "github.com/spf13/cobra" ) func runPull(cmd *cobra.Command, args []string) error { @@ -45,7 +45,7 @@ func runPull(cmd *cobra.Command, args []string) error { } if result.Status != entity.MergeStatusNothing { - fmt.Printf("%s: %s\n", bug.FormatHumanID(result.Id), result) + fmt.Printf("%s: %s\n", result.Id.Human(), result) } } diff --git a/commands/select.go b/commands/select.go index efe48b5e09ef646ff0dcb4cca3842e46bba072f8..7c40df5c9ebf58ba46497b555852b864ffbe27a4 100644 --- a/commands/select.go +++ b/commands/select.go @@ -34,7 +34,7 @@ func runSelect(cmd *cobra.Command, args []string) error { return err } - fmt.Printf("selected bug %s: %s\n", b.HumanId(), b.Snapshot().Title) + fmt.Printf("selected bug %s: %s\n", b.Id().Human(), b.Snapshot().Title) return nil } diff --git a/commands/select/select.go b/commands/select/select.go index b080d277013e6b6df457824f776de9eb1670b5f3..fdc87154bfc3abcdd1f33ebb2a6117f943667077 100644 --- a/commands/select/select.go +++ b/commands/select/select.go @@ -7,11 +7,12 @@ import ( "os" "path" + "github.com/pkg/errors" + "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/repository" - "github.com/MichaelMure/git-bug/util/git" - "github.com/pkg/errors" ) const selectFile = "select" @@ -69,7 +70,7 @@ func ResolveBug(repo *cache.RepoCache, args []string) (*cache.BugCache, []string } // Select will select a bug for future use -func Select(repo *cache.RepoCache, id string) error { +func Select(repo *cache.RepoCache, id entity.Id) error { selectPath := selectFilePath(repo) f, err := os.OpenFile(selectPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) @@ -77,7 +78,7 @@ func Select(repo *cache.RepoCache, id string) error { return err } - _, err = f.WriteString(id) + _, err = f.WriteString(id.String()) if err != nil { return err } @@ -112,8 +113,8 @@ func selected(repo *cache.RepoCache) (*cache.BugCache, error) { return nil, fmt.Errorf("the select file should be < 100 bytes") } - h := git.Hash(buf) - if !h.IsValid() { + id := entity.Id(buf) + if err := id.Validate(); err != nil { err = os.Remove(selectPath) if err != nil { return nil, errors.Wrap(err, "error while removing invalid select file") @@ -122,7 +123,7 @@ func selected(repo *cache.RepoCache) (*cache.BugCache, error) { return nil, fmt.Errorf("select file in invalid, removing it") } - b, err := repo.ResolveBug(string(h)) + b, err := repo.ResolveBug(id) if err != nil { return nil, err } diff --git a/commands/select/select_test.go b/commands/select/select_test.go index 393ec88b845e308fbcb711a6e291aec649f57cf0..989d6b3cf0658b25af85aaf164959510532fd8ae 100644 --- a/commands/select/select_test.go +++ b/commands/select/select_test.go @@ -52,12 +52,12 @@ func TestSelect(t *testing.T) { require.Equal(t, b1.Id(), b3.Id()) // override selection with same id - b4, _, err := ResolveBug(repoCache, []string{b1.Id()}) + b4, _, err := ResolveBug(repoCache, []string{b1.Id().String()}) require.NoError(t, err) require.Equal(t, b1.Id(), b4.Id()) // override selection with a prefix - b5, _, err := ResolveBug(repoCache, []string{b1.HumanId()}) + b5, _, err := ResolveBug(repoCache, []string{b1.Id().Human()}) require.NoError(t, err) require.Equal(t, b1.Id(), b5.Id()) @@ -67,7 +67,7 @@ func TestSelect(t *testing.T) { require.Equal(t, b1.Id(), b6.Id()) // override with a different id - b7, _, err := ResolveBug(repoCache, []string{b2.Id()}) + b7, _, err := ResolveBug(repoCache, []string{b2.Id().String()}) require.NoError(t, err) require.Equal(t, b2.Id(), b7.Id()) diff --git a/commands/show.go b/commands/show.go index 41dc5851a0ef34770f5ff6bfd9d0c77a68bf49f5..0bb3dc4a416bffc3abc0fbb7d1bd72172aec2cbc 100644 --- a/commands/show.go +++ b/commands/show.go @@ -46,7 +46,7 @@ func runShowBug(cmd *cobra.Command, args []string) error { case "createTime": fmt.Printf("%s\n", firstComment.FormatTime()) case "humanId": - fmt.Printf("%s\n", snapshot.HumanId()) + fmt.Printf("%s\n", snapshot.Id().Human()) case "id": fmt.Printf("%s\n", snapshot.Id()) case "labels": @@ -62,7 +62,7 @@ func runShowBug(cmd *cobra.Command, args []string) error { fmt.Printf("%s\n", p.DisplayName()) } case "shortId": - fmt.Printf("%s\n", snapshot.HumanId()) + fmt.Printf("%s\n", snapshot.Id().Human()) case "status": fmt.Printf("%s\n", snapshot.Status) case "title": @@ -77,7 +77,7 @@ func runShowBug(cmd *cobra.Command, args []string) error { // Header fmt.Printf("[%s] %s %s\n\n", colors.Yellow(snapshot.Status), - colors.Cyan(snapshot.HumanId()), + colors.Cyan(snapshot.Id().Human()), snapshot.Title, ) diff --git a/commands/user.go b/commands/user.go index 8cb64491da94f9ac25305f6af2b08f31e7e2a6bb..254abf2f08dbd8daeb0eddf16b7307ad2d0fc3ce 100644 --- a/commands/user.go +++ b/commands/user.go @@ -41,7 +41,7 @@ func runUser(cmd *cobra.Command, args []string) error { case "email": fmt.Printf("%s\n", id.Email()) case "humanId": - fmt.Printf("%s\n", id.HumanId()) + fmt.Printf("%s\n", id.Id().Human()) case "id": fmt.Printf("%s\n", id.Id()) case "lastModification": diff --git a/commands/user_ls.go b/commands/user_ls.go index 23ecea73457a0c9603b8414ad87eba3f723b7dae..609ff5a4170b6ca41bf0391f2b7c4269cd2bd774 100644 --- a/commands/user_ls.go +++ b/commands/user_ls.go @@ -24,7 +24,7 @@ func runUserLs(cmd *cobra.Command, args []string) error { } fmt.Printf("%s %s\n", - colors.Cyan(i.HumanId()), + colors.Cyan(i.Id.Human()), i.DisplayName(), ) } diff --git a/entity/err.go b/entity/err.go new file mode 100644 index 0000000000000000000000000000000000000000..7022305cf252e3c1fdd2cce8fff6c43cf7450118 --- /dev/null +++ b/entity/err.go @@ -0,0 +1,27 @@ +package entity + +import ( + "fmt" + "strings" +) + +type ErrMultipleMatch struct { + entityType string + Matching []Id +} + +func NewErrMultipleMatch(entityType string, matching []Id) *ErrMultipleMatch { + return &ErrMultipleMatch{entityType: entityType, Matching: matching} +} + +func (e ErrMultipleMatch) Error() string { + matching := make([]string, len(e.Matching)) + + for i, match := range e.Matching { + matching[i] = match.String() + } + + return fmt.Sprintf("Multiple matching %s found:\n%s", + e.entityType, + strings.Join(matching, "\n")) +} diff --git a/entity/id.go b/entity/id.go new file mode 100644 index 0000000000000000000000000000000000000000..7ff6b2232c6462f71785ce1f5155b19cc88843e1 --- /dev/null +++ b/entity/id.go @@ -0,0 +1,67 @@ +package entity + +import ( + "fmt" + "io" + "strings" + + "github.com/pkg/errors" +) + +const IdLengthSHA1 = 40 +const IdLengthSHA256 = 64 +const humanIdLength = 7 + +const UnsetId = Id("unset") + +// Id is an identifier for an entity or part of an entity +type Id string + +// String return the identifier as a string +func (i Id) String() string { + return string(i) +} + +// Human return the identifier, shortened for human consumption +func (i Id) Human() string { + format := fmt.Sprintf("%%.%ds", humanIdLength) + return fmt.Sprintf(format, i) +} + +func (i Id) HasPrefix(prefix string) bool { + return strings.HasPrefix(string(i), prefix) +} + +// UnmarshalGQL implement the Unmarshaler interface for gqlgen +func (i *Id) UnmarshalGQL(v interface{}) error { + _, ok := v.(string) + if !ok { + return fmt.Errorf("IDs must be strings") + } + + *i = v.(Id) + + if err := i.Validate(); err != nil { + return errors.Wrap(err, "invalid ID") + } + + return nil +} + +// MarshalGQL implement the Marshaler interface for gqlgen +func (i Id) MarshalGQL(w io.Writer) { + _, _ = w.Write([]byte(`"` + i.String() + `"`)) +} + +// IsValid tell if the Id is valid +func (i Id) Validate() error { + if len(i) != IdLengthSHA1 && len(i) != IdLengthSHA256 { + return fmt.Errorf("invalid length") + } + for _, r := range i { + if (r < 'a' || r > 'z') && (r < '0' || r > '9') { + return fmt.Errorf("invalid character") + } + } + return nil +} diff --git a/entity/interface.go b/entity/interface.go index 62b92a582af0af4e22d88a73d733550c44dc61fa..dd5d69b1edf13a2a80df417ab4f66ac780af7864 100644 --- a/entity/interface.go +++ b/entity/interface.go @@ -2,7 +2,5 @@ package entity type Interface interface { // Id return the Entity identifier - Id() string - // HumanId return the Entity identifier truncated for human consumption - HumanId() string + Id() Id } diff --git a/entity/merge.go b/entity/merge.go index 544f21683b0070f8fb1be861c211f9527f77c43d..7c3e71c846608af18834c2c9be1978731f073f3c 100644 --- a/entity/merge.go +++ b/entity/merge.go @@ -1,6 +1,8 @@ package entity -import "fmt" +import ( + "fmt" +) // MergeStatus represent the result of a merge operation of an entity type MergeStatus int @@ -17,7 +19,7 @@ type MergeResult struct { // Err is set when a terminal error occur in the process Err error - Id string + Id Id Status MergeStatus // Only set for invalid status @@ -42,14 +44,14 @@ func (mr MergeResult) String() string { } } -func NewMergeError(err error, id string) MergeResult { +func NewMergeError(err error, id Id) MergeResult { return MergeResult{ Err: err, Id: id, } } -func NewMergeStatus(status MergeStatus, id string, entity Interface) MergeResult { +func NewMergeStatus(status MergeStatus, id Id, entity Interface) MergeResult { return MergeResult{ Id: id, Status: status, @@ -59,7 +61,7 @@ func NewMergeStatus(status MergeStatus, id string, entity Interface) MergeResult } } -func NewMergeInvalidStatus(id string, reason string) MergeResult { +func NewMergeInvalidStatus(id Id, reason string) MergeResult { return MergeResult{ Id: id, Status: MergeStatusInvalid, diff --git a/graphql/connections/connections.go b/graphql/connections/connections.go index d54a506867686e6f6b69170029b83d850799d345..d82c562023af7ebc40e78877d7cf3fe118cc0db9 100644 --- a/graphql/connections/connections.go +++ b/graphql/connections/connections.go @@ -1,5 +1,5 @@ -//go:generate genny -in=connection_template.go -out=gen_lazy_bug.go gen "Name=LazyBug NodeType=string EdgeType=LazyBugEdge ConnectionType=models.BugConnection" -//go:generate genny -in=connection_template.go -out=gen_lazy_identity.go gen "Name=LazyIdentity NodeType=string EdgeType=LazyIdentityEdge ConnectionType=models.IdentityConnection" +//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_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" diff --git a/graphql/connections/gen_lazy_bug.go b/graphql/connections/gen_lazy_bug.go index 6c9eb0121a7108badf6a7d0c13529400482b48ac..9638e86b82b94613ed100426db4a790b927c1a0d 100644 --- a/graphql/connections/gen_lazy_bug.go +++ b/graphql/connections/gen_lazy_bug.go @@ -7,23 +7,24 @@ package connections import ( "fmt" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/graphql/models" ) -// StringEdgeMaker define a function that take a string and an offset and +// EntityIdEdgeMaker define a function that take a entity.Id and an offset and // create an Edge. -type LazyBugEdgeMaker func(value string, offset int) Edge +type LazyBugEdgeMaker func(value entity.Id, offset int) Edge // LazyBugConMaker define a function that create a models.BugConnection type LazyBugConMaker func( edges []*LazyBugEdge, - nodes []string, + nodes []entity.Id, info *models.PageInfo, totalCount int) (*models.BugConnection, error) // LazyBugCon will paginate a source according to the input of a relay connection -func LazyBugCon(source []string, edgeMaker LazyBugEdgeMaker, conMaker LazyBugConMaker, input models.ConnectionInput) (*models.BugConnection, error) { - var nodes []string +func LazyBugCon(source []entity.Id, edgeMaker LazyBugEdgeMaker, conMaker LazyBugConMaker, input models.ConnectionInput) (*models.BugConnection, error) { + var nodes []entity.Id var edges []*LazyBugEdge var cursors []string var pageInfo = &models.PageInfo{} diff --git a/graphql/connections/gen_lazy_identity.go b/graphql/connections/gen_lazy_identity.go index 96461be5da4e5d400ee53232fd0dbdb5fa4efe1e..932d802c81f8bc5c4402c02acbaff29e7765ae68 100644 --- a/graphql/connections/gen_lazy_identity.go +++ b/graphql/connections/gen_lazy_identity.go @@ -7,23 +7,24 @@ package connections import ( "fmt" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/graphql/models" ) -// StringEdgeMaker define a function that take a string and an offset and +// EntityIdEdgeMaker define a function that take a entity.Id and an offset and // create an Edge. -type LazyIdentityEdgeMaker func(value string, offset int) Edge +type LazyIdentityEdgeMaker func(value entity.Id, offset int) Edge // LazyIdentityConMaker define a function that create a models.IdentityConnection type LazyIdentityConMaker func( edges []*LazyIdentityEdge, - nodes []string, + nodes []entity.Id, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) // LazyIdentityCon will paginate a source according to the input of a relay connection -func LazyIdentityCon(source []string, edgeMaker LazyIdentityEdgeMaker, conMaker LazyIdentityConMaker, input models.ConnectionInput) (*models.IdentityConnection, error) { - var nodes []string +func LazyIdentityCon(source []entity.Id, edgeMaker LazyIdentityEdgeMaker, conMaker LazyIdentityConMaker, input models.ConnectionInput) (*models.IdentityConnection, error) { + var nodes []entity.Id var edges []*LazyIdentityEdge var cursors []string var pageInfo = &models.PageInfo{} diff --git a/graphql/connections/lazy_bug.go b/graphql/connections/lazy_bug.go index 24eda0b6d7ca35a2cc62ed6db788e1d8eb142b63..00692e8b669701dcf32d11329b1a5db5a71a7cc2 100644 --- a/graphql/connections/lazy_bug.go +++ b/graphql/connections/lazy_bug.go @@ -1,8 +1,10 @@ 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 string + Id entity.Id Cursor string } diff --git a/graphql/connections/lazy_identity.go b/graphql/connections/lazy_identity.go index 34ba579a6fe9cb13c6fcf0249d83c60a0538ac2a..3274dd7e418524a82a18198fc7380093ac00c24e 100644 --- a/graphql/connections/lazy_identity.go +++ b/graphql/connections/lazy_identity.go @@ -1,8 +1,10 @@ package connections +import "github.com/MichaelMure/git-bug/entity" + // LazyIdentityEdge is a special relay edge used to implement a lazy loading connection type LazyIdentityEdge struct { - Id string + Id entity.Id Cursor string } diff --git a/graphql/graph/gen_graph.go b/graphql/graph/gen_graph.go index d6d33e9328931d3e25682c6c1a7140cd111b09c5..c052e57b6f586346569143ac3c05f04b1b5cabec 100644 --- a/graphql/graph/gen_graph.go +++ b/graphql/graph/gen_graph.go @@ -71,7 +71,7 @@ type ComplexityRoot struct { Author func(childComplexity int) int Date func(childComplexity int) int Files func(childComplexity int) int - Hash func(childComplexity int) int + ID func(childComplexity int) int Message func(childComplexity int) int } @@ -86,8 +86,8 @@ type ComplexityRoot struct { CreatedAt func(childComplexity int) int Edited func(childComplexity int) int Files func(childComplexity int) int - Hash func(childComplexity int) int History func(childComplexity int) int + ID func(childComplexity int) int LastEdit func(childComplexity int) int Message func(childComplexity int) int MessageIsEmpty func(childComplexity int) int @@ -98,8 +98,8 @@ type ComplexityRoot struct { Author func(childComplexity int) int Comments func(childComplexity int, after *string, before *string, first *int, last *int) int CreatedAt func(childComplexity int) int - HumanId func(childComplexity int) int - Id func(childComplexity int) int + HumanID func(childComplexity int) int + ID func(childComplexity int) int Labels func(childComplexity int) int LastEdit func(childComplexity int) int Operations func(childComplexity int, after *string, before *string, first *int, last *int) int @@ -177,7 +177,7 @@ type ComplexityRoot struct { Author func(childComplexity int) int Date func(childComplexity int) int Files func(childComplexity int) int - Hash func(childComplexity int) int + ID func(childComplexity int) int Message func(childComplexity int) int Title func(childComplexity int) int } @@ -187,8 +187,8 @@ type ComplexityRoot struct { CreatedAt func(childComplexity int) int Edited func(childComplexity int) int Files func(childComplexity int) int - Hash func(childComplexity int) int History func(childComplexity int) int + ID func(childComplexity int) int LastEdit func(childComplexity int) int Message func(childComplexity int) int MessageIsEmpty func(childComplexity int) int @@ -198,7 +198,7 @@ type ComplexityRoot struct { Author func(childComplexity int) int Date func(childComplexity int) int Files func(childComplexity int) int - Hash func(childComplexity int) int + ID func(childComplexity int) int Message func(childComplexity int) int Target func(childComplexity int) int } @@ -235,7 +235,7 @@ type ComplexityRoot struct { Added func(childComplexity int) int Author func(childComplexity int) int Date func(childComplexity int) int - Hash func(childComplexity int) int + ID func(childComplexity int) int Removed func(childComplexity int) int } @@ -248,7 +248,7 @@ type ComplexityRoot struct { Added func(childComplexity int) int Author func(childComplexity int) int Date func(childComplexity int) int - Hash func(childComplexity int) int + ID func(childComplexity int) int Removed func(childComplexity int) int } @@ -311,21 +311,21 @@ type ComplexityRoot struct { SetStatusOperation struct { Author func(childComplexity int) int Date func(childComplexity int) int - Hash func(childComplexity int) int + ID func(childComplexity int) int Status func(childComplexity int) int } SetStatusTimelineItem struct { Author func(childComplexity int) int Date func(childComplexity int) int - Hash func(childComplexity int) int + ID func(childComplexity int) int Status func(childComplexity int) int } SetTitleOperation struct { Author func(childComplexity int) int Date func(childComplexity int) int - Hash func(childComplexity int) int + ID func(childComplexity int) int Title func(childComplexity int) int Was func(childComplexity int) int } @@ -339,7 +339,7 @@ type ComplexityRoot struct { SetTitleTimelineItem struct { Author func(childComplexity int) int Date func(childComplexity int) int - Hash func(childComplexity int) int + ID func(childComplexity int) int Title func(childComplexity int) int Was func(childComplexity int) int } @@ -358,13 +358,19 @@ type ComplexityRoot struct { } type AddCommentOperationResolver interface { + ID(ctx context.Context, obj *bug.AddCommentOperation) (string, error) + Date(ctx context.Context, obj *bug.AddCommentOperation) (*time.Time, error) } type AddCommentTimelineItemResolver interface { + ID(ctx context.Context, obj *bug.AddCommentTimelineItem) (string, 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) LastEdit(ctx context.Context, obj *bug.Snapshot) (*time.Time, error) @@ -383,14 +389,21 @@ 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) + Date(ctx context.Context, obj *bug.CreateOperation) (*time.Time, error) } type CreateTimelineItemResolver interface { + ID(ctx context.Context, obj *bug.CreateTimelineItem) (string, 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) + 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) @@ -407,12 +420,16 @@ type LabelResolver interface { Color(ctx context.Context, obj *bug.Label) (*color.RGBA, error) } type LabelChangeOperationResolver interface { + ID(ctx context.Context, obj *bug.LabelChangeOperation) (string, error) + Date(ctx context.Context, obj *bug.LabelChangeOperation) (*time.Time, error) } type LabelChangeResultResolver interface { Status(ctx context.Context, obj *bug.LabelChangeResult) (models.LabelChangeStatus, error) } type LabelChangeTimelineItemResolver interface { + ID(ctx context.Context, obj *bug.LabelChangeTimelineItem) (string, error) + Date(ctx context.Context, obj *bug.LabelChangeTimelineItem) (*time.Time, error) } type MutationResolver interface { @@ -438,17 +455,25 @@ type RepositoryResolver interface { ValidLabels(ctx context.Context, obj *models.Repository) ([]bug.Label, error) } type SetStatusOperationResolver interface { + ID(ctx context.Context, obj *bug.SetStatusOperation) (string, 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) + 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) + Date(ctx context.Context, obj *bug.SetTitleOperation) (*time.Time, error) } type SetTitleTimelineItemResolver interface { + ID(ctx context.Context, obj *bug.SetTitleTimelineItem) (string, error) + Date(ctx context.Context, obj *bug.SetTitleTimelineItem) (*time.Time, error) } @@ -488,12 +513,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.AddCommentOperation.Files(childComplexity), true - case "AddCommentOperation.hash": - if e.complexity.AddCommentOperation.Hash == nil { + case "AddCommentOperation.id": + if e.complexity.AddCommentOperation.ID == nil { break } - return e.complexity.AddCommentOperation.Hash(childComplexity), true + return e.complexity.AddCommentOperation.ID(childComplexity), true case "AddCommentOperation.message": if e.complexity.AddCommentOperation.Message == nil { @@ -551,19 +576,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.AddCommentTimelineItem.Files(childComplexity), true - case "AddCommentTimelineItem.hash": - if e.complexity.AddCommentTimelineItem.Hash == nil { + case "AddCommentTimelineItem.history": + if e.complexity.AddCommentTimelineItem.History == nil { break } - return e.complexity.AddCommentTimelineItem.Hash(childComplexity), true + return e.complexity.AddCommentTimelineItem.History(childComplexity), true - case "AddCommentTimelineItem.history": - if e.complexity.AddCommentTimelineItem.History == nil { + case "AddCommentTimelineItem.id": + if e.complexity.AddCommentTimelineItem.ID == nil { break } - return e.complexity.AddCommentTimelineItem.History(childComplexity), true + return e.complexity.AddCommentTimelineItem.ID(childComplexity), true case "AddCommentTimelineItem.lastEdit": if e.complexity.AddCommentTimelineItem.LastEdit == nil { @@ -625,18 +650,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Bug.CreatedAt(childComplexity), true case "Bug.humanId": - if e.complexity.Bug.HumanId == nil { + if e.complexity.Bug.HumanID == nil { break } - return e.complexity.Bug.HumanId(childComplexity), true + return e.complexity.Bug.HumanID(childComplexity), true case "Bug.id": - if e.complexity.Bug.Id == nil { + if e.complexity.Bug.ID == nil { break } - return e.complexity.Bug.Id(childComplexity), true + return e.complexity.Bug.ID(childComplexity), true case "Bug.labels": if e.complexity.Bug.Labels == nil { @@ -940,12 +965,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.CreateOperation.Files(childComplexity), true - case "CreateOperation.hash": - if e.complexity.CreateOperation.Hash == nil { + case "CreateOperation.id": + if e.complexity.CreateOperation.ID == nil { break } - return e.complexity.CreateOperation.Hash(childComplexity), true + return e.complexity.CreateOperation.ID(childComplexity), true case "CreateOperation.message": if e.complexity.CreateOperation.Message == nil { @@ -989,19 +1014,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.CreateTimelineItem.Files(childComplexity), true - case "CreateTimelineItem.hash": - if e.complexity.CreateTimelineItem.Hash == nil { + case "CreateTimelineItem.history": + if e.complexity.CreateTimelineItem.History == nil { break } - return e.complexity.CreateTimelineItem.Hash(childComplexity), true + return e.complexity.CreateTimelineItem.History(childComplexity), true - case "CreateTimelineItem.history": - if e.complexity.CreateTimelineItem.History == nil { + case "CreateTimelineItem.id": + if e.complexity.CreateTimelineItem.ID == nil { break } - return e.complexity.CreateTimelineItem.History(childComplexity), true + return e.complexity.CreateTimelineItem.ID(childComplexity), true case "CreateTimelineItem.lastEdit": if e.complexity.CreateTimelineItem.LastEdit == nil { @@ -1045,12 +1070,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.EditCommentOperation.Files(childComplexity), true - case "EditCommentOperation.hash": - if e.complexity.EditCommentOperation.Hash == nil { + case "EditCommentOperation.id": + if e.complexity.EditCommentOperation.ID == nil { break } - return e.complexity.EditCommentOperation.Hash(childComplexity), true + return e.complexity.EditCommentOperation.ID(childComplexity), true case "EditCommentOperation.message": if e.complexity.EditCommentOperation.Message == nil { @@ -1199,12 +1224,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.LabelChangeOperation.Date(childComplexity), true - case "LabelChangeOperation.hash": - if e.complexity.LabelChangeOperation.Hash == nil { + case "LabelChangeOperation.id": + if e.complexity.LabelChangeOperation.ID == nil { break } - return e.complexity.LabelChangeOperation.Hash(childComplexity), true + return e.complexity.LabelChangeOperation.ID(childComplexity), true case "LabelChangeOperation.removed": if e.complexity.LabelChangeOperation.Removed == nil { @@ -1248,12 +1273,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.LabelChangeTimelineItem.Date(childComplexity), true - case "LabelChangeTimelineItem.hash": - if e.complexity.LabelChangeTimelineItem.Hash == nil { + case "LabelChangeTimelineItem.id": + if e.complexity.LabelChangeTimelineItem.ID == nil { break } - return e.complexity.LabelChangeTimelineItem.Hash(childComplexity), true + return e.complexity.LabelChangeTimelineItem.ID(childComplexity), true case "LabelChangeTimelineItem.removed": if e.complexity.LabelChangeTimelineItem.Removed == nil { @@ -1565,12 +1590,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.SetStatusOperation.Date(childComplexity), true - case "SetStatusOperation.hash": - if e.complexity.SetStatusOperation.Hash == nil { + case "SetStatusOperation.id": + if e.complexity.SetStatusOperation.ID == nil { break } - return e.complexity.SetStatusOperation.Hash(childComplexity), true + return e.complexity.SetStatusOperation.ID(childComplexity), true case "SetStatusOperation.status": if e.complexity.SetStatusOperation.Status == nil { @@ -1593,12 +1618,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.SetStatusTimelineItem.Date(childComplexity), true - case "SetStatusTimelineItem.hash": - if e.complexity.SetStatusTimelineItem.Hash == nil { + case "SetStatusTimelineItem.id": + if e.complexity.SetStatusTimelineItem.ID == nil { break } - return e.complexity.SetStatusTimelineItem.Hash(childComplexity), true + return e.complexity.SetStatusTimelineItem.ID(childComplexity), true case "SetStatusTimelineItem.status": if e.complexity.SetStatusTimelineItem.Status == nil { @@ -1621,12 +1646,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.SetTitleOperation.Date(childComplexity), true - case "SetTitleOperation.hash": - if e.complexity.SetTitleOperation.Hash == nil { + case "SetTitleOperation.id": + if e.complexity.SetTitleOperation.ID == nil { break } - return e.complexity.SetTitleOperation.Hash(childComplexity), true + return e.complexity.SetTitleOperation.ID(childComplexity), true case "SetTitleOperation.title": if e.complexity.SetTitleOperation.Title == nil { @@ -1677,12 +1702,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.SetTitleTimelineItem.Date(childComplexity), true - case "SetTitleTimelineItem.hash": - if e.complexity.SetTitleTimelineItem.Hash == nil { + case "SetTitleTimelineItem.id": + if e.complexity.SetTitleTimelineItem.ID == nil { break } - return e.complexity.SetTitleTimelineItem.Hash(childComplexity), true + return e.complexity.SetTitleTimelineItem.ID(childComplexity), true case "SetTitleTimelineItem.title": if e.complexity.SetTitleTimelineItem.Title == nil { @@ -2126,8 +2151,8 @@ type CommitAsNeededPayload { `}, &ast.Source{Name: "schema/operations.graphql", Input: `"""An operation applied to a bug.""" interface Operation { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The operations author.""" author: Identity! """The datetime when this operation was issued.""" @@ -2153,8 +2178,8 @@ type OperationEdge { # Operations type CreateOperation implements Operation & Authored { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The author of this object.""" author: Identity! """The datetime when this operation was issued.""" @@ -2166,8 +2191,8 @@ type CreateOperation implements Operation & Authored { } type SetTitleOperation implements Operation & Authored { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The author of this object.""" author: Identity! """The datetime when this operation was issued.""" @@ -2178,8 +2203,8 @@ type SetTitleOperation implements Operation & Authored { } type AddCommentOperation implements Operation & Authored { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The author of this object.""" author: Identity! """The datetime when this operation was issued.""" @@ -2190,21 +2215,21 @@ type AddCommentOperation implements Operation & Authored { } type EditCommentOperation implements Operation & Authored { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The author of this object.""" author: Identity! """The datetime when this operation was issued.""" date: Time! - target: Hash! + target: String! message: String! files: [Hash!]! } type SetStatusOperation implements Operation & Authored { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The author of this object.""" author: Identity! """The datetime when this operation was issued.""" @@ -2214,8 +2239,8 @@ type SetStatusOperation implements Operation & Authored { } type LabelChangeOperation implements Operation & Authored { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The author of this object.""" author: Identity! """The datetime when this operation was issued.""" @@ -2291,8 +2316,8 @@ type Mutation { `}, &ast.Source{Name: "schema/timeline.graphql", Input: `"""An item in the timeline of events""" interface TimelineItem { - """The hash of the source operation""" - hash: Hash! + """The identifier of the source operation""" + id: String! } """CommentHistoryStep hold one version of a message in the history""" @@ -2321,8 +2346,8 @@ type TimelineItemEdge { """CreateTimelineItem is a TimelineItem that represent the creation of a bug and its message edition history""" type CreateTimelineItem implements TimelineItem & Authored { - """The hash of the source operation""" - hash: Hash! + """The identifier of the source operation""" + id: String! author: Identity! message: String! messageIsEmpty: Boolean! @@ -2335,8 +2360,8 @@ type CreateTimelineItem implements TimelineItem & Authored { """AddCommentTimelineItem is a TimelineItem that represent a Comment and its edition history""" type AddCommentTimelineItem implements TimelineItem & Authored { - """The hash of the source operation""" - hash: Hash! + """The identifier of the source operation""" + id: String! author: Identity! message: String! messageIsEmpty: Boolean! @@ -2349,8 +2374,8 @@ type AddCommentTimelineItem implements TimelineItem & Authored { """LabelChangeTimelineItem is a TimelineItem that represent a change in the labels of a bug""" type LabelChangeTimelineItem implements TimelineItem & Authored { - """The hash of the source operation""" - hash: Hash! + """The identifier of the source operation""" + id: String! author: Identity! date: Time! added: [Label!]! @@ -2359,8 +2384,8 @@ type LabelChangeTimelineItem implements TimelineItem & Authored { """SetStatusTimelineItem is a TimelineItem that represent a change in the status of a bug""" type SetStatusTimelineItem implements TimelineItem & Authored { - """The hash of the source operation""" - hash: Hash! + """The identifier of the source operation""" + id: String! author: Identity! date: Time! status: Status! @@ -2368,8 +2393,8 @@ type SetStatusTimelineItem implements TimelineItem & Authored { """LabelChangeTimelineItem is a TimelineItem that represent a change in the title of a bug""" type SetTitleTimelineItem implements TimelineItem & Authored { - """The hash of the source operation""" - hash: Hash! + """The identifier of the source operation""" + id: String! author: Identity! date: Time! title: String! @@ -2899,7 +2924,7 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg // region **************************** field.gotpl ***************************** -func (ec *executionContext) _AddCommentOperation_hash(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) (ret graphql.Marshaler) { +func (ec *executionContext) _AddCommentOperation_id(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { if r := recover(); r != nil { @@ -2918,7 +2943,7 @@ func (ec *executionContext) _AddCommentOperation_hash(ctx context.Context, field ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Hash() + return ec.resolvers.AddCommentOperation().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -2930,10 +2955,10 @@ func (ec *executionContext) _AddCommentOperation_hash(ctx context.Context, field } return graphql.Null } - res := resTmp.(git.Hash) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNHash2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _AddCommentOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) (ret graphql.Marshaler) { @@ -3192,7 +3217,7 @@ func (ec *executionContext) _AddCommentPayload_operation(ctx context.Context, fi return ec.marshalNAddCommentOperation2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐAddCommentOperation(ctx, field.Selections, res) } -func (ec *executionContext) _AddCommentTimelineItem_hash(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentTimelineItem) (ret graphql.Marshaler) { +func (ec *executionContext) _AddCommentTimelineItem_id(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentTimelineItem) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { if r := recover(); r != nil { @@ -3211,7 +3236,7 @@ func (ec *executionContext) _AddCommentTimelineItem_hash(ctx context.Context, fi ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Hash(), nil + return ec.resolvers.AddCommentTimelineItem().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -3223,10 +3248,10 @@ func (ec *executionContext) _AddCommentTimelineItem_hash(ctx context.Context, fi } return graphql.Null } - res := resTmp.(git.Hash) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNHash2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _AddCommentTimelineItem_author(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentTimelineItem) (ret graphql.Marshaler) { @@ -3544,7 +3569,7 @@ func (ec *executionContext) _Bug_id(ctx context.Context, field graphql.Collected ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Id(), nil + return ec.resolvers.Bug().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -3581,7 +3606,7 @@ func (ec *executionContext) _Bug_humanId(ctx context.Context, field graphql.Coll ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.HumanId(), nil + return ec.resolvers.Bug().HumanID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -5176,7 +5201,7 @@ func (ec *executionContext) _CommitPayload_bug(ctx context.Context, field graphq return ec.marshalNBug2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐSnapshot(ctx, field.Selections, res) } -func (ec *executionContext) _CreateOperation_hash(ctx context.Context, field graphql.CollectedField, obj *bug.CreateOperation) (ret graphql.Marshaler) { +func (ec *executionContext) _CreateOperation_id(ctx context.Context, field graphql.CollectedField, obj *bug.CreateOperation) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { if r := recover(); r != nil { @@ -5195,7 +5220,7 @@ func (ec *executionContext) _CreateOperation_hash(ctx context.Context, field gra ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Hash() + return ec.resolvers.CreateOperation().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -5207,10 +5232,10 @@ func (ec *executionContext) _CreateOperation_hash(ctx context.Context, field gra } return graphql.Null } - res := resTmp.(git.Hash) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNHash2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _CreateOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.CreateOperation) (ret graphql.Marshaler) { @@ -5398,7 +5423,7 @@ func (ec *executionContext) _CreateOperation_files(ctx context.Context, field gr return ec.marshalNHash2ᚕgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) } -func (ec *executionContext) _CreateTimelineItem_hash(ctx context.Context, field graphql.CollectedField, obj *bug.CreateTimelineItem) (ret graphql.Marshaler) { +func (ec *executionContext) _CreateTimelineItem_id(ctx context.Context, field graphql.CollectedField, obj *bug.CreateTimelineItem) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { if r := recover(); r != nil { @@ -5417,7 +5442,7 @@ func (ec *executionContext) _CreateTimelineItem_hash(ctx context.Context, field ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Hash(), nil + return ec.resolvers.CreateTimelineItem().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -5429,10 +5454,10 @@ func (ec *executionContext) _CreateTimelineItem_hash(ctx context.Context, field } return graphql.Null } - res := resTmp.(git.Hash) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNHash2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _CreateTimelineItem_author(ctx context.Context, field graphql.CollectedField, obj *bug.CreateTimelineItem) (ret graphql.Marshaler) { @@ -5731,7 +5756,7 @@ func (ec *executionContext) _CreateTimelineItem_history(ctx context.Context, fie return ec.marshalNCommentHistoryStep2ᚕgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐCommentHistoryStep(ctx, field.Selections, res) } -func (ec *executionContext) _EditCommentOperation_hash(ctx context.Context, field graphql.CollectedField, obj *bug.EditCommentOperation) (ret graphql.Marshaler) { +func (ec *executionContext) _EditCommentOperation_id(ctx context.Context, field graphql.CollectedField, obj *bug.EditCommentOperation) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { if r := recover(); r != nil { @@ -5750,7 +5775,7 @@ func (ec *executionContext) _EditCommentOperation_hash(ctx context.Context, fiel ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Hash() + return ec.resolvers.EditCommentOperation().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -5762,10 +5787,10 @@ func (ec *executionContext) _EditCommentOperation_hash(ctx context.Context, fiel } return graphql.Null } - res := resTmp.(git.Hash) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNHash2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _EditCommentOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.EditCommentOperation) (ret graphql.Marshaler) { @@ -5855,13 +5880,13 @@ func (ec *executionContext) _EditCommentOperation_target(ctx context.Context, fi Object: "EditCommentOperation", Field: field, Args: nil, - IsMethod: false, + IsMethod: true, } ctx = graphql.WithResolverContext(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Target, nil + return ec.resolvers.EditCommentOperation().Target(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -5873,10 +5898,10 @@ func (ec *executionContext) _EditCommentOperation_target(ctx context.Context, fi } return graphql.Null } - res := resTmp.(git.Hash) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNHash2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _EditCommentOperation_message(ctx context.Context, field graphql.CollectedField, obj *bug.EditCommentOperation) (ret graphql.Marshaler) { @@ -6533,7 +6558,7 @@ func (ec *executionContext) _Label_color(ctx context.Context, field graphql.Coll return ec.marshalNColor2ᚖimageᚋcolorᚐRGBA(ctx, field.Selections, res) } -func (ec *executionContext) _LabelChangeOperation_hash(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) (ret graphql.Marshaler) { +func (ec *executionContext) _LabelChangeOperation_id(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { if r := recover(); r != nil { @@ -6552,7 +6577,7 @@ func (ec *executionContext) _LabelChangeOperation_hash(ctx context.Context, fiel ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Hash() + return ec.resolvers.LabelChangeOperation().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -6564,10 +6589,10 @@ func (ec *executionContext) _LabelChangeOperation_hash(ctx context.Context, fiel } return graphql.Null } - res := resTmp.(git.Hash) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNHash2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _LabelChangeOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) (ret graphql.Marshaler) { @@ -6792,7 +6817,7 @@ func (ec *executionContext) _LabelChangeResult_status(ctx context.Context, field return ec.marshalNLabelChangeStatus2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐLabelChangeStatus(ctx, field.Selections, res) } -func (ec *executionContext) _LabelChangeTimelineItem_hash(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeTimelineItem) (ret graphql.Marshaler) { +func (ec *executionContext) _LabelChangeTimelineItem_id(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeTimelineItem) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { if r := recover(); r != nil { @@ -6811,7 +6836,7 @@ func (ec *executionContext) _LabelChangeTimelineItem_hash(ctx context.Context, f ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Hash(), nil + return ec.resolvers.LabelChangeTimelineItem().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -6823,10 +6848,10 @@ func (ec *executionContext) _LabelChangeTimelineItem_hash(ctx context.Context, f } return graphql.Null } - res := resTmp.(git.Hash) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNHash2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _LabelChangeTimelineItem_author(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeTimelineItem) (ret graphql.Marshaler) { @@ -8306,7 +8331,7 @@ func (ec *executionContext) _Repository_validLabels(ctx context.Context, field g return ec.marshalNLabel2ᚕgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐLabel(ctx, field.Selections, res) } -func (ec *executionContext) _SetStatusOperation_hash(ctx context.Context, field graphql.CollectedField, obj *bug.SetStatusOperation) (ret graphql.Marshaler) { +func (ec *executionContext) _SetStatusOperation_id(ctx context.Context, field graphql.CollectedField, obj *bug.SetStatusOperation) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { if r := recover(); r != nil { @@ -8325,7 +8350,7 @@ func (ec *executionContext) _SetStatusOperation_hash(ctx context.Context, field ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Hash() + return ec.resolvers.SetStatusOperation().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -8337,10 +8362,10 @@ func (ec *executionContext) _SetStatusOperation_hash(ctx context.Context, field } return graphql.Null } - res := resTmp.(git.Hash) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNHash2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _SetStatusOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.SetStatusOperation) (ret graphql.Marshaler) { @@ -8454,7 +8479,7 @@ func (ec *executionContext) _SetStatusOperation_status(ctx context.Context, fiel return ec.marshalNStatus2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐStatus(ctx, field.Selections, res) } -func (ec *executionContext) _SetStatusTimelineItem_hash(ctx context.Context, field graphql.CollectedField, obj *bug.SetStatusTimelineItem) (ret graphql.Marshaler) { +func (ec *executionContext) _SetStatusTimelineItem_id(ctx context.Context, field graphql.CollectedField, obj *bug.SetStatusTimelineItem) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { if r := recover(); r != nil { @@ -8473,7 +8498,7 @@ func (ec *executionContext) _SetStatusTimelineItem_hash(ctx context.Context, fie ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Hash(), nil + return ec.resolvers.SetStatusTimelineItem().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -8485,10 +8510,10 @@ func (ec *executionContext) _SetStatusTimelineItem_hash(ctx context.Context, fie } return graphql.Null } - res := resTmp.(git.Hash) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNHash2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _SetStatusTimelineItem_author(ctx context.Context, field graphql.CollectedField, obj *bug.SetStatusTimelineItem) (ret graphql.Marshaler) { @@ -8602,7 +8627,7 @@ func (ec *executionContext) _SetStatusTimelineItem_status(ctx context.Context, f return ec.marshalNStatus2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋgraphqlᚋmodelsᚐStatus(ctx, field.Selections, res) } -func (ec *executionContext) _SetTitleOperation_hash(ctx context.Context, field graphql.CollectedField, obj *bug.SetTitleOperation) (ret graphql.Marshaler) { +func (ec *executionContext) _SetTitleOperation_id(ctx context.Context, field graphql.CollectedField, obj *bug.SetTitleOperation) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { if r := recover(); r != nil { @@ -8621,7 +8646,7 @@ func (ec *executionContext) _SetTitleOperation_hash(ctx context.Context, field g ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Hash() + return ec.resolvers.SetTitleOperation().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -8633,10 +8658,10 @@ func (ec *executionContext) _SetTitleOperation_hash(ctx context.Context, field g } return graphql.Null } - res := resTmp.(git.Hash) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNHash2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _SetTitleOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.SetTitleOperation) (ret graphql.Marshaler) { @@ -8895,7 +8920,7 @@ func (ec *executionContext) _SetTitlePayload_operation(ctx context.Context, fiel return ec.marshalNSetTitleOperation2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐSetTitleOperation(ctx, field.Selections, res) } -func (ec *executionContext) _SetTitleTimelineItem_hash(ctx context.Context, field graphql.CollectedField, obj *bug.SetTitleTimelineItem) (ret graphql.Marshaler) { +func (ec *executionContext) _SetTitleTimelineItem_id(ctx context.Context, field graphql.CollectedField, obj *bug.SetTitleTimelineItem) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { if r := recover(); r != nil { @@ -8914,7 +8939,7 @@ func (ec *executionContext) _SetTitleTimelineItem_hash(ctx context.Context, fiel ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Hash(), nil + return ec.resolvers.SetTitleTimelineItem().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -8926,10 +8951,10 @@ func (ec *executionContext) _SetTitleTimelineItem_hash(ctx context.Context, fiel } return graphql.Null } - res := resTmp.(git.Hash) + res := resTmp.(string) rctx.Result = res ctx = ec.Tracer.StartFieldChildExecution(ctx) - return ec.marshalNHash2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋutilᚋgitᚐHash(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } func (ec *executionContext) _SetTitleTimelineItem_author(ctx context.Context, field graphql.CollectedField, obj *bug.SetTitleTimelineItem) (ret graphql.Marshaler) { @@ -10837,11 +10862,20 @@ func (ec *executionContext) _AddCommentOperation(ctx context.Context, sel ast.Se switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("AddCommentOperation") - case "hash": - out.Values[i] = ec._AddCommentOperation_hash(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._AddCommentOperation_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "author": out.Values[i] = ec._AddCommentOperation_author(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -10927,11 +10961,20 @@ func (ec *executionContext) _AddCommentTimelineItem(ctx context.Context, sel ast switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("AddCommentTimelineItem") - case "hash": - out.Values[i] = ec._AddCommentTimelineItem_hash(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._AddCommentTimelineItem_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "author": out.Values[i] = ec._AddCommentTimelineItem_author(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -11013,15 +11056,33 @@ func (ec *executionContext) _Bug(ctx context.Context, sel ast.SelectionSet, obj case "__typename": out.Values[i] = graphql.MarshalString("Bug") case "id": - out.Values[i] = ec._Bug_id(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Bug_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "humanId": - out.Values[i] = ec._Bug_humanId(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Bug_humanId(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "status": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -11583,11 +11644,20 @@ func (ec *executionContext) _CreateOperation(ctx context.Context, sel ast.Select switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("CreateOperation") - case "hash": - out.Values[i] = ec._CreateOperation_hash(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._CreateOperation_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "author": out.Values[i] = ec._CreateOperation_author(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -11644,11 +11714,20 @@ func (ec *executionContext) _CreateTimelineItem(ctx context.Context, sel ast.Sel switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("CreateTimelineItem") - case "hash": - out.Values[i] = ec._CreateTimelineItem_hash(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._CreateTimelineItem_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "author": out.Values[i] = ec._CreateTimelineItem_author(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -11729,11 +11808,20 @@ func (ec *executionContext) _EditCommentOperation(ctx context.Context, sel ast.S switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("EditCommentOperation") - case "hash": - out.Values[i] = ec._EditCommentOperation_hash(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._EditCommentOperation_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "author": out.Values[i] = ec._EditCommentOperation_author(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -11754,10 +11842,19 @@ func (ec *executionContext) _EditCommentOperation(ctx context.Context, sel ast.S return res }) case "target": - out.Values[i] = ec._EditCommentOperation_target(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._EditCommentOperation_target(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "message": out.Values[i] = ec._EditCommentOperation_message(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -12036,11 +12133,20 @@ func (ec *executionContext) _LabelChangeOperation(ctx context.Context, sel ast.S switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("LabelChangeOperation") - case "hash": - out.Values[i] = ec._LabelChangeOperation_hash(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._LabelChangeOperation_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "author": out.Values[i] = ec._LabelChangeOperation_author(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -12133,11 +12239,20 @@ func (ec *executionContext) _LabelChangeTimelineItem(ctx context.Context, sel as switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("LabelChangeTimelineItem") - case "hash": - out.Values[i] = ec._LabelChangeTimelineItem_hash(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._LabelChangeTimelineItem_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "author": out.Values[i] = ec._LabelChangeTimelineItem_author(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -12588,11 +12703,20 @@ func (ec *executionContext) _SetStatusOperation(ctx context.Context, sel ast.Sel switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("SetStatusOperation") - case "hash": - out.Values[i] = ec._SetStatusOperation_hash(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._SetStatusOperation_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "author": out.Values[i] = ec._SetStatusOperation_author(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -12648,11 +12772,20 @@ func (ec *executionContext) _SetStatusTimelineItem(ctx context.Context, sel ast. switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("SetStatusTimelineItem") - case "hash": - out.Values[i] = ec._SetStatusTimelineItem_hash(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._SetStatusTimelineItem_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "author": out.Values[i] = ec._SetStatusTimelineItem_author(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -12708,11 +12841,20 @@ func (ec *executionContext) _SetTitleOperation(ctx context.Context, sel ast.Sele switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("SetTitleOperation") - case "hash": - out.Values[i] = ec._SetTitleOperation_hash(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._SetTitleOperation_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "author": out.Values[i] = ec._SetTitleOperation_author(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -12798,11 +12940,20 @@ func (ec *executionContext) _SetTitleTimelineItem(ctx context.Context, sel ast.S switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("SetTitleTimelineItem") - case "hash": - out.Values[i] = ec._SetTitleTimelineItem_hash(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._SetTitleTimelineItem_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "author": out.Values[i] = ec._SetTitleTimelineItem_author(ctx, field, obj) if out.Values[i] == graphql.Null { diff --git a/graphql/resolvers/bug.go b/graphql/resolvers/bug.go index 37766b95c8a342e62aa9a97e4ec4503b4f70edfe..8f994c0b5ae8bac1fcfe9d73cb2386780c0d3ded 100644 --- a/graphql/resolvers/bug.go +++ b/graphql/resolvers/bug.go @@ -15,6 +15,14 @@ var _ graph.BugResolver = &bugResolver{} type bugResolver struct{} +func (bugResolver) ID(ctx context.Context, obj *bug.Snapshot) (string, error) { + return obj.Id().String(), nil +} + +func (bugResolver) HumanID(ctx context.Context, obj *bug.Snapshot) (string, error) { + return obj.Id().Human(), nil +} + func (bugResolver) Status(ctx context.Context, obj *bug.Snapshot) (models.Status, error) { return convertStatus(obj.Status) } diff --git a/graphql/resolvers/identity.go b/graphql/resolvers/identity.go index 05f7207e5b6e832a1f8b0d4cae1061f603864132..ee40d4d813dac99947a464834e9a8f2184d8f552 100644 --- a/graphql/resolvers/identity.go +++ b/graphql/resolvers/identity.go @@ -12,11 +12,11 @@ var _ graph.IdentityResolver = &identityResolver{} type identityResolver struct{} func (identityResolver) ID(ctx context.Context, obj *identity.Interface) (string, error) { - return (*obj).Id(), nil + return (*obj).Id().String(), nil } func (identityResolver) HumanID(ctx context.Context, obj *identity.Interface) (string, error) { - return (*obj).HumanId(), nil + return (*obj).Id().Human(), nil } func (identityResolver) Name(ctx context.Context, obj *identity.Interface) (*string, error) { diff --git a/graphql/resolvers/operations.go b/graphql/resolvers/operations.go index 19b2b17f7ae987284be1d39302d42c9fc2e57098..ec803c1f99e9378785aa7398847e09ffa1f7c351 100644 --- a/graphql/resolvers/operations.go +++ b/graphql/resolvers/operations.go @@ -6,39 +6,74 @@ import ( "time" "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/graphql/models" ) +var _ graph.CreateOperationResolver = createOperationResolver{} + type createOperationResolver struct{} +func (createOperationResolver) ID(ctx context.Context, obj *bug.CreateOperation) (string, error) { + return obj.Id().String(), nil +} + func (createOperationResolver) Date(ctx context.Context, obj *bug.CreateOperation) (*time.Time, error) { t := obj.Time() return &t, nil } +var _ graph.AddCommentOperationResolver = addCommentOperationResolver{} + type addCommentOperationResolver struct{} +func (addCommentOperationResolver) ID(ctx context.Context, obj *bug.AddCommentOperation) (string, error) { + return obj.Id().String(), nil +} + func (addCommentOperationResolver) Date(ctx context.Context, obj *bug.AddCommentOperation) (*time.Time, error) { t := obj.Time() return &t, nil } +var _ graph.EditCommentOperationResolver = editCommentOperationResolver{} + type editCommentOperationResolver struct{} +func (editCommentOperationResolver) ID(ctx context.Context, obj *bug.EditCommentOperation) (string, error) { + return obj.Id().String(), nil +} + +func (editCommentOperationResolver) Target(ctx context.Context, obj *bug.EditCommentOperation) (string, error) { + return obj.Target.String(), nil +} + func (editCommentOperationResolver) Date(ctx context.Context, obj *bug.EditCommentOperation) (*time.Time, error) { t := obj.Time() return &t, nil } -type labelChangeOperation struct{} +var _ graph.LabelChangeOperationResolver = labelChangeOperationResolver{} + +type labelChangeOperationResolver struct{} -func (labelChangeOperation) Date(ctx context.Context, obj *bug.LabelChangeOperation) (*time.Time, error) { +func (labelChangeOperationResolver) ID(ctx context.Context, obj *bug.LabelChangeOperation) (string, error) { + return obj.Id().String(), nil +} + +func (labelChangeOperationResolver) Date(ctx context.Context, obj *bug.LabelChangeOperation) (*time.Time, error) { t := obj.Time() return &t, nil } +var _ graph.SetStatusOperationResolver = setStatusOperationResolver{} + type setStatusOperationResolver struct{} +func (setStatusOperationResolver) ID(ctx context.Context, obj *bug.SetStatusOperation) (string, error) { + return obj.Id().String(), nil +} + func (setStatusOperationResolver) Date(ctx context.Context, obj *bug.SetStatusOperation) (*time.Time, error) { t := obj.Time() return &t, nil @@ -48,8 +83,14 @@ func (setStatusOperationResolver) Status(ctx context.Context, obj *bug.SetStatus return convertStatus(obj.Status) } +var _ graph.SetTitleOperationResolver = setTitleOperationResolver{} + type setTitleOperationResolver struct{} +func (setTitleOperationResolver) ID(ctx context.Context, obj *bug.SetTitleOperation) (string, error) { + return obj.Id().String(), nil +} + func (setTitleOperationResolver) Date(ctx context.Context, obj *bug.SetTitleOperation) (*time.Time, error) { t := obj.Time() return &t, nil diff --git a/graphql/resolvers/repo.go b/graphql/resolvers/repo.go index 68a3ce0a64c18b285cab3149d1b3b01091bc1296..ac9c162fc8bd4a059344f219eb7c0b9290e1f84c 100644 --- a/graphql/resolvers/repo.go +++ b/graphql/resolvers/repo.go @@ -5,6 +5,7 @@ import ( "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/graphql/connections" "github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/graphql/models" @@ -38,7 +39,7 @@ func (repoResolver) AllBugs(ctx context.Context, obj *models.Repository, after * source := obj.Repo.QueryBugs(query) // The edger create a custom edge holding just the id - edger := func(id string, offset int) connections.Edge { + edger := func(id entity.Id, offset int) connections.Edge { return connections.LazyBugEdge{ Id: id, Cursor: connections.OffsetToCursor(offset), @@ -46,7 +47,7 @@ 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 []string, info *models.PageInfo, totalCount int) (*models.BugConnection, error) { + 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)) @@ -99,7 +100,7 @@ func (repoResolver) AllIdentities(ctx context.Context, obj *models.Repository, a source := obj.Repo.AllIdentityIds() // The edger create a custom edge holding just the id - edger := func(id string, offset int) connections.Edge { + edger := func(id entity.Id, offset int) connections.Edge { return connections.LazyIdentityEdge{ Id: id, Cursor: connections.OffsetToCursor(offset), @@ -107,7 +108,7 @@ 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 []string, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) { + 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)) diff --git a/graphql/resolvers/root.go b/graphql/resolvers/root.go index 8873b7232ca784d547e5943509a9f25fa3a3e006..3b129f3b97646f2d1f5abc3597f793f5b662fa25 100644 --- a/graphql/resolvers/root.go +++ b/graphql/resolvers/root.go @@ -87,7 +87,7 @@ func (r RootResolver) EditCommentOperation() graph.EditCommentOperationResolver } func (RootResolver) LabelChangeOperation() graph.LabelChangeOperationResolver { - return &labelChangeOperation{} + return &labelChangeOperationResolver{} } func (RootResolver) SetStatusOperation() graph.SetStatusOperationResolver { diff --git a/graphql/resolvers/timeline.go b/graphql/resolvers/timeline.go index 27f799ba2e707f6980b55630b42422c9b09d7d3e..b206f89814125189d3c4cd93063c0a91ee07ba56 100644 --- a/graphql/resolvers/timeline.go +++ b/graphql/resolvers/timeline.go @@ -5,9 +5,12 @@ import ( "time" "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/graphql/graph" "github.com/MichaelMure/git-bug/graphql/models" ) +var _ graph.CommentHistoryStepResolver = commentHistoryStepResolver{} + type commentHistoryStepResolver struct{} func (commentHistoryStepResolver) Date(ctx context.Context, obj *bug.CommentHistoryStep) (*time.Time, error) { @@ -15,8 +18,14 @@ func (commentHistoryStepResolver) Date(ctx context.Context, obj *bug.CommentHist return &t, nil } +var _ graph.AddCommentTimelineItemResolver = addCommentTimelineItemResolver{} + type addCommentTimelineItemResolver struct{} +func (addCommentTimelineItemResolver) ID(ctx context.Context, obj *bug.AddCommentTimelineItem) (string, error) { + return obj.Id().String(), nil +} + func (addCommentTimelineItemResolver) CreatedAt(ctx context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error) { t := obj.CreatedAt.Time() return &t, nil @@ -27,8 +36,14 @@ func (addCommentTimelineItemResolver) LastEdit(ctx context.Context, obj *bug.Add return &t, nil } +var _ graph.CreateTimelineItemResolver = createTimelineItemResolver{} + type createTimelineItemResolver struct{} +func (createTimelineItemResolver) ID(ctx context.Context, obj *bug.CreateTimelineItem) (string, error) { + return obj.Id().String(), nil +} + func (createTimelineItemResolver) CreatedAt(ctx context.Context, obj *bug.CreateTimelineItem) (*time.Time, error) { t := obj.CreatedAt.Time() return &t, nil @@ -39,15 +54,27 @@ func (createTimelineItemResolver) LastEdit(ctx context.Context, obj *bug.CreateT return &t, nil } +var _ graph.LabelChangeTimelineItemResolver = labelChangeTimelineItem{} + type labelChangeTimelineItem struct{} +func (labelChangeTimelineItem) ID(ctx context.Context, obj *bug.LabelChangeTimelineItem) (string, error) { + return obj.Id().String(), nil +} + func (labelChangeTimelineItem) Date(ctx context.Context, obj *bug.LabelChangeTimelineItem) (*time.Time, error) { t := obj.UnixTime.Time() return &t, nil } +var _ graph.SetStatusTimelineItemResolver = setStatusTimelineItem{} + type setStatusTimelineItem struct{} +func (setStatusTimelineItem) ID(ctx context.Context, obj *bug.SetStatusTimelineItem) (string, error) { + return obj.Id().String(), nil +} + func (setStatusTimelineItem) Date(ctx context.Context, obj *bug.SetStatusTimelineItem) (*time.Time, error) { t := obj.UnixTime.Time() return &t, nil @@ -57,8 +84,14 @@ func (setStatusTimelineItem) Status(ctx context.Context, obj *bug.SetStatusTimel return convertStatus(obj.Status) } +var _ graph.SetTitleTimelineItemResolver = setTitleTimelineItem{} + type setTitleTimelineItem struct{} +func (setTitleTimelineItem) ID(ctx context.Context, obj *bug.SetTitleTimelineItem) (string, error) { + return obj.Id().String(), nil +} + func (setTitleTimelineItem) Date(ctx context.Context, obj *bug.SetTitleTimelineItem) (*time.Time, error) { t := obj.UnixTime.Time() return &t, nil diff --git a/graphql/schema/operations.graphql b/graphql/schema/operations.graphql index d37df2e221ed232732aea834adc65d64b287d096..18e0929c4f18cbaa3751c40184ab61ac1c40ba20 100644 --- a/graphql/schema/operations.graphql +++ b/graphql/schema/operations.graphql @@ -1,7 +1,7 @@ """An operation applied to a bug.""" interface Operation { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The operations author.""" author: Identity! """The datetime when this operation was issued.""" @@ -27,8 +27,8 @@ type OperationEdge { # Operations type CreateOperation implements Operation & Authored { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The author of this object.""" author: Identity! """The datetime when this operation was issued.""" @@ -40,8 +40,8 @@ type CreateOperation implements Operation & Authored { } type SetTitleOperation implements Operation & Authored { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The author of this object.""" author: Identity! """The datetime when this operation was issued.""" @@ -52,8 +52,8 @@ type SetTitleOperation implements Operation & Authored { } type AddCommentOperation implements Operation & Authored { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The author of this object.""" author: Identity! """The datetime when this operation was issued.""" @@ -64,21 +64,21 @@ type AddCommentOperation implements Operation & Authored { } type EditCommentOperation implements Operation & Authored { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The author of this object.""" author: Identity! """The datetime when this operation was issued.""" date: Time! - target: Hash! + target: String! message: String! files: [Hash!]! } type SetStatusOperation implements Operation & Authored { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The author of this object.""" author: Identity! """The datetime when this operation was issued.""" @@ -88,8 +88,8 @@ type SetStatusOperation implements Operation & Authored { } type LabelChangeOperation implements Operation & Authored { - """The hash of the operation""" - hash: Hash! + """The identifier of the operation""" + id: String! """The author of this object.""" author: Identity! """The datetime when this operation was issued.""" diff --git a/graphql/schema/timeline.graphql b/graphql/schema/timeline.graphql index ccc89b876f9ca3a43a4cfc24c7b16401bc05ff35..12462aa3532f394d6c2270ba19250ab0fc574cbc 100644 --- a/graphql/schema/timeline.graphql +++ b/graphql/schema/timeline.graphql @@ -1,7 +1,7 @@ """An item in the timeline of events""" interface TimelineItem { - """The hash of the source operation""" - hash: Hash! + """The identifier of the source operation""" + id: String! } """CommentHistoryStep hold one version of a message in the history""" @@ -30,8 +30,8 @@ type TimelineItemEdge { """CreateTimelineItem is a TimelineItem that represent the creation of a bug and its message edition history""" type CreateTimelineItem implements TimelineItem & Authored { - """The hash of the source operation""" - hash: Hash! + """The identifier of the source operation""" + id: String! author: Identity! message: String! messageIsEmpty: Boolean! @@ -44,8 +44,8 @@ type CreateTimelineItem implements TimelineItem & Authored { """AddCommentTimelineItem is a TimelineItem that represent a Comment and its edition history""" type AddCommentTimelineItem implements TimelineItem & Authored { - """The hash of the source operation""" - hash: Hash! + """The identifier of the source operation""" + id: String! author: Identity! message: String! messageIsEmpty: Boolean! @@ -58,8 +58,8 @@ type AddCommentTimelineItem implements TimelineItem & Authored { """LabelChangeTimelineItem is a TimelineItem that represent a change in the labels of a bug""" type LabelChangeTimelineItem implements TimelineItem & Authored { - """The hash of the source operation""" - hash: Hash! + """The identifier of the source operation""" + id: String! author: Identity! date: Time! added: [Label!]! @@ -68,8 +68,8 @@ type LabelChangeTimelineItem implements TimelineItem & Authored { """SetStatusTimelineItem is a TimelineItem that represent a change in the status of a bug""" type SetStatusTimelineItem implements TimelineItem & Authored { - """The hash of the source operation""" - hash: Hash! + """The identifier of the source operation""" + id: String! author: Identity! date: Time! status: Status! @@ -77,8 +77,8 @@ type SetStatusTimelineItem implements TimelineItem & Authored { """LabelChangeTimelineItem is a TimelineItem that represent a change in the title of a bug""" type SetTitleTimelineItem implements TimelineItem & Authored { - """The hash of the source operation""" - hash: Hash! + """The identifier of the source operation""" + id: String! author: Identity! date: Time! title: String! diff --git a/identity/bare.go b/identity/bare.go index 6af794dd9f702b1bcbc62d3e2e8ef539ef2b717a..a243f0746356c14e9d9d631757e59c0caff3b892 100644 --- a/identity/bare.go +++ b/identity/bare.go @@ -6,6 +6,7 @@ import ( "fmt" "strings" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/lamport" "github.com/MichaelMure/git-bug/util/text" @@ -13,6 +14,7 @@ import ( ) var _ Interface = &Bare{} +var _ entity.Interface = &Bare{} // Bare is a very minimal identity, designed to be fully embedded directly along // other data. @@ -20,7 +22,7 @@ var _ Interface = &Bare{} // in particular, this identity is designed to be compatible with the handling of // identities in the early version of git-bug. type Bare struct { - id string + id entity.Id name string email string login string @@ -28,11 +30,16 @@ type Bare struct { } func NewBare(name string, email string) *Bare { - return &Bare{name: name, email: email} + return &Bare{id: entity.UnsetId, name: name, email: email} } func NewBareFull(name string, email string, login string, avatarUrl string) *Bare { - return &Bare{name: name, email: email, login: login, avatarUrl: avatarUrl} + return &Bare{id: entity.UnsetId, name: name, email: email, login: login, avatarUrl: avatarUrl} +} + +func deriveId(data []byte) entity.Id { + sum := sha256.Sum256(data) + return entity.Id(fmt.Sprintf("%x", sum)) } type bareIdentityJSON struct { @@ -52,6 +59,9 @@ func (i *Bare) MarshalJSON() ([]byte, error) { } func (i *Bare) UnmarshalJSON(data []byte) error { + // Compute the Id when loading the op from disk. + i.id = deriveId(data) + aux := bareIdentityJSON{} if err := json.Unmarshal(data, &aux); err != nil { @@ -67,30 +77,28 @@ func (i *Bare) UnmarshalJSON(data []byte) error { } // Id return the Identity identifier -func (i *Bare) Id() string { - // We don't have a proper ID at hand, so let's hash all the data to get one. - // Hopefully the - - if i.id != "" { - return i.id - } +func (i *Bare) Id() entity.Id { + // We don't have a proper Id at hand, so let's hash all the data to get one. - data, err := json.Marshal(i) - if err != nil { - panic(err) + if i.id == "" { + // something went really wrong + panic("identity's id not set") } + if i.id == entity.UnsetId { + // This means we are trying to get the identity identifier *before* it has been stored + // As the Id is computed based on the actual bytes written on the disk, we are going to predict + // those and then get the Id. This is safe as it will be the exact same code writing on disk later. - h := fmt.Sprintf("%x", sha256.New().Sum(data)[:16]) - i.id = string(h) + data, err := json.Marshal(i) + if err != nil { + panic(err) + } + i.id = deriveId(data) + } return i.id } -// HumanId return the Identity identifier truncated for human consumption -func (i *Bare) HumanId() string { - return FormatHumanID(i.Id()) -} - // Name return the last version of the name func (i *Bare) Name() string { return i.name diff --git a/identity/bare_test.go b/identity/bare_test.go index 7db9f64439ce55a3c8ffa2f1d03b1a659d7f1dc1..335c8d37ef22bbb81fa32f2ffb2f74514d752e97 100644 --- a/identity/bare_test.go +++ b/identity/bare_test.go @@ -5,12 +5,15 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/MichaelMure/git-bug/entity" ) func TestBare_Id(t *testing.T) { i := NewBare("name", "email") id := i.Id() - assert.Equal(t, "7b226e616d65223a226e616d65222c22", id) + expected := entity.Id("e18b853fbd89d5d40ca24811539c9a800c705abd9232f396954e8ca8bb63fa8a") + assert.Equal(t, expected, id) } func TestBareSerialize(t *testing.T) { @@ -28,5 +31,7 @@ func TestBareSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + before.id = after.id + assert.Equal(t, before, &after) } diff --git a/identity/common.go b/identity/common.go index 2f2b90425b7d7435caad854c2527278a5f5712d6..007e10d609e9801161a64361fe89f96de443dc51 100644 --- a/identity/common.go +++ b/identity/common.go @@ -4,17 +4,14 @@ import ( "encoding/json" "errors" "fmt" - "strings" + + "github.com/MichaelMure/git-bug/entity" ) var ErrIdentityNotExist = errors.New("identity doesn't exist") -type ErrMultipleMatch struct { - Matching []string -} - -func (e ErrMultipleMatch) Error() string { - return fmt.Sprintf("Multiple matching identities found:\n%s", strings.Join(e.Matching, "\n")) +func NewErrMultipleMatch(matching []entity.Id) *entity.ErrMultipleMatch { + return entity.NewErrMultipleMatch("identity", matching) } // Custom unmarshaling function to allow package user to delegate @@ -37,11 +34,11 @@ func UnmarshalJSON(raw json.RawMessage) (Interface, error) { } // Fallback on a legacy Bare identity - var b Bare + b := &Bare{} - err = json.Unmarshal(raw, &b) + err = json.Unmarshal(raw, b) if err == nil && (b.name != "" || b.login != "") { - return &b, nil + return b, nil } // abort if we have an error other than the wrong type diff --git a/identity/identity.go b/identity/identity.go index f952bbf92d6fa1838cac7beee395662316e8e0ff..765b77cda561be6e0a3e8e62d85ab282748e09d7 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/lamport" @@ -21,18 +22,16 @@ const identityRemoteRefPattern = "refs/remotes/%s/identities/" const versionEntryName = "version" const identityConfigKey = "git-bug.identity" -const idLength = 40 -const humanIdLength = 7 - var ErrNonFastForwardMerge = errors.New("non fast-forward identity merge") var ErrNoIdentitySet = errors.New("to interact with bugs, an identity first needs to be created using \"git bug user create\" or \"git bug user adopt\"") var ErrMultipleIdentitiesSet = errors.New("multiple user identities set") var _ Interface = &Identity{} +var _ entity.Interface = &Identity{} type Identity struct { // Id used as unique identifier - id string + id entity.Id // all the successive version of the identity versions []*Version @@ -43,6 +42,7 @@ type Identity struct { func NewIdentity(name string, email string) *Identity { return &Identity{ + id: entity.UnsetId, versions: []*Version{ { name: name, @@ -55,6 +55,7 @@ func NewIdentity(name string, email string) *Identity { func NewIdentityFull(name string, email string, login string, avatarUrl string) *Identity { return &Identity{ + id: entity.UnsetId, versions: []*Version{ { name: name, @@ -70,7 +71,7 @@ func NewIdentityFull(name string, email string, login string, avatarUrl string) // MarshalJSON will only serialize the id func (i *Identity) MarshalJSON() ([]byte, error) { return json.Marshal(&IdentityStub{ - id: i.Id(), + id: i.id, }) } @@ -82,7 +83,7 @@ func (i *Identity) UnmarshalJSON(data []byte) error { } // ReadLocal load a local Identity from the identities data available in git -func ReadLocal(repo repository.Repo, id string) (*Identity, error) { +func ReadLocal(repo repository.Repo, id entity.Id) (*Identity, error) { ref := fmt.Sprintf("%s%s", identityRefPattern, id) return read(repo, ref) } @@ -96,10 +97,10 @@ func ReadRemote(repo repository.Repo, remote string, id string) (*Identity, erro // read will load and parse an identity from git func read(repo repository.Repo, ref string) (*Identity, error) { refSplit := strings.Split(ref, "/") - id := refSplit[len(refSplit)-1] + id := entity.Id(refSplit[len(refSplit)-1]) - if len(id) != idLength { - return nil, fmt.Errorf("invalid ref length") + if err := id.Validate(); err != nil { + return nil, errors.Wrap(err, "invalid ref") } hashes, err := repo.ListCommits(ref) @@ -233,7 +234,7 @@ func IsUserIdentitySet(repo repository.RepoCommon) (bool, error) { // SetUserIdentity store the user identity's id in the git config func SetUserIdentity(repo repository.RepoCommon, identity *Identity) error { - return repo.StoreConfig(identityConfigKey, identity.Id()) + return repo.StoreConfig(identityConfigKey, identity.Id().String()) } // GetUserIdentity read the current user identity, set with a git config entry @@ -251,9 +252,13 @@ func GetUserIdentity(repo repository.Repo) (*Identity, error) { return nil, ErrMultipleIdentitiesSet } - var id string + var id entity.Id for _, val := range configs { - id = val + id = entity.Id(val) + } + + if err := id.Validate(); err != nil { + return nil, err } i, err := ReadLocal(repo, id) @@ -326,8 +331,8 @@ func (i *Identity) Commit(repo repository.ClockedRepo) error { v.commitHash = commitHash // if it was the first commit, use the commit hash as the Identity id - if i.id == "" { - i.id = string(commitHash) + if i.id == "" || i.id == entity.UnsetId { + i.id = entity.Id(commitHash) } } @@ -410,7 +415,7 @@ func (i *Identity) Merge(repo repository.Repo, other *Identity) (bool, error) { } if modified { - err := repo.UpdateRef(identityRefPattern+i.id, i.lastCommit) + err := repo.UpdateRef(identityRefPattern+i.id.String(), i.lastCommit) if err != nil { return false, err } @@ -439,8 +444,8 @@ func (i *Identity) Validate() error { lastTime = v.time } - // The identity ID should be the hash of the first commit - if i.versions[0].commitHash != "" && string(i.versions[0].commitHash) != i.id { + // The identity Id should be the hash of the first commit + if i.versions[0].commitHash != "" && string(i.versions[0].commitHash) != i.id.String() { return fmt.Errorf("identity id should be the first commit hash") } @@ -456,7 +461,7 @@ func (i *Identity) lastVersion() *Version { } // Id return the Identity identifier -func (i *Identity) Id() string { +func (i *Identity) Id() entity.Id { if i.id == "" { // simply panic as it would be a coding error // (using an id of an identity not stored yet) @@ -465,16 +470,6 @@ func (i *Identity) Id() string { return i.id } -// HumanId return the Identity identifier truncated for human consumption -func (i *Identity) HumanId() string { - return FormatHumanID(i.Id()) -} - -func FormatHumanID(id string) string { - format := fmt.Sprintf("%%.%ds", humanIdLength) - return fmt.Sprintf(format, id) -} - // Name return the last version of the name func (i *Identity) Name() string { return i.lastVersion().name diff --git a/identity/identity_actions.go b/identity/identity_actions.go index 38d83b19e6ffccb441173ceeef673e56a422833b..aa6a2a9178d5f0a88f0f617adb3a0ea6fe67112b 100644 --- a/identity/identity_actions.go +++ b/identity/identity_actions.go @@ -59,8 +59,13 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeRes } for _, remoteRef := range remoteRefs { - refSplitted := strings.Split(remoteRef, "/") - id := refSplitted[len(refSplitted)-1] + refSplit := strings.Split(remoteRef, "/") + id := entity.Id(refSplit[len(refSplit)-1]) + + if err := id.Validate(); err != nil { + out <- entity.NewMergeInvalidStatus(id, errors.Wrap(err, "invalid ref").Error()) + continue + } remoteIdentity, err := read(repo, remoteRef) @@ -75,7 +80,7 @@ func MergeAll(repo repository.ClockedRepo, remote string) <-chan entity.MergeRes continue } - localRef := identityRefPattern + remoteIdentity.Id() + localRef := identityRefPattern + remoteIdentity.Id().String() localExist, err := repo.RefExist(localRef) if err != nil { diff --git a/identity/identity_stub.go b/identity/identity_stub.go index a2cee0ad6c818c2421f6b06981282cc2d7c00f92..be52ffc0ae730b38ca07094c16a49e31981229af 100644 --- a/identity/identity_stub.go +++ b/identity/identity_stub.go @@ -3,6 +3,7 @@ package identity import ( "encoding/json" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/lamport" "github.com/MichaelMure/git-bug/util/timestamp" @@ -16,13 +17,13 @@ var _ Interface = &IdentityStub{} // When this JSON is deserialized, an IdentityStub is returned instead, to be replaced // later by the proper Identity, loaded from the Repo. type IdentityStub struct { - id string + id entity.Id } func (i *IdentityStub) MarshalJSON() ([]byte, error) { // TODO: add a type marker return json.Marshal(struct { - Id string `json:"id"` + Id entity.Id `json:"id"` }{ Id: i.id, }) @@ -30,7 +31,7 @@ func (i *IdentityStub) MarshalJSON() ([]byte, error) { func (i *IdentityStub) UnmarshalJSON(data []byte) error { aux := struct { - Id string `json:"id"` + Id entity.Id `json:"id"` }{} if err := json.Unmarshal(data, &aux); err != nil { @@ -43,15 +44,10 @@ func (i *IdentityStub) UnmarshalJSON(data []byte) error { } // Id return the Identity identifier -func (i *IdentityStub) Id() string { +func (i *IdentityStub) Id() entity.Id { return i.id } -// HumanId return the Identity identifier truncated for human consumption -func (i *IdentityStub) HumanId() string { - return FormatHumanID(i.Id()) -} - func (IdentityStub) Name() string { panic("identities needs to be properly loaded with identity.ReadLocal()") } diff --git a/identity/identity_stub_test.go b/identity/identity_stub_test.go index 3d94cd3e23a63ee2df358e06546d9a3ea308b680..b01a718c853fbb26f4bcc9edc3837688150d260f 100644 --- a/identity/identity_stub_test.go +++ b/identity/identity_stub_test.go @@ -19,5 +19,8 @@ func TestIdentityStubSerialize(t *testing.T) { err = json.Unmarshal(data, &after) assert.NoError(t, err) + // enforce creating the Id + before.Id() + assert.Equal(t, before, &after) } diff --git a/identity/identity_test.go b/identity/identity_test.go index 2ddb64eab2101e9245212b7efa8e628ffdb806ba..f91c548f7423513e9682fa8980d924137dce836e 100644 --- a/identity/identity_test.go +++ b/identity/identity_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "testing" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/repository" "github.com/stretchr/testify/assert" ) @@ -15,6 +16,7 @@ func TestIdentityCommitLoad(t *testing.T) { // single version identity := &Identity{ + id: entity.UnsetId, versions: []*Version{ { name: "René Descartes", @@ -36,6 +38,7 @@ func TestIdentityCommitLoad(t *testing.T) { // multiple version identity = &Identity{ + id: entity.UnsetId, versions: []*Version{ { time: 100, @@ -114,6 +117,7 @@ func commitsAreSet(t *testing.T, identity *Identity) { // Test that the correct crypto keys are returned for a given lamport time func TestIdentity_ValidKeysAtTime(t *testing.T) { identity := Identity{ + id: entity.UnsetId, versions: []*Version{ { time: 100, @@ -215,6 +219,7 @@ func TestJSON(t *testing.T) { mockRepo := repository.NewMockRepoForTest() identity := &Identity{ + id: entity.UnsetId, versions: []*Version{ { name: "René Descartes", @@ -223,7 +228,7 @@ func TestJSON(t *testing.T) { }, } - // commit to make sure we have an ID + // commit to make sure we have an Id err := identity.Commit(mockRepo) assert.Nil(t, err) assert.NotEmpty(t, identity.id) diff --git a/identity/interface.go b/identity/interface.go index 88f1d9a7dc9b16b21c67a6752c2cf3cd81e9aaf4..54a9da78b193eaf2ef5761cf35360792efbfbe70 100644 --- a/identity/interface.go +++ b/identity/interface.go @@ -1,6 +1,7 @@ package identity import ( + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/lamport" "github.com/MichaelMure/git-bug/util/timestamp" @@ -8,10 +9,7 @@ import ( type Interface interface { // Id return the Identity identifier - Id() string - - // HumanId return the Identity identifier truncated for human consumption - HumanId() string + Id() entity.Id // Name return the last version of the name Name() string diff --git a/identity/resolver.go b/identity/resolver.go index 7facfc0c97cbb763c915d213eaad544825f6a195..70fac74c6096dc1d15a56569340d9df3025cf7d4 100644 --- a/identity/resolver.go +++ b/identity/resolver.go @@ -1,11 +1,14 @@ package identity -import "github.com/MichaelMure/git-bug/repository" +import ( + "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/repository" +) // Resolver define the interface of an Identity resolver, able to load // an identity from, for example, a repo or a cache. type Resolver interface { - ResolveIdentity(id string) (Interface, error) + ResolveIdentity(id entity.Id) (Interface, error) } // DefaultResolver is a Resolver loading Identities directly from a Repo @@ -17,6 +20,6 @@ func NewSimpleResolver(repo repository.Repo) *SimpleResolver { return &SimpleResolver{repo: repo} } -func (r *SimpleResolver) ResolveIdentity(id string) (Interface, error) { +func (r *SimpleResolver) ResolveIdentity(id entity.Id) (Interface, error) { return ReadLocal(r.repo, id) } diff --git a/termui/bug_table.go b/termui/bug_table.go index 4293a3c2c82d587b2775237f39d54a00516eee9d..8d69d66536cdbcba7f045d2a6eee5219f2e2c2a7 100644 --- a/termui/bug_table.go +++ b/termui/bug_table.go @@ -25,7 +25,7 @@ type bugTable struct { repo *cache.RepoCache queryStr string query *cache.Query - allIds []string + allIds []entity.Id excerpts []*cache.BugExcerpt pageCursor int selectCursor int @@ -308,7 +308,7 @@ func (bt *bugTable) render(v *gocui.View, maxX int) { lastEditTime := time.Unix(excerpt.EditUnixTime, 0) - id := text.LeftPadMaxLine(excerpt.HumanId(), columnWidths["id"], 1) + id := text.LeftPadMaxLine(excerpt.Id.Human(), columnWidths["id"], 1) status := text.LeftPadMaxLine(excerpt.Status.String(), columnWidths["status"], 1) title := text.LeftPadMaxLine(excerpt.Title, columnWidths["title"], 1) author := text.LeftPadMaxLine(authorDisplayName, columnWidths["author"], 1) @@ -482,7 +482,7 @@ func (bt *bugTable) pull(g *gocui.Gui, v *gocui.View) error { }) } else { _, _ = fmt.Fprintf(&buffer, "%s%s: %s", - beginLine, colors.Cyan(result.Entity.HumanId()), result, + beginLine, colors.Cyan(result.Entity.Id().Human()), result, ) beginLine = "\n" diff --git a/termui/show_bug.go b/termui/show_bug.go index 68a46a31cafa56e4519d0577faf89e630ffc976b..228b85b0353986f552548a3e60af8f0b1a6f80ec 100644 --- a/termui/show_bug.go +++ b/termui/show_bug.go @@ -7,8 +7,8 @@ import ( "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/entity" "github.com/MichaelMure/git-bug/util/colors" - "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/git-bug/util/text" "github.com/MichaelMure/gocui" ) @@ -214,7 +214,7 @@ func (sb *showBug) renderMain(g *gocui.Gui, mainView *gocui.View) error { } bugHeader := fmt.Sprintf("[%s] %s\n\n[%s] %s opened this bug on %s%s", - colors.Cyan(snap.HumanId()), + colors.Cyan(snap.Id().Human()), colors.Bold(snap.Title), colors.Yellow(snap.Status), colors.Magenta(snap.Author.DisplayName()), @@ -232,7 +232,7 @@ func (sb *showBug) renderMain(g *gocui.Gui, mainView *gocui.View) error { y0 += lines + 1 for _, op := range snap.Timeline { - viewName := op.Hash().String() + viewName := op.Id().String() // TODO: me might skip the rendering of blocks that are outside of the view // but to do that we need to rework how sb.mainSelectableView is maintained @@ -643,7 +643,7 @@ func (sb *showBug) edit(g *gocui.Gui, v *gocui.View) error { return nil } - op, err := snap.SearchTimelineItem(git.Hash(sb.selected)) + op, err := snap.SearchTimelineItem(entity.Id(sb.selected)) if err != nil { return err } @@ -651,10 +651,10 @@ func (sb *showBug) edit(g *gocui.Gui, v *gocui.View) error { switch op.(type) { case *bug.AddCommentTimelineItem: message := op.(*bug.AddCommentTimelineItem).Message - return editCommentWithEditor(sb.bug, op.Hash(), message) + return editCommentWithEditor(sb.bug, op.Id(), message) case *bug.CreateTimelineItem: preMessage := op.(*bug.CreateTimelineItem).Message - return editCommentWithEditor(sb.bug, op.Hash(), preMessage) + return editCommentWithEditor(sb.bug, op.Id(), preMessage) case *bug.LabelChangeTimelineItem: return sb.editLabels(g, snap) } diff --git a/termui/termui.go b/termui/termui.go index 46f9dd06eaea4af54f86c95b8bb3d10c1c047865..5d3bb0c12dc4ebc8e31a1420cba168be6f2d013c 100644 --- a/termui/termui.go +++ b/termui/termui.go @@ -2,11 +2,12 @@ package termui import ( - "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/input" - "github.com/MichaelMure/git-bug/util/git" "github.com/MichaelMure/gocui" "github.com/pkg/errors" + + "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/input" ) var errTerminateMainloop = errors.New("terminate gocui mainloop") @@ -237,7 +238,7 @@ func addCommentWithEditor(bug *cache.BugCache) error { return errTerminateMainloop } -func editCommentWithEditor(bug *cache.BugCache, target git.Hash, preMessage string) error { +func editCommentWithEditor(bug *cache.BugCache, target entity.Id, preMessage string) error { // This is somewhat hacky. // As there is no way to pause gocui, run the editor and restart gocui, // we have to stop it entirely and start a new one later. diff --git a/util/git/hash.go b/util/git/hash.go index d9160d75b178da11bc6e699607fa3f1d3ac379d5..d72541782557fdc0cbd48bb322750d41d167cfba 100644 --- a/util/git/hash.go +++ b/util/git/hash.go @@ -5,6 +5,9 @@ import ( "io" ) +const idLengthSHA1 = 40 +const idLengthSHA256 = 64 + // Hash is a git hash type Hash string @@ -16,7 +19,7 @@ func (h Hash) String() string { func (h *Hash) UnmarshalGQL(v interface{}) error { _, ok := v.(string) if !ok { - return fmt.Errorf("labels must be strings") + return fmt.Errorf("hashes must be strings") } *h = v.(Hash) @@ -35,7 +38,8 @@ func (h Hash) MarshalGQL(w io.Writer) { // IsValid tell if the hash is valid func (h *Hash) IsValid() bool { - if len(*h) != 40 && len(*h) != 64 { + // Support for both sha1 and sha256 git hashes + if len(*h) != idLengthSHA1 && len(*h) != idLengthSHA256 { return false } for _, r := range *h {