op_label_change.go

  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}