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
100func NewLabelChangeOperation(author Person, unixTime int64, added, removed []Label) *LabelChangeOperation {
101 return &LabelChangeOperation{
102 OpBase: newOpBase(LabelChangeOp, author, unixTime),
103 Added: added,
104 Removed: removed,
105 }
106}
107
108type LabelChangeTimelineItem struct {
109 hash git.Hash
110 Author Person
111 UnixTime Timestamp
112 Added []Label
113 Removed []Label
114}
115
116func (l LabelChangeTimelineItem) Hash() git.Hash {
117 return l.hash
118}
119
120// ChangeLabels is a convenience function to apply the operation
121func ChangeLabels(b Interface, author Person, unixTime int64, add, remove []string) ([]LabelChangeResult, error) {
122 var added, removed []Label
123 var results []LabelChangeResult
124
125 snap := b.Compile()
126
127 for _, str := range add {
128 label := Label(str)
129
130 // check for duplicate
131 if labelExist(added, label) {
132 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeDuplicateInOp})
133 continue
134 }
135
136 // check that the label doesn't already exist
137 if labelExist(snap.Labels, label) {
138 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeAlreadySet})
139 continue
140 }
141
142 added = append(added, label)
143 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeAdded})
144 }
145
146 for _, str := range remove {
147 label := Label(str)
148
149 // check for duplicate
150 if labelExist(removed, label) {
151 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeDuplicateInOp})
152 continue
153 }
154
155 // check that the label actually exist
156 if !labelExist(snap.Labels, label) {
157 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeDoesntExist})
158 continue
159 }
160
161 removed = append(removed, label)
162 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeRemoved})
163 }
164
165 if len(added) == 0 && len(removed) == 0 {
166 return results, fmt.Errorf("no label added or removed")
167 }
168
169 labelOp := NewLabelChangeOperation(author, unixTime, added, removed)
170
171 if err := labelOp.Validate(); err != nil {
172 return nil, err
173 }
174
175 b.Append(labelOp)
176
177 return results, nil
178}
179
180func labelExist(labels []Label, label Label) bool {
181 for _, l := range labels {
182 if l == label {
183 return true
184 }
185 }
186
187 return false
188}
189
190type LabelChangeStatus int
191
192const (
193 _ LabelChangeStatus = iota
194 LabelChangeAdded
195 LabelChangeRemoved
196 LabelChangeDuplicateInOp
197 LabelChangeAlreadySet
198 LabelChangeDoesntExist
199)
200
201type LabelChangeResult struct {
202 Label Label
203 Status LabelChangeStatus
204}
205
206func (l LabelChangeResult) String() string {
207 switch l.Status {
208 case LabelChangeAdded:
209 return fmt.Sprintf("label %s added", l.Label)
210 case LabelChangeRemoved:
211 return fmt.Sprintf("label %s removed", l.Label)
212 case LabelChangeDuplicateInOp:
213 return fmt.Sprintf("label %s is a duplicate", l.Label)
214 case LabelChangeAlreadySet:
215 return fmt.Sprintf("label %s was already set", l.Label)
216 case LabelChangeDoesntExist:
217 return fmt.Sprintf("label %s doesn't exist on this bug", l.Label)
218 default:
219 panic(fmt.Sprintf("unknown label change status %v", l.Status))
220 }
221}