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