1package bug
2
3import (
4 "fmt"
5 "sort"
6
7 "github.com/MichaelMure/git-bug/util/git"
8 "github.com/pkg/errors"
9)
10
11var _ Operation = &LabelChangeOperation{}
12
13// LabelChangeOperation define a Bug operation to add or remove labels
14type LabelChangeOperation struct {
15 OpBase
16 Added []Label `json:"added"`
17 Removed []Label `json:"removed"`
18}
19
20func (op *LabelChangeOperation) base() *OpBase {
21 return &op.OpBase
22}
23
24func (op *LabelChangeOperation) Hash() (git.Hash, error) {
25 return hashOperation(op)
26}
27
28// Apply apply the operation
29func (op *LabelChangeOperation) Apply(snapshot *Snapshot) {
30 // Add in the set
31AddLoop:
32 for _, added := range op.Added {
33 for _, label := range snapshot.Labels {
34 if label == added {
35 // Already exist
36 continue AddLoop
37 }
38 }
39
40 snapshot.Labels = append(snapshot.Labels, added)
41 }
42
43 // Remove in the set
44 for _, removed := range op.Removed {
45 for i, label := range snapshot.Labels {
46 if label == removed {
47 snapshot.Labels[i] = snapshot.Labels[len(snapshot.Labels)-1]
48 snapshot.Labels = snapshot.Labels[:len(snapshot.Labels)-1]
49 }
50 }
51 }
52
53 // Sort
54 sort.Slice(snapshot.Labels, func(i, j int) bool {
55 return string(snapshot.Labels[i]) < string(snapshot.Labels[j])
56 })
57
58 hash, err := op.Hash()
59 if err != nil {
60 // Should never error unless a programming error happened
61 // (covered in OpBase.Validate())
62 panic(err)
63 }
64
65 item := &LabelChangeTimelineItem{
66 hash: hash,
67 Author: op.Author,
68 UnixTime: Timestamp(op.UnixTime),
69 Added: op.Added,
70 Removed: op.Removed,
71 }
72
73 snapshot.Timeline = append(snapshot.Timeline, item)
74}
75
76func (op *LabelChangeOperation) Validate() error {
77 if err := opBaseValidate(op, LabelChangeOp); err != nil {
78 return err
79 }
80
81 for _, l := range op.Added {
82 if err := l.Validate(); err != nil {
83 return errors.Wrap(err, "added label")
84 }
85 }
86
87 for _, l := range op.Removed {
88 if err := l.Validate(); err != nil {
89 return errors.Wrap(err, "removed label")
90 }
91 }
92
93 if len(op.Added)+len(op.Removed) <= 0 {
94 return fmt.Errorf("no label change")
95 }
96
97 return nil
98}
99
100// Sign post method for gqlgen
101func (op *LabelChangeOperation) IsAuthored() {}
102
103func NewLabelChangeOperation(author Person, unixTime int64, added, removed []Label) *LabelChangeOperation {
104 return &LabelChangeOperation{
105 OpBase: newOpBase(LabelChangeOp, author, unixTime),
106 Added: added,
107 Removed: removed,
108 }
109}
110
111type LabelChangeTimelineItem struct {
112 hash git.Hash
113 Author Person
114 UnixTime Timestamp
115 Added []Label
116 Removed []Label
117}
118
119func (l LabelChangeTimelineItem) Hash() git.Hash {
120 return l.hash
121}
122
123// ChangeLabels is a convenience function to apply the operation
124func ChangeLabels(b Interface, author Person, unixTime int64, add, remove []string) ([]LabelChangeResult, *LabelChangeOperation, error) {
125 var added, removed []Label
126 var results []LabelChangeResult
127
128 snap := b.Compile()
129
130 for _, str := range add {
131 label := Label(str)
132
133 // check for duplicate
134 if labelExist(added, label) {
135 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeDuplicateInOp})
136 continue
137 }
138
139 // check that the label doesn't already exist
140 if labelExist(snap.Labels, label) {
141 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeAlreadySet})
142 continue
143 }
144
145 added = append(added, label)
146 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeAdded})
147 }
148
149 for _, str := range remove {
150 label := Label(str)
151
152 // check for duplicate
153 if labelExist(removed, label) {
154 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeDuplicateInOp})
155 continue
156 }
157
158 // check that the label actually exist
159 if !labelExist(snap.Labels, label) {
160 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeDoesntExist})
161 continue
162 }
163
164 removed = append(removed, label)
165 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeRemoved})
166 }
167
168 if len(added) == 0 && len(removed) == 0 {
169 return results, nil, fmt.Errorf("no label added or removed")
170 }
171
172 labelOp := NewLabelChangeOperation(author, unixTime, added, removed)
173
174 if err := labelOp.Validate(); err != nil {
175 return nil, nil, err
176 }
177
178 b.Append(labelOp)
179
180 return results, labelOp, nil
181}
182
183func labelExist(labels []Label, label Label) bool {
184 for _, l := range labels {
185 if l == label {
186 return true
187 }
188 }
189
190 return false
191}
192
193type LabelChangeStatus int
194
195const (
196 _ LabelChangeStatus = iota
197 LabelChangeAdded
198 LabelChangeRemoved
199 LabelChangeDuplicateInOp
200 LabelChangeAlreadySet
201 LabelChangeDoesntExist
202)
203
204type LabelChangeResult struct {
205 Label Label
206 Status LabelChangeStatus
207}
208
209func (l LabelChangeResult) String() string {
210 switch l.Status {
211 case LabelChangeAdded:
212 return fmt.Sprintf("label %s added", l.Label)
213 case LabelChangeRemoved:
214 return fmt.Sprintf("label %s removed", l.Label)
215 case LabelChangeDuplicateInOp:
216 return fmt.Sprintf("label %s is a duplicate", l.Label)
217 case LabelChangeAlreadySet:
218 return fmt.Sprintf("label %s was already set", l.Label)
219 case LabelChangeDoesntExist:
220 return fmt.Sprintf("label %s doesn't exist on this bug", l.Label)
221 default:
222 panic(fmt.Sprintf("unknown label change status %v", l.Status))
223 }
224}