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 by gob
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 op, err := opp.unmarshalOp(raw, t.OperationType)
61 if err != nil {
62 return err
63 }
64
65 // Compute the hash of the operation
66 op.base().hash = hashRaw(raw)
67
68 opp.Operations = append(opp.Operations, op)
69 }
70
71 return nil
72}
73
74func (opp *OperationPack) unmarshalOp(raw []byte, _type OperationType) (Operation, error) {
75 switch _type {
76 case CreateOp:
77 op := &CreateOperation{}
78 err := json.Unmarshal(raw, &op)
79 return op, err
80 case SetTitleOp:
81 op := &SetTitleOperation{}
82 err := json.Unmarshal(raw, &op)
83 return op, err
84 case AddCommentOp:
85 op := &AddCommentOperation{}
86 err := json.Unmarshal(raw, &op)
87 return op, err
88 case SetStatusOp:
89 op := &SetStatusOperation{}
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 EditCommentOp:
97 op := &EditCommentOperation{}
98 err := json.Unmarshal(raw, &op)
99 return op, err
100 default:
101 return nil, fmt.Errorf("unknown operation type %v", _type)
102 }
103}
104
105// Append a new operation to the pack
106func (opp *OperationPack) Append(op Operation) {
107 opp.Operations = append(opp.Operations, op)
108}
109
110// IsEmpty tell if the OperationPack is empty
111func (opp *OperationPack) IsEmpty() bool {
112 return len(opp.Operations) == 0
113}
114
115// IsValid tell if the OperationPack is considered valid
116func (opp *OperationPack) Validate() error {
117 if opp.IsEmpty() {
118 return fmt.Errorf("empty")
119 }
120
121 for _, op := range opp.Operations {
122 if err := op.Validate(); err != nil {
123 return errors.Wrap(err, "op")
124 }
125 }
126
127 return nil
128}
129
130// Write will serialize and store the OperationPack as a git blob and return
131// its hash
132func (opp *OperationPack) Write(repo repository.Repo) (git.Hash, error) {
133 data, err := json.Marshal(opp)
134
135 if err != nil {
136 return "", err
137 }
138
139 hash, err := repo.StoreData(data)
140
141 if err != nil {
142 return "", err
143 }
144
145 return hash, nil
146}
147
148// Make a deep copy
149func (opp *OperationPack) Clone() OperationPack {
150
151 clone := OperationPack{
152 Operations: make([]Operation, len(opp.Operations)),
153 commitHash: opp.commitHash,
154 }
155
156 for i, op := range opp.Operations {
157 clone.Operations[i] = op
158 }
159
160 return clone
161}