1package bug
2
3import (
4 "encoding/json"
5 "fmt"
6 "reflect"
7
8 "github.com/MichaelMure/git-bug/repository"
9 "github.com/MichaelMure/git-bug/util/git"
10 "github.com/pkg/errors"
11)
12
13const formatVersion = 1
14
15// OperationPack represent an ordered set of operation to apply
16// to a Bug. These operations are stored in a single Git commit.
17//
18// These commits will be linked together in a linear chain of commits
19// inside Git to form the complete ordered chain of operation to
20// apply to get the final state of the Bug
21type OperationPack struct {
22 Operations []Operation
23
24 // Private field so not serialized by gob
25 commitHash git.Hash
26}
27
28// hold the different operation type to instantiate to parse JSON
29var operations map[OperationType]reflect.Type
30
31// Register will register a new type of Operation to be able to parse the corresponding JSON
32func Register(t OperationType, op interface{}) {
33 if operations == nil {
34 operations = make(map[OperationType]reflect.Type)
35 }
36 operations[t] = reflect.TypeOf(op)
37}
38
39func (opp *OperationPack) MarshalJSON() ([]byte, error) {
40 return json.Marshal(struct {
41 Version uint `json:"version"`
42 Operations []Operation `json:"ops"`
43 }{
44 Version: formatVersion,
45 Operations: opp.Operations,
46 })
47}
48
49func (opp *OperationPack) UnmarshalJSON(data []byte) error {
50 aux := struct {
51 Version uint `json:"version"`
52 Operations []json.RawMessage `json:"ops"`
53 }{}
54
55 if err := json.Unmarshal(data, &aux); err != nil {
56 return err
57 }
58
59 if aux.Version != formatVersion {
60 return fmt.Errorf("unknown format version %v", aux.Version)
61 }
62
63 for _, raw := range aux.Operations {
64 var t struct {
65 OperationType OperationType `json:"type"`
66 }
67
68 if err := json.Unmarshal(raw, &t); err != nil {
69 return err
70 }
71
72 opType, ok := operations[t.OperationType]
73 if !ok {
74 return fmt.Errorf("unknown operation type %v", t.OperationType)
75 }
76
77 op := reflect.New(opType).Interface()
78
79 if err := json.Unmarshal(raw, op); err != nil {
80 return err
81 }
82
83 deref := reflect.ValueOf(op).Elem().Interface()
84
85 opp.Operations = append(opp.Operations, deref.(Operation))
86 }
87
88 return nil
89}
90
91// Append a new operation to the pack
92func (opp *OperationPack) Append(op Operation) {
93 opp.Operations = append(opp.Operations, op)
94}
95
96// IsEmpty tell if the OperationPack is empty
97func (opp *OperationPack) IsEmpty() bool {
98 return len(opp.Operations) == 0
99}
100
101// IsValid tell if the OperationPack is considered valid
102func (opp *OperationPack) Validate() error {
103 if opp.IsEmpty() {
104 return fmt.Errorf("empty")
105 }
106
107 for _, op := range opp.Operations {
108 if err := op.Validate(); err != nil {
109 return errors.Wrap(err, "op")
110 }
111 }
112
113 return nil
114}
115
116// Write will serialize and store the OperationPack as a git blob and return
117// its hash
118func (opp *OperationPack) Write(repo repository.Repo) (git.Hash, error) {
119 data, err := json.Marshal(opp)
120
121 if err != nil {
122 return "", err
123 }
124
125 hash, err := repo.StoreData(data)
126
127 if err != nil {
128 return "", err
129 }
130
131 return hash, nil
132}
133
134// Make a deep copy
135func (opp *OperationPack) Clone() OperationPack {
136
137 clone := OperationPack{
138 Operations: make([]Operation, len(opp.Operations)),
139 commitHash: opp.commitHash,
140 }
141
142 for i, op := range opp.Operations {
143 clone.Operations[i] = op
144 }
145
146 return clone
147}