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 default:
97 return nil, fmt.Errorf("unknown operation type %v", _type)
98 }
99}
100
101// Append a new operation to the pack
102func (opp *OperationPack) Append(op Operation) {
103 opp.Operations = append(opp.Operations, op)
104}
105
106// IsEmpty tell if the OperationPack is empty
107func (opp *OperationPack) IsEmpty() bool {
108 return len(opp.Operations) == 0
109}
110
111// IsValid tell if the OperationPack is considered valid
112func (opp *OperationPack) Validate() error {
113 if opp.IsEmpty() {
114 return fmt.Errorf("empty")
115 }
116
117 for _, op := range opp.Operations {
118 if err := op.Validate(); err != nil {
119 return errors.Wrap(err, "op")
120 }
121 }
122
123 return nil
124}
125
126// Write will serialize and store the OperationPack as a git blob and return
127// its hash
128func (opp *OperationPack) Write(repo repository.Repo) (git.Hash, error) {
129 data, err := json.Marshal(opp)
130
131 if err != nil {
132 return "", err
133 }
134
135 hash, err := repo.StoreData(data)
136
137 if err != nil {
138 return "", err
139 }
140
141 return hash, nil
142}
143
144// Make a deep copy
145func (opp *OperationPack) Clone() OperationPack {
146
147 clone := OperationPack{
148 Operations: make([]Operation, len(opp.Operations)),
149 commitHash: opp.commitHash,
150 }
151
152 for i, op := range opp.Operations {
153 clone.Operations[i] = op
154 }
155
156 return clone
157}