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