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, *LabelChangeOperation, 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, nil, 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, nil, err
173	}
174
175	b.Append(labelOp)
176
177	return results, labelOp, 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}