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}