operation_pack.go

  1package bug
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"reflect"
  7
  8	"github.com/MichaelMure/git-bug/repository"
  9	"github.com/MichaelMure/git-bug/util/git"
 10	"github.com/pkg/errors"
 11)
 12
 13const formatVersion = 1
 14
 15// OperationPack represent an ordered set of operation to apply
 16// to a Bug. These operations are stored in a single Git commit.
 17//
 18// These commits will be linked together in a linear chain of commits
 19// inside Git to form the complete ordered chain of operation to
 20// apply to get the final state of the Bug
 21type OperationPack struct {
 22	Operations []Operation
 23
 24	// Private field so not serialized by gob
 25	commitHash git.Hash
 26}
 27
 28// hold the different operation type to instantiate to parse JSON
 29var operations map[OperationType]reflect.Type
 30
 31// Register will register a new type of Operation to be able to parse the corresponding JSON
 32func Register(t OperationType, op interface{}) {
 33	if operations == nil {
 34		operations = make(map[OperationType]reflect.Type)
 35	}
 36	operations[t] = reflect.TypeOf(op)
 37}
 38
 39func (opp *OperationPack) MarshalJSON() ([]byte, error) {
 40	return json.Marshal(struct {
 41		Version    uint        `json:"version"`
 42		Operations []Operation `json:"ops"`
 43	}{
 44		Version:    formatVersion,
 45		Operations: opp.Operations,
 46	})
 47}
 48
 49func (opp *OperationPack) UnmarshalJSON(data []byte) error {
 50	aux := struct {
 51		Version    uint              `json:"version"`
 52		Operations []json.RawMessage `json:"ops"`
 53	}{}
 54
 55	if err := json.Unmarshal(data, &aux); err != nil {
 56		return err
 57	}
 58
 59	if aux.Version != formatVersion {
 60		return fmt.Errorf("unknown format version %v", aux.Version)
 61	}
 62
 63	for _, raw := range aux.Operations {
 64		var t struct {
 65			OperationType OperationType `json:"type"`
 66		}
 67
 68		if err := json.Unmarshal(raw, &t); err != nil {
 69			return err
 70		}
 71
 72		opType, ok := operations[t.OperationType]
 73		if !ok {
 74			return fmt.Errorf("unknown operation type %v", t.OperationType)
 75		}
 76
 77		op := reflect.New(opType).Interface()
 78
 79		if err := json.Unmarshal(raw, op); err != nil {
 80			return err
 81		}
 82
 83		deref := reflect.ValueOf(op).Elem().Interface()
 84
 85		opp.Operations = append(opp.Operations, deref.(Operation))
 86	}
 87
 88	return nil
 89}
 90
 91// Append a new operation to the pack
 92func (opp *OperationPack) Append(op Operation) {
 93	opp.Operations = append(opp.Operations, op)
 94}
 95
 96// IsEmpty tell if the OperationPack is empty
 97func (opp *OperationPack) IsEmpty() bool {
 98	return len(opp.Operations) == 0
 99}
100
101// IsValid tell if the OperationPack is considered valid
102func (opp *OperationPack) Validate() error {
103	if opp.IsEmpty() {
104		return fmt.Errorf("empty")
105	}
106
107	for _, op := range opp.Operations {
108		if err := op.Validate(); err != nil {
109			return errors.Wrap(err, "op")
110		}
111	}
112
113	return nil
114}
115
116// Write will serialize and store the OperationPack as a git blob and return
117// its hash
118func (opp *OperationPack) Write(repo repository.Repo) (git.Hash, error) {
119	data, err := json.Marshal(opp)
120
121	if err != nil {
122		return "", err
123	}
124
125	hash, err := repo.StoreData(data)
126
127	if err != nil {
128		return "", err
129	}
130
131	return hash, nil
132}
133
134// Make a deep copy
135func (opp *OperationPack) Clone() OperationPack {
136
137	clone := OperationPack{
138		Operations: make([]Operation, len(opp.Operations)),
139		commitHash: opp.commitHash,
140	}
141
142	for i, op := range opp.Operations {
143		clone.Operations[i] = op
144	}
145
146	return clone
147}