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