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
 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		// delegate to specialized unmarshal function
 61		op, err := opp.unmarshalOp(raw, t.OperationType)
 62		if err != nil {
 63			return err
 64		}
 65
 66		// Compute the hash of the operation
 67		op.base().hash = hashRaw(raw)
 68
 69		opp.Operations = append(opp.Operations, op)
 70	}
 71
 72	return nil
 73}
 74
 75func (opp *OperationPack) unmarshalOp(raw []byte, _type OperationType) (Operation, error) {
 76	switch _type {
 77	case AddCommentOp:
 78		op := &AddCommentOperation{}
 79		err := json.Unmarshal(raw, &op)
 80		return op, err
 81	case CreateOp:
 82		op := &CreateOperation{}
 83		err := json.Unmarshal(raw, &op)
 84		return op, err
 85	case EditCommentOp:
 86		op := &EditCommentOperation{}
 87		err := json.Unmarshal(raw, &op)
 88		return op, err
 89	case LabelChangeOp:
 90		op := &LabelChangeOperation{}
 91		err := json.Unmarshal(raw, &op)
 92		return op, err
 93	case NoOpOp:
 94		op := &NoOpOperation{}
 95		err := json.Unmarshal(raw, &op)
 96		return op, err
 97	case SetMetadataOp:
 98		op := &SetMetadataOperation{}
 99		err := json.Unmarshal(raw, &op)
100		return op, err
101	case SetStatusOp:
102		op := &SetStatusOperation{}
103		err := json.Unmarshal(raw, &op)
104		return op, err
105	case SetTitleOp:
106		op := &SetTitleOperation{}
107		err := json.Unmarshal(raw, &op)
108		return op, err
109	default:
110		return nil, fmt.Errorf("unknown operation type %v", _type)
111	}
112}
113
114// Append a new operation to the pack
115func (opp *OperationPack) Append(op Operation) {
116	opp.Operations = append(opp.Operations, op)
117}
118
119// IsEmpty tell if the OperationPack is empty
120func (opp *OperationPack) IsEmpty() bool {
121	return len(opp.Operations) == 0
122}
123
124// IsValid tell if the OperationPack is considered valid
125func (opp *OperationPack) Validate() error {
126	if opp.IsEmpty() {
127		return fmt.Errorf("empty")
128	}
129
130	for _, op := range opp.Operations {
131		if err := op.Validate(); err != nil {
132			return errors.Wrap(err, "op")
133		}
134	}
135
136	return nil
137}
138
139// Write will serialize and store the OperationPack as a git blob and return
140// its hash
141func (opp *OperationPack) Write(repo repository.Repo) (git.Hash, error) {
142	// First, make sure that all the identities are properly Commit as well
143	for _, op := range opp.Operations {
144		err := op.base().Author.Commit(repo)
145		if err != nil {
146			return "", err
147		}
148	}
149
150	data, err := json.Marshal(opp)
151
152	if err != nil {
153		return "", err
154	}
155
156	hash, err := repo.StoreData(data)
157
158	if err != nil {
159		return "", err
160	}
161
162	return hash, nil
163}
164
165// Make a deep copy
166func (opp *OperationPack) Clone() OperationPack {
167
168	clone := OperationPack{
169		Operations: make([]Operation, len(opp.Operations)),
170		commitHash: opp.commitHash,
171	}
172
173	for i, op := range opp.Operations {
174		clone.Operations[i] = op
175	}
176
177	return clone
178}