aboutsummaryrefslogtreecommitdiffstats
path: root/entity/operation_pack.go
diff options
context:
space:
mode:
Diffstat (limited to 'entity/operation_pack.go')
-rw-r--r--entity/operation_pack.go132
1 files changed, 103 insertions, 29 deletions
diff --git a/entity/operation_pack.go b/entity/operation_pack.go
index 2377167f..0a16dd61 100644
--- a/entity/operation_pack.go
+++ b/entity/operation_pack.go
@@ -2,6 +2,7 @@ package entity
import (
"encoding/json"
+ "fmt"
"strconv"
"strings"
@@ -11,25 +12,82 @@ import (
"github.com/MichaelMure/git-bug/util/lamport"
)
+// TODO: extra data tree
+const extraEntryName = "extra"
+
const opsEntryName = "ops"
const versionEntryPrefix = "version-"
const createClockEntryPrefix = "create-clock-"
const editClockEntryPrefix = "edit-clock-"
+const packClockEntryPrefix = "pack-clock-"
type operationPack struct {
Operations []Operation
+ // Encode the entity's logical time of creation across all entities of the same type.
+ // Only exist on the root operationPack
CreateTime lamport.Time
- EditTime lamport.Time
+ // Encode the entity's logical time of last edition across all entities of the same type.
+ // Exist on all operationPack
+ EditTime lamport.Time
+ // Encode the operationPack's logical time of creation withing this entity.
+ // Exist on all operationPack
+ PackTime lamport.Time
}
-// func (opp *operationPack) MarshalJSON() ([]byte, error) {
-// return json.Marshal(struct {
-// Operations []Operation `json:"ops"`
-// }{
-// Operations: opp.Operations,
-// })
-// }
+func (opp operationPack) write(def Definition, repo repository.RepoData) (repository.Hash, error) {
+ // For different reason, we store the clocks and format version directly in the git tree.
+ // Version has to be accessible before any attempt to decode to return early with a unique error.
+ // Clocks could possibly be stored in the git blob but it's nice to separate data and metadata, and
+ // we are storing something directly in the tree already so why not.
+ //
+ // To have a valid Tree, we point the "fake" entries to always the same value, the empty blob.
+ emptyBlobHash, err := repo.StoreData([]byte{})
+ if err != nil {
+ return "", err
+ }
+
+ // Write the Ops as a Git blob containing the serialized array
+ data, err := json.Marshal(struct {
+ Operations []Operation `json:"ops"`
+ }{
+ Operations: opp.Operations,
+ })
+ if err != nil {
+ return "", err
+ }
+ hash, err := repo.StoreData(data)
+ if err != nil {
+ return "", err
+ }
+
+ // Make a Git tree referencing this blob and encoding the other values:
+ // - format version
+ // - clocks
+ tree := []repository.TreeEntry{
+ {ObjectType: repository.Blob, Hash: emptyBlobHash,
+ Name: fmt.Sprintf(versionEntryPrefix+"%d", def.formatVersion)},
+ {ObjectType: repository.Blob, Hash: hash,
+ Name: opsEntryName},
+ {ObjectType: repository.Blob, Hash: emptyBlobHash,
+ Name: fmt.Sprintf(editClockEntryPrefix+"%d", opp.EditTime)},
+ {ObjectType: repository.Blob, Hash: emptyBlobHash,
+ Name: fmt.Sprintf(packClockEntryPrefix+"%d", opp.PackTime)},
+ }
+ if opp.CreateTime > 0 {
+ tree = append(tree, repository.TreeEntry{
+ ObjectType: repository.Blob,
+ Hash: emptyBlobHash,
+ Name: fmt.Sprintf(createClockEntryPrefix+"%d", opp.CreateTime),
+ })
+ }
+ // Store the tree
+ return repo.StoreTree(tree)
+}
+
+// readOperationPack read the operationPack encoded in git at the given Tree hash.
+//
+// Validity of the Lamport clocks is left for the caller to decide.
func readOperationPack(def Definition, repo repository.RepoData, treeHash repository.Hash) (*operationPack, error) {
entries, err := repo.ReadTree(treeHash)
if err != nil {
@@ -37,30 +95,31 @@ func readOperationPack(def Definition, repo repository.RepoData, treeHash reposi
}
// check the format version first, fail early instead of trying to read something
- // var version uint
- // for _, entry := range entries {
- // if strings.HasPrefix(entry.Name, versionEntryPrefix) {
- // v, err := strconv.ParseUint(strings.TrimPrefix(entry.Name, versionEntryPrefix), 10, 64)
- // if err != nil {
- // return nil, errors.Wrap(err, "can't read format version")
- // }
- // if v > 1<<12 {
- // return nil, fmt.Errorf("format version too big")
- // }
- // version = uint(v)
- // break
- // }
- // }
- // if version == 0 {
- // return nil, NewErrUnknowFormat(def.formatVersion)
- // }
- // if version != def.formatVersion {
- // return nil, NewErrInvalidFormat(version, def.formatVersion)
- // }
+ var version uint
+ for _, entry := range entries {
+ if strings.HasPrefix(entry.Name, versionEntryPrefix) {
+ v, err := strconv.ParseUint(strings.TrimPrefix(entry.Name, versionEntryPrefix), 10, 64)
+ if err != nil {
+ return nil, errors.Wrap(err, "can't read format version")
+ }
+ if v > 1<<12 {
+ return nil, fmt.Errorf("format version too big")
+ }
+ version = uint(v)
+ break
+ }
+ }
+ if version == 0 {
+ return nil, NewErrUnknowFormat(def.formatVersion)
+ }
+ if version != def.formatVersion {
+ return nil, NewErrInvalidFormat(version, def.formatVersion)
+ }
var ops []Operation
var createTime lamport.Time
var editTime lamport.Time
+ var packTime lamport.Time
for _, entry := range entries {
if entry.Name == opsEntryName {
@@ -73,7 +132,7 @@ func readOperationPack(def Definition, repo repository.RepoData, treeHash reposi
if err != nil {
return nil, err
}
- break
+ continue
}
if strings.HasPrefix(entry.Name, createClockEntryPrefix) {
@@ -82,6 +141,7 @@ func readOperationPack(def Definition, repo repository.RepoData, treeHash reposi
return nil, errors.Wrap(err, "can't read creation lamport time")
}
createTime = lamport.Time(v)
+ continue
}
if strings.HasPrefix(entry.Name, editClockEntryPrefix) {
@@ -90,6 +150,16 @@ func readOperationPack(def Definition, repo repository.RepoData, treeHash reposi
return nil, errors.Wrap(err, "can't read edit lamport time")
}
editTime = lamport.Time(v)
+ continue
+ }
+
+ if strings.HasPrefix(entry.Name, packClockEntryPrefix) {
+ v, err := strconv.ParseUint(strings.TrimPrefix(entry.Name, packClockEntryPrefix), 10, 64)
+ if err != nil {
+ return nil, errors.Wrap(err, "can't read pack lamport time")
+ }
+ packTime = lamport.Time(v)
+ continue
}
}
@@ -97,9 +167,13 @@ func readOperationPack(def Definition, repo repository.RepoData, treeHash reposi
Operations: ops,
CreateTime: createTime,
EditTime: editTime,
+ PackTime: packTime,
}, nil
}
+// unmarshallOperations delegate the unmarshalling of the Operation's JSON to the decoding
+// function provided by the concrete entity. This gives access to the concrete type of each
+// Operation.
func unmarshallOperations(def Definition, data []byte) ([]Operation, error) {
aux := struct {
Operations []json.RawMessage `json:"ops"`