1package bug
2
3import (
4 "encoding/json"
5 "fmt"
6 "strings"
7
8 "github.com/MichaelMure/git-bug/identity"
9 "github.com/MichaelMure/git-bug/util/timestamp"
10
11 "github.com/MichaelMure/git-bug/util/git"
12 "github.com/MichaelMure/git-bug/util/text"
13)
14
15var _ Operation = &SetTitleOperation{}
16
17// SetTitleOperation will change the title of a bug
18type SetTitleOperation struct {
19 OpBase
20 Title string
21 Was string
22}
23
24func (op *SetTitleOperation) base() *OpBase {
25 return &op.OpBase
26}
27
28func (op *SetTitleOperation) Hash() (git.Hash, error) {
29 return hashOperation(op)
30}
31
32func (op *SetTitleOperation) Apply(snapshot *Snapshot) {
33 snapshot.Title = op.Title
34
35 hash, err := op.Hash()
36 if err != nil {
37 // Should never error unless a programming error happened
38 // (covered in OpBase.Validate())
39 panic(err)
40 }
41
42 item := &SetTitleTimelineItem{
43 hash: hash,
44 Author: op.Author,
45 UnixTime: timestamp.Timestamp(op.UnixTime),
46 Title: op.Title,
47 Was: op.Was,
48 }
49
50 snapshot.Timeline = append(snapshot.Timeline, item)
51}
52
53func (op *SetTitleOperation) Validate() error {
54 if err := opBaseValidate(op, SetTitleOp); err != nil {
55 return err
56 }
57
58 if text.Empty(op.Title) {
59 return fmt.Errorf("title is empty")
60 }
61
62 if strings.Contains(op.Title, "\n") {
63 return fmt.Errorf("title should be a single line")
64 }
65
66 if !text.Safe(op.Title) {
67 return fmt.Errorf("title should be fully printable")
68 }
69
70 if strings.Contains(op.Was, "\n") {
71 return fmt.Errorf("previous title should be a single line")
72 }
73
74 if !text.Safe(op.Was) {
75 return fmt.Errorf("previous title should be fully printable")
76 }
77
78 return nil
79}
80
81// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op
82// MarshalJSON
83func (op *SetTitleOperation) MarshalJSON() ([]byte, error) {
84 base, err := json.Marshal(op.OpBase)
85 if err != nil {
86 return nil, err
87 }
88
89 // revert back to a flat map to be able to add our own fields
90 var data map[string]interface{}
91 if err := json.Unmarshal(base, &data); err != nil {
92 return nil, err
93 }
94
95 data["title"] = op.Title
96 data["was"] = op.Was
97
98 return json.Marshal(data)
99}
100
101// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op
102// MarshalJSON
103func (op *SetTitleOperation) UnmarshalJSON(data []byte) error {
104 // Unmarshal OpBase and the op separately
105
106 base := OpBase{}
107 err := json.Unmarshal(data, &base)
108 if err != nil {
109 return err
110 }
111
112 aux := struct {
113 Title string `json:"title"`
114 Was string `json:"was"`
115 }{}
116
117 err = json.Unmarshal(data, &aux)
118 if err != nil {
119 return err
120 }
121
122 op.OpBase = base
123 op.Title = aux.Title
124 op.Was = aux.Was
125
126 return nil
127}
128
129// Sign post method for gqlgen
130func (op *SetTitleOperation) IsAuthored() {}
131
132func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was string) *SetTitleOperation {
133 return &SetTitleOperation{
134 OpBase: newOpBase(SetTitleOp, author, unixTime),
135 Title: title,
136 Was: was,
137 }
138}
139
140type SetTitleTimelineItem struct {
141 hash git.Hash
142 Author identity.Interface
143 UnixTime timestamp.Timestamp
144 Title string
145 Was string
146}
147
148func (s SetTitleTimelineItem) Hash() git.Hash {
149 return s.hash
150}
151
152// Convenience function to apply the operation
153func SetTitle(b Interface, author identity.Interface, unixTime int64, title string) (*SetTitleOperation, error) {
154 it := NewOperationIterator(b)
155
156 var lastTitleOp Operation
157 for it.Next() {
158 op := it.Value()
159 if op.base().OperationType == SetTitleOp {
160 lastTitleOp = op
161 }
162 }
163
164 var was string
165 if lastTitleOp != nil {
166 was = lastTitleOp.(*SetTitleOperation).Title
167 } else {
168 was = b.FirstOp().(*CreateOperation).Title
169 }
170
171 setTitleOp := NewSetTitleOp(author, unixTime, title, was)
172
173 if err := setTitleOp.Validate(); err != nil {
174 return nil, err
175 }
176
177 b.Append(setTitleOp)
178 return setTitleOp, nil
179}