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	default:
 97		return nil, fmt.Errorf("unknown operation type %v", _type)
 98	}
 99}
100
101// Append a new operation to the pack
102func (opp *OperationPack) Append(op Operation) {
103	opp.Operations = append(opp.Operations, op)
104}
105
106// IsEmpty tell if the OperationPack is empty
107func (opp *OperationPack) IsEmpty() bool {
108	return len(opp.Operations) == 0
109}
110
111// IsValid tell if the OperationPack is considered valid
112func (opp *OperationPack) Validate() error {
113	if opp.IsEmpty() {
114		return fmt.Errorf("empty")
115	}
116
117	for _, op := range opp.Operations {
118		if err := op.Validate(); err != nil {
119			return errors.Wrap(err, "op")
120		}
121	}
122
123	return nil
124}
125
126// Write will serialize and store the OperationPack as a git blob and return
127// its hash
128func (opp *OperationPack) Write(repo repository.Repo) (git.Hash, error) {
129	data, err := json.Marshal(opp)
130
131	if err != nil {
132		return "", err
133	}
134
135	hash, err := repo.StoreData(data)
136
137	if err != nil {
138		return "", err
139	}
140
141	return hash, nil
142}
143
144// Make a deep copy
145func (opp *OperationPack) Clone() OperationPack {
146
147	clone := OperationPack{
148		Operations: make([]Operation, len(opp.Operations)),
149		commitHash: opp.commitHash,
150	}
151
152	for i, op := range opp.Operations {
153		clone.Operations[i] = op
154	}
155
156	return clone
157}