diff options
Diffstat (limited to 'bug')
-rw-r--r-- | bug/bug.go | 233 | ||||
-rw-r--r-- | bug/bug_actions_test.go | 20 | ||||
-rw-r--r-- | bug/bug_test.go | 28 | ||||
-rw-r--r-- | bug/git_tree.go | 84 | ||||
-rw-r--r-- | bug/op_add_comment_test.go | 4 | ||||
-rw-r--r-- | bug/op_create.go | 26 | ||||
-rw-r--r-- | bug/op_create_test.go | 26 | ||||
-rw-r--r-- | bug/op_edit_comment_test.go | 59 | ||||
-rw-r--r-- | bug/op_label_change_test.go | 14 | ||||
-rw-r--r-- | bug/op_noop_test.go | 4 | ||||
-rw-r--r-- | bug/op_set_metadata_test.go | 51 | ||||
-rw-r--r-- | bug/op_set_status_test.go | 14 | ||||
-rw-r--r-- | bug/op_set_title_test.go | 14 | ||||
-rw-r--r-- | bug/operation_iterator_test.go | 11 | ||||
-rw-r--r-- | bug/operation_pack.go | 5 | ||||
-rw-r--r-- | bug/operation_pack_test.go | 15 | ||||
-rw-r--r-- | bug/operation_test.go | 32 |
17 files changed, 338 insertions, 302 deletions
@@ -4,7 +4,6 @@ package bug import ( "encoding/json" "fmt" - "strings" "github.com/pkg/errors" @@ -18,7 +17,6 @@ const bugsRefPattern = "refs/bugs/" const bugsRemoteRefPattern = "refs/remotes/%s/bugs/" const opsEntryName = "ops" -const rootEntryName = "root" const mediaEntryName = "media" const createClockEntryPrefix = "create-clock-" @@ -57,7 +55,6 @@ type Bug struct { id entity.Id lastCommit repository.Hash - rootPack repository.Hash // all the committed operations packs []OperationPack @@ -71,7 +68,7 @@ type Bug struct { func NewBug() *Bug { // No id yet // No logical clock yet - return &Bug{} + return &Bug{id: entity.UnsetId} } // ReadLocal will read a local bug from its hash @@ -100,122 +97,77 @@ func ReadRemoteWithResolver(repo repository.ClockedRepo, identityResolver identi // read will read and parse a Bug from git func read(repo repository.ClockedRepo, identityResolver identity.Resolver, ref string) (*Bug, error) { - refSplit := strings.Split(ref, "/") - id := entity.Id(refSplit[len(refSplit)-1]) + id := entity.RefToId(ref) if err := id.Validate(); err != nil { return nil, errors.Wrap(err, "invalid ref ") } hashes, err := repo.ListCommits(ref) - - // TODO: this is not perfect, it might be a command invoke error if err != nil { return nil, ErrBugNotExist } + if len(hashes) == 0 { + return nil, fmt.Errorf("empty bug") + } bug := Bug{ - id: id, - editTime: 0, + id: id, } // Load each OperationPack for _, hash := range hashes { - entries, err := repo.ReadTree(hash) + tree, err := readTree(repo, hash) if err != nil { - return nil, errors.Wrap(err, "can't list git tree entries") - } - - bug.lastCommit = hash - - var opsEntry repository.TreeEntry - opsFound := false - var rootEntry repository.TreeEntry - rootFound := false - var createTime uint64 - var editTime uint64 - - for _, entry := range entries { - if entry.Name == opsEntryName { - opsEntry = entry - opsFound = true - continue - } - if entry.Name == rootEntryName { - rootEntry = entry - rootFound = true - } - if strings.HasPrefix(entry.Name, createClockEntryPrefix) { - n, err := fmt.Sscanf(entry.Name, createClockEntryPattern, &createTime) - if err != nil { - return nil, errors.Wrap(err, "can't read create lamport time") - } - if n != 1 { - return nil, fmt.Errorf("could not parse create time lamport value") - } - } - if strings.HasPrefix(entry.Name, editClockEntryPrefix) { - n, err := fmt.Sscanf(entry.Name, editClockEntryPattern, &editTime) - if err != nil { - return nil, errors.Wrap(err, "can't read edit lamport time") - } - if n != 1 { - return nil, fmt.Errorf("could not parse edit time lamport value") - } - } - } - - if !opsFound { - return nil, errors.New("invalid tree, missing the ops entry") - } - if !rootFound { - return nil, errors.New("invalid tree, missing the root entry") - } - - if bug.rootPack == "" { - bug.rootPack = rootEntry.Hash - bug.createTime = lamport.Time(createTime) + return nil, err } // Due to rebase, edit Lamport time are not necessarily ordered - if editTime > uint64(bug.editTime) { - bug.editTime = lamport.Time(editTime) + if tree.editTime > bug.editTime { + bug.editTime = tree.editTime } // Update the clocks - createClock, err := repo.GetOrCreateClock(creationClockName) + err = repo.Witness(creationClockName, bug.createTime) if err != nil { - return nil, err - } - if err := createClock.Witness(bug.createTime); err != nil { return nil, errors.Wrap(err, "failed to update create lamport clock") } - editClock, err := repo.GetOrCreateClock(editClockName) + err = repo.Witness(editClockName, bug.editTime) if err != nil { - return nil, err - } - if err := editClock.Witness(bug.editTime); err != nil { return nil, errors.Wrap(err, "failed to update edit lamport clock") } - data, err := repo.ReadData(opsEntry.Hash) + data, err := repo.ReadData(tree.opsEntry.Hash) if err != nil { return nil, errors.Wrap(err, "failed to read git blob data") } opp := &OperationPack{} err = json.Unmarshal(data, &opp) - if err != nil { return nil, errors.Wrap(err, "failed to decode OperationPack json") } // tag the pack with the commit hash opp.commitHash = hash + bug.lastCommit = hash + + // if it's the first OperationPack read + if len(bug.packs) == 0 { + bug.createTime = tree.createTime + } bug.packs = append(bug.packs, *opp) } + // Bug Id is the Id of the first operation + if len(bug.packs[0].Operations) == 0 { + return nil, fmt.Errorf("first OperationPack is empty") + } + if bug.id != bug.packs[0].Operations[0].Id() { + return nil, fmt.Errorf("bug ID doesn't match the first operation ID") + } + // Make sure that the identities are properly loaded err = bug.EnsureIdentities(identityResolver) if err != nil { @@ -367,8 +319,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.String() { + // The bug Id should be the id of the first operation + if bug.FirstOp().Id() != bug.id { return fmt.Errorf("bug id should be the first commit hash") } @@ -396,12 +348,17 @@ func (bug *Bug) Validate() error { // Append an operation into the staging area, to be committed later func (bug *Bug) Append(op Operation) { + if len(bug.packs) == 0 && len(bug.staging.Operations) == 0 { + if op.base().OperationType != CreateOp { + panic("first operation should be a Create") + } + bug.id = op.Id() + } bug.staging.Append(op) } // Commit write the staging area in Git and move the operations to the packs func (bug *Bug) Commit(repo repository.ClockedRepo) error { - if !bug.NeedCommit() { return fmt.Errorf("can't commit a bug with no pending operation") } @@ -410,38 +367,29 @@ func (bug *Bug) Commit(repo repository.ClockedRepo) error { return errors.Wrap(err, "can't commit a bug with invalid data") } - // Write the Ops as a Git blob containing the serialized array - hash, err := bug.staging.Write(repo) + // update clocks + var err error + bug.editTime, err = repo.Increment(editClockName) if err != nil { return err } + if bug.lastCommit == "" { + bug.createTime, err = repo.Increment(creationClockName) + if err != nil { + return err + } + } - if bug.rootPack == "" { - bug.rootPack = hash + // Write the Ops as a Git blob containing the serialized array + hash, err := bug.staging.Write(repo) + if err != nil { + return err } // Make a Git tree referencing this blob tree := []repository.TreeEntry{ // the last pack of ops {ObjectType: repository.Blob, Hash: hash, Name: opsEntryName}, - // always the first pack of ops (might be the same) - {ObjectType: repository.Blob, Hash: bug.rootPack, Name: rootEntryName}, - } - - // Reference, if any, all the files required by the ops - // Git will check that they actually exist in the storage and will make sure - // to push/pull them as needed. - mediaTree := makeMediaTree(bug.staging) - if len(mediaTree) > 0 { - mediaTreeHash, err := repo.StoreTree(mediaTree) - if err != nil { - return err - } - tree = append(tree, repository.TreeEntry{ - ObjectType: repository.Tree, - Hash: mediaTreeHash, - Name: mediaEntryName, - }) } // Store the logical clocks as well @@ -454,31 +402,12 @@ func (bug *Bug) Commit(repo repository.ClockedRepo) error { if err != nil { return err } - - editClock, err := repo.GetOrCreateClock(editClockName) - if err != nil { - return err - } - bug.editTime, err = editClock.Increment() - if err != nil { - return err - } - tree = append(tree, repository.TreeEntry{ ObjectType: repository.Blob, Hash: emptyBlobHash, Name: fmt.Sprintf(editClockEntryPattern, bug.editTime), }) if bug.lastCommit == "" { - createClock, err := repo.GetOrCreateClock(creationClockName) - if err != nil { - return err - } - bug.createTime, err = createClock.Increment() - if err != nil { - return err - } - tree = append(tree, repository.TreeEntry{ ObjectType: repository.Blob, Hash: emptyBlobHash, @@ -486,6 +415,22 @@ func (bug *Bug) Commit(repo repository.ClockedRepo) error { }) } + // Reference, if any, all the files required by the ops + // Git will check that they actually exist in the storage and will make sure + // to push/pull them as needed. + mediaTree := makeMediaTree(bug.staging) + if len(mediaTree) > 0 { + mediaTreeHash, err := repo.StoreTree(mediaTree) + if err != nil { + return err + } + tree = append(tree, repository.TreeEntry{ + ObjectType: repository.Tree, + Hash: mediaTreeHash, + Name: mediaEntryName, + }) + } + // Store the tree hash, err = repo.StoreTree(tree) if err != nil { @@ -498,33 +443,25 @@ func (bug *Bug) Commit(repo repository.ClockedRepo) error { } else { hash, err = repo.StoreCommit(hash) } - if err != nil { return err } bug.lastCommit = hash + bug.staging.commitHash = hash + bug.packs = append(bug.packs, bug.staging) + bug.staging = OperationPack{} - // if it was the first commit, use the commit hash as bug id - if bug.id == "" { - bug.id = entity.Id(hash) + // if it was the first commit, use the Id of the first op (create) + if bug.id == "" || bug.id == entity.UnsetId { + bug.id = bug.packs[0].Operations[0].Id() } // Create or update the Git reference for this bug // When pushing later, the remote will ensure that this ref update // is fast-forward, that is no data has been overwritten ref := fmt.Sprintf("%s%s", bugsRefPattern, bug.id) - err = repo.UpdateRef(ref, hash) - - if err != nil { - return err - } - - bug.staging.commitHash = hash - bug.packs = append(bug.packs, bug.staging) - bug.staging = OperationPack{} - - return nil + return repo.UpdateRef(ref, hash) } func (bug *Bug) CommitAsNeeded(repo repository.ClockedRepo) error { @@ -538,30 +475,6 @@ func (bug *Bug) NeedCommit() bool { return !bug.staging.IsEmpty() } -func makeMediaTree(pack OperationPack) []repository.TreeEntry { - var tree []repository.TreeEntry - counter := 0 - added := make(map[repository.Hash]interface{}) - - for _, ops := range pack.Operations { - for _, file := range ops.GetFiles() { - if _, has := added[file]; !has { - tree = append(tree, repository.TreeEntry{ - ObjectType: repository.Blob, - Hash: file, - // The name is not important here, we only need to - // reference the blob. - Name: fmt.Sprintf("file%d", counter), - }) - counter++ - added[file] = struct{}{} - } - } - } - - return tree -} - // Merge a different version of the same bug by rebasing operations of this bug // that are not present in the other on top of the chain of operations of the // other version. @@ -657,9 +570,9 @@ func (bug *Bug) Merge(repo repository.Repo, other Interface) (bool, error) { // Id return the Bug identifier func (bug *Bug) Id() entity.Id { - if bug.id == "" { + if bug.id == "" || bug.id == entity.UnsetId { // simply panic as it would be a coding error - // (using an id of a bug not stored yet) + // (using an id of a bug without operation yet) panic("no id yet") } return bug.id diff --git a/bug/bug_actions_test.go b/bug/bug_actions_test.go index df35a5e5..7a9673d6 100644 --- a/bug/bug_actions_test.go +++ b/bug/bug_actions_test.go @@ -15,8 +15,9 @@ func TestPushPull(t *testing.T) { repoA, repoB, remote := repository.SetupReposAndRemote() defer repository.CleanupTestRepos(repoA, repoB, remote) - reneA := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := reneA.Commit(repoA) + reneA, err := identity.NewIdentity(repoA, "René Descartes", "rene@descartes.fr") + require.NoError(t, err) + err = reneA.Commit(repoA) require.NoError(t, err) bug1, _, err := Create(reneA, time.Now().Unix(), "bug1", "message") @@ -92,8 +93,9 @@ func _RebaseTheirs(t testing.TB) { repoA, repoB, remote := repository.SetupReposAndRemote() defer repository.CleanupTestRepos(repoA, repoB, remote) - reneA := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := reneA.Commit(repoA) + reneA, err := identity.NewIdentity(repoA, "René Descartes", "rene@descartes.fr") + require.NoError(t, err) + err = reneA.Commit(repoA) require.NoError(t, err) bug1, _, err := Create(reneA, time.Now().Unix(), "bug1", "message") @@ -172,8 +174,9 @@ func _RebaseOurs(t testing.TB) { repoA, repoB, remote := repository.SetupReposAndRemote() defer repository.CleanupTestRepos(repoA, repoB, remote) - reneA := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := reneA.Commit(repoA) + reneA, err := identity.NewIdentity(repoA, "René Descartes", "rene@descartes.fr") + require.NoError(t, err) + err = reneA.Commit(repoA) require.NoError(t, err) bug1, _, err := Create(reneA, time.Now().Unix(), "bug1", "message") @@ -263,8 +266,9 @@ func _RebaseConflict(t testing.TB) { repoA, repoB, remote := repository.SetupReposAndRemote() defer repository.CleanupTestRepos(repoA, repoB, remote) - reneA := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := reneA.Commit(repoA) + reneA, err := identity.NewIdentity(repoA, "René Descartes", "rene@descartes.fr") + require.NoError(t, err) + err = reneA.Commit(repoA) require.NoError(t, err) bug1, _, err := Create(reneA, time.Now().Unix(), "bug1", "message") diff --git a/bug/bug_test.go b/bug/bug_test.go index 047fe386..a8987ac1 100644 --- a/bug/bug_test.go +++ b/bug/bug_test.go @@ -12,19 +12,20 @@ import ( ) func TestBugId(t *testing.T) { - mockRepo := repository.NewMockRepo() + repo := repository.NewMockRepo() bug1 := NewBug() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(mockRepo) + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") + require.NoError(t, err) + err = rene.Commit(repo) require.NoError(t, err) createOp := NewCreateOp(rene, time.Now().Unix(), "title", "message", nil) bug1.Append(createOp) - err = bug1.Commit(mockRepo) + err = bug1.Commit(repo) if err != nil { t.Fatal(err) @@ -34,12 +35,13 @@ func TestBugId(t *testing.T) { } func TestBugValidity(t *testing.T) { - mockRepo := repository.NewMockRepo() + repo := repository.NewMockRepo() bug1 := NewBug() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(mockRepo) + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") + require.NoError(t, err) + err = rene.Commit(repo) require.NoError(t, err) createOp := NewCreateOp(rene, time.Now().Unix(), "title", "message", nil) @@ -54,7 +56,7 @@ func TestBugValidity(t *testing.T) { t.Fatal("Bug with just a CreateOp should be valid") } - err = bug1.Commit(mockRepo) + err = bug1.Commit(repo) if err != nil { t.Fatal(err) } @@ -65,7 +67,7 @@ func TestBugValidity(t *testing.T) { t.Fatal("Bug with multiple CreateOp should be invalid") } - err = bug1.Commit(mockRepo) + err = bug1.Commit(repo) if err == nil { t.Fatal("Invalid bug should not commit") } @@ -76,8 +78,9 @@ func TestBugCommitLoad(t *testing.T) { bug1 := NewBug() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") + require.NoError(t, err) + err = rene.Commit(repo) require.NoError(t, err) createOp := NewCreateOp(rene, time.Now().Unix(), "title", "message", nil) @@ -137,7 +140,8 @@ func TestBugRemove(t *testing.T) { require.NoError(t, err) // generate a bunch of bugs - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") + require.NoError(t, err) err = rene.Commit(repo) require.NoError(t, err) diff --git a/bug/git_tree.go b/bug/git_tree.go new file mode 100644 index 00000000..a5583bda --- /dev/null +++ b/bug/git_tree.go @@ -0,0 +1,84 @@ +package bug + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + + "github.com/MichaelMure/git-bug/repository" + "github.com/MichaelMure/git-bug/util/lamport" +) + +type gitTree struct { + opsEntry repository.TreeEntry + createTime lamport.Time + editTime lamport.Time +} + +func readTree(repo repository.RepoData, hash repository.Hash) (*gitTree, error) { + tree := &gitTree{} + + entries, err := repo.ReadTree(hash) + if err != nil { + return nil, errors.Wrap(err, "can't list git tree entries") + } + + opsFound := false + + for _, entry := range entries { + if entry.Name == opsEntryName { + tree.opsEntry = entry + opsFound = true + continue + } + if strings.HasPrefix(entry.Name, createClockEntryPrefix) { + n, err := fmt.Sscanf(entry.Name, createClockEntryPattern, &tree.createTime) + if err != nil { + return nil, errors.Wrap(err, "can't read create lamport time") + } + if n != 1 { + return nil, fmt.Errorf("could not parse create time lamport value") + } + } + if strings.HasPrefix(entry.Name, editClockEntryPrefix) { + n, err := fmt.Sscanf(entry.Name, editClockEntryPattern, &tree.editTime) + if err != nil { + return nil, errors.Wrap(err, "can't read edit lamport time") + } + if n != 1 { + return nil, fmt.Errorf("could not parse edit time lamport value") + } + } + } + + if !opsFound { + return nil, errors.New("invalid tree, missing the ops entry") + } + + return tree, nil +} + +func makeMediaTree(pack OperationPack) []repository.TreeEntry { + var tree []repository.TreeEntry + counter := 0 + added := make(map[repository.Hash]interface{}) + + for _, ops := range pack.Operations { + for _, file := range ops.GetFiles() { + if _, has := added[file]; !has { + tree = append(tree, repository.TreeEntry{ + ObjectType: repository.Blob, + Hash: file, + // The name is not important here, we only need to + // reference the blob. + Name: fmt.Sprintf("file%d", counter), + }) + counter++ + added[file] = struct{}{} + } + } + } + + return tree +} diff --git a/bug/op_add_comment_test.go b/bug/op_add_comment_test.go index 3f9d02f1..3b41d62d 100644 --- a/bug/op_add_comment_test.go +++ b/bug/op_add_comment_test.go @@ -14,8 +14,8 @@ import ( func TestAddCommentSerialize(t *testing.T) { repo := repository.NewMockRepo() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") require.NoError(t, err) unix := time.Now().Unix() diff --git a/bug/op_create.go b/bug/op_create.go index 9bb40d35..3c8ce658 100644 --- a/bug/op_create.go +++ b/bug/op_create.go @@ -1,6 +1,7 @@ package bug import ( + "crypto/rand" "encoding/json" "fmt" "strings" @@ -17,6 +18,10 @@ var _ Operation = &CreateOperation{} // CreateOperation define the initial creation of a bug type CreateOperation struct { OpBase + // mandatory random bytes to ensure a better randomness of the data of the first + // operation of a bug, used to later generate the ID + // len(Nonce) should be > 20 and < 64 bytes + Nonce []byte `json:"nonce"` Title string `json:"title"` Message string `json:"message"` Files []repository.Hash `json:"files"` @@ -66,14 +71,19 @@ func (op *CreateOperation) Validate() error { return err } + if len(op.Nonce) > 64 { + return fmt.Errorf("create nonce is too big") + } + if len(op.Nonce) < 20 { + return fmt.Errorf("create nonce is too small") + } + if text.Empty(op.Title) { return fmt.Errorf("title is empty") } - if strings.Contains(op.Title, "\n") { return fmt.Errorf("title should be a single line") } - if !text.Safe(op.Title) { return fmt.Errorf("title is not fully printable") } @@ -98,6 +108,7 @@ func (op *CreateOperation) UnmarshalJSON(data []byte) error { } aux := struct { + Nonce []byte `json:"nonce"` Title string `json:"title"` Message string `json:"message"` Files []repository.Hash `json:"files"` @@ -109,6 +120,7 @@ func (op *CreateOperation) UnmarshalJSON(data []byte) error { } op.OpBase = base + op.Nonce = aux.Nonce op.Title = aux.Title op.Message = aux.Message op.Files = aux.Files @@ -119,9 +131,19 @@ func (op *CreateOperation) UnmarshalJSON(data []byte) error { // Sign post method for gqlgen func (op *CreateOperation) IsAuthored() {} +func makeNonce(len int) []byte { + result := make([]byte, len) + _, err := rand.Read(result) + if err != nil { + panic(err) + } + return result +} + func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) *CreateOperation { return &CreateOperation{ OpBase: newOpBase(CreateOp, author, unixTime), + Nonce: makeNonce(20), Title: title, Message: message, Files: files, diff --git a/bug/op_create_test.go b/bug/op_create_test.go index 2d28a208..533aec2e 100644 --- a/bug/op_create_test.go +++ b/bug/op_create_test.go @@ -5,17 +5,21 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/timestamp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestCreate(t *testing.T) { snapshot := Snapshot{} - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") + repo := repository.NewMockRepoClock() + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") + require.NoError(t, err) + unix := time.Now().Unix() create := NewCreateOp(rene, unix, "title", "message", nil) @@ -23,7 +27,7 @@ func TestCreate(t *testing.T) { create.Apply(&snapshot) id := create.Id() - assert.NoError(t, id.Validate()) + require.NoError(t, id.Validate()) comment := Comment{ id: id, @@ -48,31 +52,31 @@ func TestCreate(t *testing.T) { }, } - assert.Equal(t, expected, snapshot) + require.Equal(t, expected, snapshot) } func TestCreateSerialize(t *testing.T) { repo := repository.NewMockRepo() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") require.NoError(t, err) unix := time.Now().Unix() before := NewCreateOp(rene, unix, "title", "message", nil) data, err := json.Marshal(before) - assert.NoError(t, err) + require.NoError(t, err) var after CreateOperation err = json.Unmarshal(data, &after) - assert.NoError(t, err) + require.NoError(t, err) // enforce creating the ID before.Id() // Replace the identity stub with the real thing - assert.Equal(t, rene.Id(), after.base().Author.Id()) + require.Equal(t, rene.Id(), after.base().Author.Id()) after.Author = rene - assert.Equal(t, before, &after) + require.Equal(t, before, &after) } diff --git a/bug/op_edit_comment_test.go b/bug/op_edit_comment_test.go index 263111f9..92ee7539 100644 --- a/bug/op_edit_comment_test.go +++ b/bug/op_edit_comment_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/MichaelMure/git-bug/identity" @@ -16,8 +15,8 @@ func TestEdit(t *testing.T) { snapshot := Snapshot{} repo := repository.NewMockRepo() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") require.NoError(t, err) unix := time.Now().Unix() @@ -47,59 +46,59 @@ func TestEdit(t *testing.T) { edit := NewEditCommentOp(rene, unix, id1, "create edited", nil) edit.Apply(&snapshot) - assert.Equal(t, len(snapshot.Timeline), 4) - assert.Equal(t, len(snapshot.Timeline[0].(*CreateTimelineItem).History), 2) - assert.Equal(t, len(snapshot.Timeline[1].(*AddCommentTimelineItem).History), 1) - assert.Equal(t, len(snapshot.Timeline[3].(*AddCommentTimelineItem).History), 1) - assert.Equal(t, snapshot.Comments[0].Message, "create edited") - assert.Equal(t, snapshot.Comments[1].Message, "comment 1") - assert.Equal(t, snapshot.Comments[2].Message, "comment 2") + require.Equal(t, len(snapshot.Timeline), 4) + require.Equal(t, len(snapshot.Timeline[0].(*CreateTimelineItem).History), 2) + require.Equal(t, len(snapshot.Timeline[1].(*AddCommentTimelineItem).History), 1) + require.Equal(t, len(snapshot.Timeline[3].(*AddCommentTimelineItem).History), 1) + require.Equal(t, snapshot.Comments[0].Message, "create edited") + require.Equal(t, snapshot.Comments[1].Message, "comment 1") + require.Equal(t, snapshot.Comments[2].Message, "comment 2") edit2 := NewEditCommentOp(rene, unix, id2, "comment 1 edited", nil) edit2.Apply(&snapshot) - assert.Equal(t, len(snapshot.Timeline), 4) - assert.Equal(t, len(snapshot.Timeline[0].(*CreateTimelineItem).History), 2) - assert.Equal(t, len(snapshot.Timeline[1].(*AddCommentTimelineItem).History), 2) - assert.Equal(t, len(snapshot.Timeline[3].(*AddCommentTimelineItem).History), 1) - assert.Equal(t, snapshot.Comments[0].Message, "create edited") - assert.Equal(t, snapshot.Comments[1].Message, "comment 1 edited") - assert.Equal(t, snapshot.Comments[2].Message, "comment 2") + require.Equal(t, len(snapshot.Timeline), 4) + require.Equal(t, len(snapshot.Timeline[0].(*CreateTimelineItem).History), 2) + require.Equal(t, len(snapshot.Timeline[1].(*AddCommentTimelineItem).History), 2) + require.Equal(t, len(snapshot.Timeline[3].(*AddCommentTimelineItem).History), 1) + require.Equal(t, snapshot.Comments[0].Message, "create edited") + require.Equal(t, snapshot.Comments[1].Message, "comment 1 edited") + require.Equal(t, snapshot.Comments[2].Message, "comment 2") edit3 := NewEditCommentOp(rene, unix, id3, "comment 2 edited", nil) edit3.Apply(&snapshot) - assert.Equal(t, len(snapshot.Timeline), 4) - assert.Equal(t, len(snapshot.Timeline[0].(*CreateTimelineItem).History), 2) - assert.Equal(t, len(snapshot.Timeline[1].(*AddCommentTimelineItem).History), 2) - assert.Equal(t, len(snapshot.Timeline[3].(*AddCommentTimelineItem).History), 2) - assert.Equal(t, snapshot.Comments[0].Message, "create edited") - assert.Equal(t, snapshot.Comments[1].Message, "comment 1 edited") - assert.Equal(t, snapshot.Comments[2].Message, "comment 2 edited") + require.Equal(t, len(snapshot.Timeline), 4) + require.Equal(t, len(snapshot.Timeline[0].(*CreateTimelineItem).History), 2) + require.Equal(t, len(snapshot.Timeline[1].(*AddCommentTimelineItem).History), 2) + require.Equal(t, len(snapshot.Timeline[3].(*AddCommentTimelineItem).History), 2) + require.Equal(t, snapshot.Comments[0].Message, "create edited") + require.Equal(t, snapshot.Comments[1].Message, "comment 1 edited") + require.Equal(t, snapshot.Comments[2].Message, "comment 2 edited") } func TestEditCommentSerialize(t *testing.T) { repo := repository.NewMockRepo() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") require.NoError(t, err) unix := time.Now().Unix() before := NewEditCommentOp(rene, unix, "target", "message", nil) data, err := json.Marshal(before) - assert.NoError(t, err) + require.NoError(t, err) var after EditCommentOperation err = json.Unmarshal(data, &after) - assert.NoError(t, err) + require.NoError(t, err) // enforce creating the ID before.Id() // Replace the identity stub with the real thing - assert.Equal(t, rene.Id(), after.base().Author.Id()) + require.Equal(t, rene.Id(), after.base().Author.Id()) after.Author = rene - assert.Equal(t, before, &after) + require.Equal(t, before, &after) } diff --git a/bug/op_label_change_test.go b/bug/op_label_change_test.go index ea73368c..96716ffe 100644 --- a/bug/op_label_change_test.go +++ b/bug/op_label_change_test.go @@ -9,32 +9,30 @@ import ( "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" - - "github.com/stretchr/testify/assert" ) func TestLabelChangeSerialize(t *testing.T) { repo := repository.NewMockRepo() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") require.NoError(t, err) unix := time.Now().Unix() before := NewLabelChangeOperation(rene, unix, []Label{"added"}, []Label{"removed"}) data, err := json.Marshal(before) - assert.NoError(t, err) + require.NoError(t, err) var after LabelChangeOperation err = json.Unmarshal(data, &after) - assert.NoError(t, err) + require.NoError(t, err) // enforce creating the ID before.Id() // Replace the identity stub with the real thing - assert.Equal(t, rene.Id(), after.base().Author.Id()) + require.Equal(t, rene.Id(), after.base().Author.Id()) after.Author = rene - assert.Equal(t, before, &after) + require.Equal(t, before, &after) } diff --git a/bug/op_noop_test.go b/bug/op_noop_test.go index 812851ea..ce2f98af 100644 --- a/bug/op_noop_test.go +++ b/bug/op_noop_test.go @@ -15,8 +15,8 @@ import ( func TestNoopSerialize(t *testing.T) { repo := repository.NewMockRepo() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") require.NoError(t, err) unix := time.Now().Unix() diff --git a/bug/op_set_metadata_test.go b/bug/op_set_metadata_test.go index ba068f61..c90f192a 100644 --- a/bug/op_set_metadata_test.go +++ b/bug/op_set_metadata_test.go @@ -8,7 +8,6 @@ import ( "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -16,8 +15,8 @@ func TestSetMetadata(t *testing.T) { snapshot := Snapshot{} repo := repository.NewMockRepo() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") require.NoError(t, err) unix := time.Now().Unix() @@ -47,15 +46,15 @@ func TestSetMetadata(t *testing.T) { snapshot.Operations = append(snapshot.Operations, op1) createMetadata := snapshot.Operations[0].AllMetadata() - assert.Equal(t, len(createMetadata), 2) + require.Equal(t, len(createMetadata), 2) // original key is not overrided - assert.Equal(t, createMetadata["key"], "value") + require.Equal(t, createMetadata["key"], "value") // new key is set - assert.Equal(t, createMetadata["key2"], "value") + require.Equal(t, createMetadata["key2"], "value") commentMetadata := snapshot.Operations[1].AllMetadata() - assert.Equal(t, len(commentMetadata), 1) - assert.Equal(t, commentMetadata["key2"], "value2") + require.Equal(t, len(commentMetadata), 1) + require.Equal(t, commentMetadata["key2"], "value2") op2 := NewSetMetadataOp(rene, unix, id2, map[string]string{ "key2": "value", @@ -66,16 +65,16 @@ func TestSetMetadata(t *testing.T) { snapshot.Operations = append(snapshot.Operations, op2) createMetadata = snapshot.Operations[0].AllMetadata() - assert.Equal(t, len(createMetadata), 2) - assert.Equal(t, createMetadata["key"], "value") - assert.Equal(t, createMetadata["key2"], "value") + require.Equal(t, len(createMetadata), 2) + require.Equal(t, createMetadata["key"], "value") + require.Equal(t, createMetadata["key2"], "value") commentMetadata = snapshot.Operations[1].AllMetadata() - assert.Equal(t, len(commentMetadata), 2) + require.Equal(t, len(commentMetadata), 2) // original key is not overrided - assert.Equal(t, commentMetadata["key2"], "value2") + require.Equal(t, commentMetadata["key2"], "value2") // new key is set - assert.Equal(t, commentMetadata["key3"], "value3") + require.Equal(t, commentMetadata["key3"], "value3") op3 := NewSetMetadataOp(rene, unix, id1, map[string]string{ "key": "override", @@ -86,22 +85,22 @@ func TestSetMetadata(t *testing.T) { snapshot.Operations = append(snapshot.Operations, op3) createMetadata = snapshot.Operations[0].AllMetadata() - assert.Equal(t, len(createMetadata), 2) + require.Equal(t, len(createMetadata), 2) // original key is not overrided - assert.Equal(t, createMetadata["key"], "value") + require.Equal(t, createMetadata["key"], "value") // previously set key is not overrided - assert.Equal(t, createMetadata["key2"], "value") + require.Equal(t, createMetadata["key2"], "value") commentMetadata = snapshot.Operations[1].AllMetadata() - assert.Equal(t, len(commentMetadata), 2) - assert.Equal(t, commentMetadata["key2"], "value2") - assert.Equal(t, commentMetadata["key3"], "value3") + require.Equal(t, len(commentMetadata), 2) + require.Equal(t, commentMetadata["key2"], "value2") + require.Equal(t, commentMetadata["key3"], "value3") } func TestSetMetadataSerialize(t *testing.T) { repo := repository.NewMockRepo() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") require.NoError(t, err) unix := time.Now().Unix() @@ -111,18 +110,18 @@ func TestSetMetadataSerialize(t *testing.T) { }) data, err := json.Marshal(before) - assert.NoError(t, err) + require.NoError(t, err) var after SetMetadataOperation err = json.Unmarshal(data, &after) - assert.NoError(t, err) + require.NoError(t, err) // enforce creating the ID before.Id() // Replace the identity stub with the real thing - assert.Equal(t, rene.Id(), after.base().Author.Id()) + require.Equal(t, rene.Id(), after.base().Author.Id()) after.Author = rene - assert.Equal(t, before, &after) + require.Equal(t, before, &after) } diff --git a/bug/op_set_status_test.go b/bug/op_set_status_test.go index 0619c913..3b26282f 100644 --- a/bug/op_set_status_test.go +++ b/bug/op_set_status_test.go @@ -9,32 +9,30 @@ import ( "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" - - "github.com/stretchr/testify/assert" ) func TestSetStatusSerialize(t *testing.T) { repo := repository.NewMockRepo() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") require.NoError(t, err) unix := time.Now().Unix() before := NewSetStatusOp(rene, unix, ClosedStatus) data, err := json.Marshal(before) - assert.NoError(t, err) + require.NoError(t, err) var after SetStatusOperation err = json.Unmarshal(data, &after) - assert.NoError(t, err) + require.NoError(t, err) // enforce creating the ID before.Id() // Replace the identity stub with the real thing - assert.Equal(t, rene.Id(), after.base().Author.Id()) + require.Equal(t, rene.Id(), after.base().Author.Id()) after.Author = rene - assert.Equal(t, before, &after) + require.Equal(t, before, &after) } diff --git a/bug/op_set_title_test.go b/bug/op_set_title_test.go index df27ee35..6ae325be 100644 --- a/bug/op_set_title_test.go +++ b/bug/op_set_title_test.go @@ -9,32 +9,30 @@ import ( "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" - - "github.com/stretchr/testify/assert" ) func TestSetTitleSerialize(t *testing.T) { repo := repository.NewMockRepo() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") require.NoError(t, err) unix := time.Now().Unix() before := NewSetTitleOp(rene, unix, "title", "was") data, err := json.Marshal(before) - assert.NoError(t, err) + require.NoError(t, err) var after SetTitleOperation err = json.Unmarshal(data, &after) - assert.NoError(t, err) + require.NoError(t, err) // enforce creating the ID before.Id() // Replace the identity stub with the real thing - assert.Equal(t, rene.Id(), after.base().Author.Id()) + require.Equal(t, rene.Id(), after.base().Author.Id()) after.Author = rene - assert.Equal(t, before, &after) + require.Equal(t, before, &after) } diff --git a/bug/operation_iterator_test.go b/bug/operation_iterator_test.go index e066ddd8..81d87a5f 100644 --- a/bug/operation_iterator_test.go +++ b/bug/operation_iterator_test.go @@ -25,10 +25,11 @@ func ExampleOperationIterator() { } func TestOpIterator(t *testing.T) { - mockRepo := repository.NewMockRepo() + repo := repository.NewMockRepo() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(mockRepo) + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") + require.NoError(t, err) + err = rene.Commit(repo) require.NoError(t, err) unix := time.Now().Unix() @@ -51,14 +52,14 @@ func TestOpIterator(t *testing.T) { bug1.Append(addCommentOp) bug1.Append(setStatusOp) bug1.Append(labelChangeOp) - err = bug1.Commit(mockRepo) + err = bug1.Commit(repo) require.NoError(t, err) // second pack bug1.Append(genTitleOp()) bug1.Append(genTitleOp()) bug1.Append(genTitleOp()) - err = bug1.Commit(mockRepo) + err = bug1.Commit(repo) require.NoError(t, err) // staging diff --git a/bug/operation_pack.go b/bug/operation_pack.go index 1a8ef0db..74d15f50 100644 --- a/bug/operation_pack.go +++ b/bug/operation_pack.go @@ -12,7 +12,8 @@ import ( // 1: original format // 2: no more legacy identities -const formatVersion = 2 +// 3: Ids are generated from the create operation serialized data instead of from the first git commit +const formatVersion = 3 // OperationPack represent an ordered set of operation to apply // to a Bug. These operations are stored in a single Git commit. @@ -158,13 +159,11 @@ func (opp *OperationPack) Write(repo repository.ClockedRepo) (repository.Hash, e } data, err := json.Marshal(opp) - if err != nil { return "", err } hash, err := repo.StoreData(data) - if err != nil { return "", err } diff --git a/bug/operation_pack_test.go b/bug/operation_pack_test.go index e1388240..02d72f0f 100644 --- a/bug/operation_pack_test.go +++ b/bug/operation_pack_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/MichaelMure/git-bug/identity" @@ -16,8 +15,8 @@ func TestOperationPackSerialize(t *testing.T) { opp := &OperationPack{} repo := repository.NewMockRepo() - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") require.NoError(t, err) createOp := NewCreateOp(rene, time.Now().Unix(), "title", "message", nil) @@ -36,7 +35,7 @@ func TestOperationPackSerialize(t *testing.T) { opMeta.SetMetadata("key", "value") opp.Append(opMeta) - assert.Equal(t, 1, len(opMeta.Metadata)) + require.Equal(t, 1, len(opMeta.Metadata)) opFile := NewAddCommentOp(rene, time.Now().Unix(), "message", []repository.Hash{ "abcdef", @@ -44,19 +43,19 @@ func TestOperationPackSerialize(t *testing.T) { }) opp.Append(opFile) - assert.Equal(t, 2, len(opFile.Files)) + require.Equal(t, 2, len(opFile.Files)) data, err := json.Marshal(opp) - assert.NoError(t, err) + require.NoError(t, err) var opp2 *OperationPack err = json.Unmarshal(data, &opp2) - assert.NoError(t, err) + require.NoError(t, err) ensureIds(opp) ensureAuthors(t, opp, opp2) - assert.Equal(t, opp, opp2) + require.Equal(t, opp, opp2) } func ensureIds(opp *OperationPack) { diff --git a/bug/operation_test.go b/bug/operation_test.go index 91e1d936..f66938ad 100644 --- a/bug/operation_test.go +++ b/bug/operation_test.go @@ -11,7 +11,16 @@ import ( ) func TestValidate(t *testing.T) { - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") + repo := repository.NewMockRepoClock() + + makeIdentity := func(t *testing.T, name, email string) *identity.Identity { + i, err := identity.NewIdentity(repo, name, email) + require.NoError(t, err) + return i + } + + rene := makeIdentity(t, "René Descartes", "rene@descartes.fr") + unix := time.Now().Unix() good := []Operation{ @@ -30,11 +39,11 @@ func TestValidate(t *testing.T) { bad := []Operation{ // opbase - NewSetStatusOp(identity.NewIdentity("", "rene@descartes.fr"), unix, ClosedStatus), - NewSetStatusOp(identity.NewIdentity("René Descartes\u001b", "rene@descartes.fr"), unix, ClosedStatus), - NewSetStatusOp(identity.NewIdentity("René Descartes", "rene@descartes.fr\u001b"), unix, ClosedStatus), - NewSetStatusOp(identity.NewIdentity("René \nDescartes", "rene@descartes.fr"), unix, ClosedStatus), - NewSetStatusOp(identity.NewIdentity("René Descartes", "rene@\ndescartes.fr"), unix, ClosedStatus), + NewSetStatusOp(makeIdentity(t, "", "rene@descartes.fr"), unix, ClosedStatus), + NewSetStatusOp(makeIdentity(t, "René Descartes\u001b", "rene@descartes.fr"), unix, ClosedStatus), + NewSetStatusOp(makeIdentity(t, "René Descartes", "rene@descartes.fr\u001b"), unix, ClosedStatus), + NewSetStatusOp(makeIdentity(t, "René \nDescartes", "rene@descartes.fr"), unix, ClosedStatus), + NewSetStatusOp(makeIdentity(t, "René Descartes", "rene@\ndescartes.fr"), unix, ClosedStatus), &CreateOperation{OpBase: OpBase{ Author: rene, UnixTime: 0, @@ -68,7 +77,11 @@ func TestValidate(t *testing.T) { } func TestMetadata(t *testing.T) { - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") + repo := repository.NewMockRepoClock() + + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") + require.NoError(t, err) + op := NewCreateOp(rene, time.Now().Unix(), "title", "message", nil) op.SetMetadata("key", "value") @@ -88,8 +101,9 @@ func TestID(t *testing.T) { } for _, repo := range repos { - rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") - err := rene.Commit(repo) + rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr") + require.NoError(t, err) + err = rene.Commit(repo) require.NoError(t, err) b, op, err := Create(rene, time.Now().Unix(), "title", "message") |