operation_pack.go

  1package bug
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6
  7	"github.com/pkg/errors"
  8
  9	"github.com/MichaelMure/git-bug/entity"
 10	"github.com/MichaelMure/git-bug/repository"
 11)
 12
 13// 1: original format
 14// 2: no more legacy identities
 15const formatVersion = 2
 16
 17// OperationPack represent an ordered set of operation to apply
 18// to a Bug. These operations are stored in a single Git commit.
 19//
 20// These commits will be linked together in a linear chain of commits
 21// inside Git to form the complete ordered chain of operation to
 22// apply to get the final state of the Bug
 23type OperationPack struct {
 24	Operations []Operation
 25
 26	// Private field so not serialized
 27	commitHash repository.Hash
 28}
 29
 30func (opp *OperationPack) MarshalJSON() ([]byte, error) {
 31	return json.Marshal(struct {
 32		Version    uint        `json:"version"`
 33		Operations []Operation `json:"ops"`
 34	}{
 35		Version:    formatVersion,
 36		Operations: opp.Operations,
 37	})
 38}
 39
 40func (opp *OperationPack) UnmarshalJSON(data []byte) error {
 41	aux := struct {
 42		Version    uint              `json:"version"`
 43		Operations []json.RawMessage `json:"ops"`
 44	}{}
 45
 46	if err := json.Unmarshal(data, &aux); err != nil {
 47		return err
 48	}
 49
 50	if aux.Version < formatVersion {
 51		return entity.NewErrOldFormatVersion(aux.Version)
 52	}
 53	if aux.Version > formatVersion {
 54		return entity.NewErrNewFormatVersion(aux.Version)
 55	}
 56
 57	for _, raw := range aux.Operations {
 58		var t struct {
 59			OperationType OperationType `json:"type"`
 60		}
 61
 62		if err := json.Unmarshal(raw, &t); err != nil {
 63			return err
 64		}
 65
 66		// delegate to specialized unmarshal function
 67		op, err := opp.unmarshalOp(raw, t.OperationType)
 68		if err != nil {
 69			return err
 70		}
 71
 72		opp.Operations = append(opp.Operations, op)
 73	}
 74
 75	return nil
 76}
 77
 78func (opp *OperationPack) unmarshalOp(raw []byte, _type OperationType) (Operation, error) {
 79	switch _type {
 80	case AddCommentOp:
 81		op := &AddCommentOperation{}
 82		err := json.Unmarshal(raw, &op)
 83		return op, err
 84	case CreateOp:
 85		op := &CreateOperation{}
 86		err := json.Unmarshal(raw, &op)
 87		return op, err
 88	case EditCommentOp:
 89		op := &EditCommentOperation{}
 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 NoOpOp:
 97		op := &NoOpOperation{}
 98		err := json.Unmarshal(raw, &op)
 99		return op, err
100	case SetMetadataOp:
101		op := &SetMetadataOperation{}
102		err := json.Unmarshal(raw, &op)
103		return op, err
104	case SetStatusOp:
105		op := &SetStatusOperation{}
106		err := json.Unmarshal(raw, &op)
107		return op, err
108	case SetTitleOp:
109		op := &SetTitleOperation{}
110		err := json.Unmarshal(raw, &op)
111		return op, err
112	default:
113		return nil, fmt.Errorf("unknown operation type %v", _type)
114	}
115}
116
117// Append a new operation to the pack
118func (opp *OperationPack) Append(op Operation) {
119	opp.Operations = append(opp.Operations, op)
120}
121
122// IsEmpty tell if the OperationPack is empty
123func (opp *OperationPack) IsEmpty() bool {
124	return len(opp.Operations) == 0
125}
126
127// IsValid tell if the OperationPack is considered valid
128func (opp *OperationPack) Validate() error {
129	if opp.IsEmpty() {
130		return fmt.Errorf("empty")
131	}
132
133	for _, op := range opp.Operations {
134		if err := op.Validate(); err != nil {
135			return errors.Wrap(err, "op")
136		}
137	}
138
139	return nil
140}
141
142// Write will serialize and store the OperationPack as a git blob and return
143// its hash
144func (opp *OperationPack) Write(repo repository.ClockedRepo) (repository.Hash, error) {
145	// make sure we don't write invalid data
146	err := opp.Validate()
147	if err != nil {
148		return "", errors.Wrap(err, "validation error")
149	}
150
151	// First, make sure that all the identities are properly Commit as well
152	// TODO: this might be downgraded to "make sure it exist in git" but then, what make
153	// sure no data is lost on identities ?
154	for _, op := range opp.Operations {
155		if op.base().Author.NeedCommit() {
156			return "", fmt.Errorf("identity need commmit")
157		}
158	}
159
160	data, err := json.Marshal(opp)
161
162	if err != nil {
163		return "", err
164	}
165
166	hash, err := repo.StoreData(data)
167
168	if err != nil {
169		return "", err
170	}
171
172	return hash, nil
173}
174
175// Make a deep copy
176func (opp *OperationPack) Clone() OperationPack {
177
178	clone := OperationPack{
179		Operations: make([]Operation, len(opp.Operations)),
180		commitHash: opp.commitHash,
181	}
182
183	for i, op := range opp.Operations {
184		clone.Operations[i] = op
185	}
186
187	return clone
188}