mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-15 02:01:43 +03:00
bug: Id from first operation data, not git + remove root link
This commit is contained in:
parent
5ae8a13277
commit
7163b2283b
247
bug/bug.go
247
bug/bug.go
@ -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,22 +367,52 @@ func (bug *Bug) Commit(repo repository.ClockedRepo) error {
|
||||
return errors.Wrap(err, "can't commit a bug with invalid data")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Write the Ops as a Git blob containing the serialized array
|
||||
hash, err := bug.staging.Write(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bug.rootPack == "" {
|
||||
bug.rootPack = hash
|
||||
}
|
||||
|
||||
// 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},
|
||||
}
|
||||
|
||||
// Store the logical clocks as well
|
||||
// --> edit clock for each OperationPack/commits
|
||||
// --> create clock only for the first OperationPack/commits
|
||||
//
|
||||
// To avoid having one blob for each clock value, clocks are serialized
|
||||
// directly into the entry name
|
||||
emptyBlobHash, err := repo.StoreData([]byte{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tree = append(tree, repository.TreeEntry{
|
||||
ObjectType: repository.Blob,
|
||||
Hash: emptyBlobHash,
|
||||
Name: fmt.Sprintf(editClockEntryPattern, bug.editTime),
|
||||
})
|
||||
if bug.lastCommit == "" {
|
||||
tree = append(tree, repository.TreeEntry{
|
||||
ObjectType: repository.Blob,
|
||||
Hash: emptyBlobHash,
|
||||
Name: fmt.Sprintf(createClockEntryPattern, bug.createTime),
|
||||
})
|
||||
}
|
||||
|
||||
// Reference, if any, all the files required by the ops
|
||||
@ -444,48 +431,6 @@ func (bug *Bug) Commit(repo repository.ClockedRepo) error {
|
||||
})
|
||||
}
|
||||
|
||||
// Store the logical clocks as well
|
||||
// --> edit clock for each OperationPack/commits
|
||||
// --> create clock only for the first OperationPack/commits
|
||||
//
|
||||
// To avoid having one blob for each clock value, clocks are serialized
|
||||
// directly into the entry name
|
||||
emptyBlobHash, err := repo.StoreData([]byte{})
|
||||
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,
|
||||
Name: fmt.Sprintf(createClockEntryPattern, bug.createTime),
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
||||
|
84
bug/git_tree.go
Normal file
84
bug/git_tree.go
Normal file
@ -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
|
||||
}
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
|
@ -6,13 +6,13 @@ func RefsToIds(refs []string) []Id {
|
||||
ids := make([]Id, len(refs))
|
||||
|
||||
for i, ref := range refs {
|
||||
ids[i] = refToId(ref)
|
||||
ids[i] = RefToId(ref)
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
func refToId(ref string) Id {
|
||||
func RefToId(ref string) Id {
|
||||
split := strings.Split(ref, "/")
|
||||
return Id(split[len(split)-1])
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@ -102,8 +101,7 @@ 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 := entity.Id(refSplit[len(refSplit)-1])
|
||||
id := entity.RefToId(ref)
|
||||
|
||||
if err := id.Validate(); err != nil {
|
||||
return nil, errors.Wrap(err, "invalid ref")
|
||||
|
Loading…
Reference in New Issue
Block a user