1package bug
2
3import (
4 "encoding/json"
5 "fmt"
6
7 "github.com/pkg/errors"
8
9 "github.com/MichaelMure/git-bug/repository"
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 repository.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) (repository.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 // TODO: this might be downgraded to "make sure it exist in git" but then, what make
147 // sure no data is lost on identities ?
148 for _, op := range opp.Operations {
149 if op.base().Author.NeedCommit() {
150 return "", fmt.Errorf("identity need commmit")
151 }
152 }
153
154 data, err := json.Marshal(opp)
155
156 if err != nil {
157 return "", err
158 }
159
160 hash, err := repo.StoreData(data)
161
162 if err != nil {
163 return "", err
164 }
165
166 return hash, nil
167}
168
169// Make a deep copy
170func (opp *OperationPack) Clone() OperationPack {
171
172 clone := OperationPack{
173 Operations: make([]Operation, len(opp.Operations)),
174 commitHash: opp.commitHash,
175 }
176
177 for i, op := range opp.Operations {
178 clone.Operations[i] = op
179 }
180
181 return clone
182}