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 snapshot.Timeline = append(snapshot.Timeline, op)
59}
60
61func (op *LabelChangeOperation) Validate() error {
62 if err := opBaseValidate(op, LabelChangeOp); err != nil {
63 return err
64 }
65
66 for _, l := range op.Added {
67 if err := l.Validate(); err != nil {
68 return errors.Wrap(err, "added label")
69 }
70 }
71
72 for _, l := range op.Removed {
73 if err := l.Validate(); err != nil {
74 return errors.Wrap(err, "removed label")
75 }
76 }
77
78 if len(op.Added)+len(op.Removed) <= 0 {
79 return fmt.Errorf("no label change")
80 }
81
82 return nil
83}
84
85func NewLabelChangeOperation(author Person, unixTime int64, added, removed []Label) *LabelChangeOperation {
86 return &LabelChangeOperation{
87 OpBase: newOpBase(LabelChangeOp, author, unixTime),
88 Added: added,
89 Removed: removed,
90 }
91}
92
93// ChangeLabels is a convenience function to apply the operation
94func ChangeLabels(b Interface, author Person, unixTime int64, add, remove []string) ([]LabelChangeResult, error) {
95 var added, removed []Label
96 var results []LabelChangeResult
97
98 snap := b.Compile()
99
100 for _, str := range add {
101 label := Label(str)
102
103 // check for duplicate
104 if labelExist(added, label) {
105 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeDuplicateInOp})
106 continue
107 }
108
109 // check that the label doesn't already exist
110 if labelExist(snap.Labels, label) {
111 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeAlreadySet})
112 continue
113 }
114
115 added = append(added, label)
116 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeAdded})
117 }
118
119 for _, str := range remove {
120 label := Label(str)
121
122 // check for duplicate
123 if labelExist(removed, label) {
124 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeDuplicateInOp})
125 continue
126 }
127
128 // check that the label actually exist
129 if !labelExist(snap.Labels, label) {
130 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeDoesntExist})
131 continue
132 }
133
134 removed = append(removed, label)
135 results = append(results, LabelChangeResult{Label: label, Status: LabelChangeRemoved})
136 }
137
138 if len(added) == 0 && len(removed) == 0 {
139 return results, fmt.Errorf("no label added or removed")
140 }
141
142 labelOp := NewLabelChangeOperation(author, unixTime, added, removed)
143
144 if err := labelOp.Validate(); err != nil {
145 return nil, err
146 }
147
148 b.Append(labelOp)
149
150 return results, nil
151}
152
153func labelExist(labels []Label, label Label) bool {
154 for _, l := range labels {
155 if l == label {
156 return true
157 }
158 }
159
160 return false
161}
162
163type LabelChangeStatus int
164
165const (
166 _ LabelChangeStatus = iota
167 LabelChangeAdded
168 LabelChangeRemoved
169 LabelChangeDuplicateInOp
170 LabelChangeAlreadySet
171 LabelChangeDoesntExist
172)
173
174type LabelChangeResult struct {
175 Label Label
176 Status LabelChangeStatus
177}
178
179func (l LabelChangeResult) String() string {
180 switch l.Status {
181 case LabelChangeAdded:
182 return fmt.Sprintf("label %s added", l.Label)
183 case LabelChangeRemoved:
184 return fmt.Sprintf("label %s removed", l.Label)
185 case LabelChangeDuplicateInOp:
186 return fmt.Sprintf("label %s is a duplicate", l.Label)
187 case LabelChangeAlreadySet:
188 return fmt.Sprintf("label %s was already set", l.Label)
189 case LabelChangeDoesntExist:
190 return fmt.Sprintf("label %s doesn't exist on this bug", l.Label)
191 default:
192 panic(fmt.Sprintf("unknown label change status %v", l.Status))
193 }
194}