operation_pack.go

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