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