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}