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)
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
27var operations map[OperationType]reflect.Type
28
29func Register(t OperationType, op interface{}) {
30 if operations == nil {
31 operations = make(map[OperationType]reflect.Type)
32 }
33 operations[t] = reflect.TypeOf(op)
34}
35
36func (opp *OperationPack) MarshalJSON() ([]byte, error) {
37 return json.Marshal(struct {
38 Version uint `json:"version"`
39 Operations []Operation `json:"ops"`
40 }{
41 Version: formatVersion,
42 Operations: opp.Operations,
43 })
44}
45
46func (opp *OperationPack) UnmarshalJSON(data []byte) error {
47 aux := struct {
48 Version uint `json:"version"`
49 Operations []json.RawMessage `json:"ops"`
50 }{}
51
52 if err := json.Unmarshal(data, &aux); err != nil {
53 return err
54 }
55
56 if aux.Version != formatVersion {
57 return fmt.Errorf("unknown format version %v", aux.Version)
58 }
59
60 for _, raw := range aux.Operations {
61 var t struct {
62 OperationType OperationType `json:"type"`
63 }
64
65 if err := json.Unmarshal(raw, &t); err != nil {
66 return err
67 }
68
69 opType, ok := operations[t.OperationType]
70 if !ok {
71 return fmt.Errorf("unknown operation type %v", t.OperationType)
72 }
73
74 op := reflect.New(opType).Interface()
75
76 if err := json.Unmarshal(raw, op); err != nil {
77 return err
78 }
79
80 deref := reflect.ValueOf(op).Elem().Interface()
81
82 opp.Operations = append(opp.Operations, deref.(Operation))
83 }
84
85 return nil
86}
87
88// Append a new operation to the pack
89func (opp *OperationPack) Append(op Operation) {
90 opp.Operations = append(opp.Operations, op)
91}
92
93// IsEmpty tell if the OperationPack is empty
94func (opp *OperationPack) IsEmpty() bool {
95 return len(opp.Operations) == 0
96}
97
98// IsValid tell if the OperationPack is considered valid
99func (opp *OperationPack) IsValid() bool {
100 return !opp.IsEmpty()
101}
102
103// Write will serialize and store the OperationPack as a git blob and return
104// its hash
105func (opp *OperationPack) Write(repo repository.Repo) (git.Hash, error) {
106 data, err := json.Marshal(opp)
107
108 if err != nil {
109 return "", err
110 }
111
112 hash, err := repo.StoreData(data)
113
114 if err != nil {
115 return "", err
116 }
117
118 return hash, nil
119}
120
121// Make a deep copy
122func (opp *OperationPack) Clone() OperationPack {
123
124 clone := OperationPack{
125 Operations: make([]Operation, len(opp.Operations)),
126 commitHash: opp.commitHash,
127 }
128
129 for i, op := range opp.Operations {
130 clone.Operations[i] = op
131 }
132
133 return clone
134}