op_label_change.go

  1package bug
  2
  3import (
  4	"fmt"
  5	"sort"
  6
  7	"github.com/pkg/errors"
  8
  9	"github.com/MichaelMure/git-bug/entity"
 10	"github.com/MichaelMure/git-bug/entity/dag"
 11	"github.com/MichaelMure/git-bug/identity"
 12	"github.com/MichaelMure/git-bug/util/timestamp"
 13)
 14
 15var _ Operation = &LabelChangeOperation{}
 16
 17// LabelChangeOperation define a Bug operation to add or remove labels
 18type LabelChangeOperation struct {
 19	dag.OpBase
 20	Added   []Label `json:"added"`
 21	Removed []Label `json:"removed"`
 22}
 23
 24func (op *LabelChangeOperation) Id() entity.Id {
 25	return dag.IdOperation(op, &op.OpBase)
 26}
 27
 28// Apply apply the operation
 29func (op *LabelChangeOperation) Apply(snapshot *Snapshot) {
 30	snapshot.addActor(op.Author())
 31
 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	item := &LabelChangeTimelineItem{
 61		id:       op.Id(),
 62		Author:   op.Author(),
 63		UnixTime: timestamp.Timestamp(op.UnixTime),
 64		Added:    op.Added,
 65		Removed:  op.Removed,
 66	}
 67
 68	snapshot.Timeline = append(snapshot.Timeline, item)
 69}
 70
 71func (op *LabelChangeOperation) Validate() error {
 72	if err := op.OpBase.Validate(op, LabelChangeOp); err != nil {
 73		return err
 74	}
 75
 76	for _, l := range op.Added {
 77		if err := l.Validate(); err != nil {
 78			return errors.Wrap(err, "added label")
 79		}
 80	}
 81
 82	for _, l := range op.Removed {
 83		if err := l.Validate(); err != nil {
 84			return errors.Wrap(err, "removed label")
 85		}
 86	}
 87
 88	if len(op.Added)+len(op.Removed) <= 0 {
 89		return fmt.Errorf("no label change")
 90	}
 91
 92	return nil
 93}
 94
 95func NewLabelChangeOperation(author identity.Interface, unixTime int64, added, removed []Label) *LabelChangeOperation {
 96	return &LabelChangeOperation{
 97		OpBase:  dag.NewOpBase(LabelChangeOp, author, unixTime),
 98		Added:   added,
 99		Removed: removed,
100	}
101}
102
103type LabelChangeTimelineItem struct {
104	id       entity.Id
105	Author   identity.Interface
106	UnixTime timestamp.Timestamp
107	Added    []Label
108	Removed  []Label
109}
110
111func (l LabelChangeTimelineItem) Id() entity.Id {
112	return l.id
113}
114
115// IsAuthored is a sign post method for gqlgen
116func (l *LabelChangeTimelineItem) IsAuthored() {}
117
118// ChangeLabels is a convenience function to apply the operation
119func ChangeLabels(b Interface, author identity.Interface, unixTime int64, add, remove []string) ([]LabelChangeResult, *LabelChangeOperation, error) {
120	var added, removed []Label
121	var results []LabelChangeResult
122
123	snap := b.Compile()
124
125	for _, str := range add {
126		label := Label(str)
127
128		// check for duplicate
129		if labelExist(added, label) {
130			results = append(results, LabelChangeResult{Label: label, Status: LabelChangeDuplicateInOp})
131			continue
132		}
133
134		// check that the label doesn't already exist
135		if labelExist(snap.Labels, label) {
136			results = append(results, LabelChangeResult{Label: label, Status: LabelChangeAlreadySet})
137			continue
138		}
139
140		added = append(added, label)
141		results = append(results, LabelChangeResult{Label: label, Status: LabelChangeAdded})
142	}
143
144	for _, str := range remove {
145		label := Label(str)
146
147		// check for duplicate
148		if labelExist(removed, label) {
149			results = append(results, LabelChangeResult{Label: label, Status: LabelChangeDuplicateInOp})
150			continue
151		}
152
153		// check that the label actually exist
154		if !labelExist(snap.Labels, label) {
155			results = append(results, LabelChangeResult{Label: label, Status: LabelChangeDoesntExist})
156			continue
157		}
158
159		removed = append(removed, label)
160		results = append(results, LabelChangeResult{Label: label, Status: LabelChangeRemoved})
161	}
162
163	if len(added) == 0 && len(removed) == 0 {
164		return results, nil, fmt.Errorf("no label added or removed")
165	}
166
167	labelOp := NewLabelChangeOperation(author, unixTime, added, removed)
168
169	if err := labelOp.Validate(); err != nil {
170		return nil, nil, err
171	}
172
173	b.Append(labelOp)
174
175	return results, labelOp, nil
176}
177
178// ForceChangeLabels is a convenience function to apply the operation
179// The difference with ChangeLabels is that no checks of deduplications are done. You are entirely
180// responsible of what you are doing. In the general case, you want to use ChangeLabels instead.
181// The intended use of this function is to allow importers to create legal but unexpected label changes,
182// like removing a label with no information of when it was added before.
183func ForceChangeLabels(b Interface, author identity.Interface, unixTime int64, add, remove []string) (*LabelChangeOperation, error) {
184	added := make([]Label, len(add))
185	for i, str := range add {
186		added[i] = Label(str)
187	}
188
189	removed := make([]Label, len(remove))
190	for i, str := range remove {
191		removed[i] = Label(str)
192	}
193
194	labelOp := NewLabelChangeOperation(author, unixTime, added, removed)
195
196	if err := labelOp.Validate(); err != nil {
197		return nil, err
198	}
199
200	b.Append(labelOp)
201
202	return labelOp, nil
203}
204
205func labelExist(labels []Label, label Label) bool {
206	for _, l := range labels {
207		if l == label {
208			return true
209		}
210	}
211
212	return false
213}
214
215type LabelChangeStatus int
216
217const (
218	_ LabelChangeStatus = iota
219	LabelChangeAdded
220	LabelChangeRemoved
221	LabelChangeDuplicateInOp
222	LabelChangeAlreadySet
223	LabelChangeDoesntExist
224)
225
226type LabelChangeResult struct {
227	Label  Label
228	Status LabelChangeStatus
229}
230
231func (l LabelChangeResult) String() string {
232	switch l.Status {
233	case LabelChangeAdded:
234		return fmt.Sprintf("label %s added", l.Label)
235	case LabelChangeRemoved:
236		return fmt.Sprintf("label %s removed", l.Label)
237	case LabelChangeDuplicateInOp:
238		return fmt.Sprintf("label %s is a duplicate", l.Label)
239	case LabelChangeAlreadySet:
240		return fmt.Sprintf("label %s was already set", l.Label)
241	case LabelChangeDoesntExist:
242		return fmt.Sprintf("label %s doesn't exist on this bug", l.Label)
243	default:
244		panic(fmt.Sprintf("unknown label change status %v", l.Status))
245	}
246}