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