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