op_label_change.go

  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}