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