diff options
Diffstat (limited to 'entity/operation_pack.go')
-rw-r--r-- | entity/operation_pack.go | 132 |
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"` |