1package bug
2
3import (
4 "crypto/sha256"
5 "encoding/json"
6 "fmt"
7 "time"
8
9 "github.com/MichaelMure/git-bug/util/git"
10 "github.com/pkg/errors"
11)
12
13// OperationType is an operation type identifier
14type OperationType int
15
16const (
17 _ OperationType = iota
18 CreateOp
19 SetTitleOp
20 AddCommentOp
21 SetStatusOp
22 LabelChangeOp
23)
24
25// Operation define the interface to fulfill for an edit operation of a Bug
26type Operation interface {
27 // base return the OpBase of the Operation, for package internal use
28 base() *OpBase
29 // Hash return the hash of the operation
30 Hash() (git.Hash, error)
31 // Time return the time when the operation was added
32 Time() time.Time
33 // GetUnixTime return the unix timestamp when the operation was added
34 GetUnixTime() int64
35 // GetFiles return the files needed by this operation
36 GetFiles() []git.Hash
37 // Apply the operation to a Snapshot to create the final state
38 Apply(snapshot *Snapshot)
39 // Validate check if the operation is valid (ex: a title is a single line)
40 Validate() error
41 // SetMetadata store arbitrary metadata about the operation
42 SetMetadata(key string, value string)
43 // GetMetadata retrieve arbitrary metadata about the operation
44 GetMetadata(key string) (string, bool)
45}
46
47func hashRaw(data []byte) git.Hash {
48 hasher := sha256.New()
49 return git.Hash(fmt.Sprintf("%x", hasher.Sum(data)))
50}
51
52// hash compute the hash of the serialized operation
53func hashOperation(op Operation) (git.Hash, error) {
54 base := op.base()
55
56 if base.hash != "" {
57 return base.hash, nil
58 }
59
60 data, err := json.Marshal(op)
61 if err != nil {
62 return "", err
63 }
64
65 base.hash = hashRaw(data)
66
67 return base.hash, nil
68}
69
70// OpBase implement the common code for all operations
71type OpBase struct {
72 OperationType OperationType `json:"type"`
73 Author Person `json:"author"`
74 UnixTime int64 `json:"timestamp"`
75 hash git.Hash
76 Metadata map[string]string `json:"metadata,omitempty"`
77}
78
79// newOpBase is the constructor for an OpBase
80func newOpBase(opType OperationType, author Person, unixTime int64) *OpBase {
81 return &OpBase{
82 OperationType: opType,
83 Author: author,
84 UnixTime: unixTime,
85 }
86}
87
88// Time return the time when the operation was added
89func (op *OpBase) Time() time.Time {
90 return time.Unix(op.UnixTime, 0)
91}
92
93// GetUnixTime return the unix timestamp when the operation was added
94func (op *OpBase) GetUnixTime() int64 {
95 return op.UnixTime
96}
97
98// GetFiles return the files needed by this operation
99func (op *OpBase) GetFiles() []git.Hash {
100 return nil
101}
102
103// Validate check the OpBase for errors
104func opBaseValidate(op Operation, opType OperationType) error {
105 if op.base().OperationType != opType {
106 return fmt.Errorf("incorrect operation type (expected: %v, actual: %v)", opType, op.base().OperationType)
107 }
108
109 if op.GetUnixTime() == 0 {
110 return fmt.Errorf("time not set")
111 }
112
113 if err := op.base().Author.Validate(); err != nil {
114 return errors.Wrap(err, "author")
115 }
116
117 for _, hash := range op.GetFiles() {
118 if !hash.IsValid() {
119 return fmt.Errorf("file with invalid hash %v", hash)
120 }
121 }
122
123 return nil
124}
125
126// SetMetadata store arbitrary metadata about the operation
127func (op *OpBase) SetMetadata(key string, value string) {
128 if op.Metadata == nil {
129 op.Metadata = make(map[string]string)
130 }
131
132 op.Metadata[key] = value
133}
134
135// GetMetadata retrieve arbitrary metadata about the operation
136func (op *OpBase) GetMetadata(key string) (string, bool) {
137 val, ok := op.Metadata[key]
138 return val, ok
139}