bug.go

  1// Package bug contains the bug data model and low-level related functions
  2package bug
  3
  4import (
  5	"fmt"
  6
  7	"github.com/MichaelMure/git-bug/entity"
  8	"github.com/MichaelMure/git-bug/entity/dag"
  9	"github.com/MichaelMure/git-bug/identity"
 10	"github.com/MichaelMure/git-bug/repository"
 11)
 12
 13var _ Interface = &Bug{}
 14var _ entity.Interface = &Bug{}
 15
 16// 1: original format
 17// 2: no more legacy identities
 18// 3: Ids are generated from the create operation serialized data instead of from the first git commit
 19// 4: with DAG entity framework
 20const formatVersion = 4
 21
 22var def = dag.Definition{
 23	Typename:             "bug",
 24	Namespace:            "bugs",
 25	OperationUnmarshaler: operationUnmarshaller,
 26	FormatVersion:        formatVersion,
 27}
 28
 29var ClockLoader = dag.ClockLoader(def)
 30
 31// Bug holds the data of a bug thread, organized in a way close to
 32// how it will be persisted inside Git. This is the data structure
 33// used to merge two different version of the same Bug.
 34type Bug struct {
 35	*dag.Entity
 36}
 37
 38// NewBug create a new Bug
 39func NewBug() *Bug {
 40	return &Bug{
 41		Entity: dag.New(def),
 42	}
 43}
 44
 45// Read will read a bug from a repository
 46func Read(repo repository.ClockedRepo, id entity.Id) (*Bug, error) {
 47	return ReadWithResolver(repo, identity.NewSimpleResolver(repo), id)
 48}
 49
 50// ReadWithResolver will read a bug from its Id, with a custom identity.Resolver
 51func ReadWithResolver(repo repository.ClockedRepo, identityResolver identity.Resolver, id entity.Id) (*Bug, error) {
 52	e, err := dag.Read(def, repo, identityResolver, id)
 53	if err != nil {
 54		return nil, err
 55	}
 56	return &Bug{Entity: e}, nil
 57}
 58
 59type StreamedBug struct {
 60	Bug *Bug
 61	Err error
 62}
 63
 64// ReadAll read and parse all local bugs
 65func ReadAll(repo repository.ClockedRepo) <-chan StreamedBug {
 66	return readAll(repo, identity.NewSimpleResolver(repo))
 67}
 68
 69// ReadAllWithResolver read and parse all local bugs
 70func ReadAllWithResolver(repo repository.ClockedRepo, identityResolver identity.Resolver) <-chan StreamedBug {
 71	return readAll(repo, identityResolver)
 72}
 73
 74// Read and parse all available bug with a given ref prefix
 75func readAll(repo repository.ClockedRepo, identityResolver identity.Resolver) <-chan StreamedBug {
 76	out := make(chan StreamedBug)
 77
 78	go func() {
 79		defer close(out)
 80
 81		for streamedEntity := range dag.ReadAll(def, repo, identityResolver) {
 82			if streamedEntity.Err != nil {
 83				out <- StreamedBug{
 84					Err: streamedEntity.Err,
 85				}
 86			} else {
 87				out <- StreamedBug{
 88					Bug: &Bug{Entity: streamedEntity.Entity},
 89				}
 90			}
 91		}
 92	}()
 93
 94	return out
 95}
 96
 97// ListLocalIds list all the available local bug ids
 98func ListLocalIds(repo repository.Repo) ([]entity.Id, error) {
 99	return dag.ListLocalIds(def, repo)
100}
101
102// Validate check if the Bug data is valid
103func (bug *Bug) Validate() error {
104	if err := bug.Entity.Validate(); err != nil {
105		return err
106	}
107
108	// The very first Op should be a CreateOp
109	firstOp := bug.FirstOp()
110	if firstOp == nil || firstOp.Type() != CreateOp {
111		return fmt.Errorf("first operation should be a Create op")
112	}
113
114	// Check that there is no more CreateOp op
115	for i, op := range bug.Operations() {
116		if i == 0 {
117			continue
118		}
119		if op.Type() == CreateOp {
120			return fmt.Errorf("only one Create op allowed")
121		}
122	}
123
124	return nil
125}
126
127// Append add a new Operation to the Bug
128func (bug *Bug) Append(op Operation) {
129	bug.Entity.Append(op)
130}
131
132// Operations return the ordered operations
133func (bug *Bug) Operations() []Operation {
134	source := bug.Entity.Operations()
135	result := make([]Operation, len(source))
136	for i, op := range source {
137		result[i] = op.(Operation)
138	}
139	return result
140}
141
142// Compile a bug in a easily usable snapshot
143func (bug *Bug) Compile() Snapshot {
144	snap := Snapshot{
145		id:     bug.Id(),
146		Status: OpenStatus,
147	}
148
149	for _, op := range bug.Operations() {
150		op.Apply(&snap)
151		snap.Operations = append(snap.Operations, op)
152	}
153
154	return snap
155}
156
157// FirstOp lookup for the very first operation of the bug.
158// For a valid Bug, this operation should be a CreateOp
159func (bug *Bug) FirstOp() Operation {
160	if fo := bug.Entity.FirstOp(); fo != nil {
161		return fo.(Operation)
162	}
163	return nil
164}
165
166// LastOp lookup for the very last operation of the bug.
167// For a valid Bug, should never be nil
168func (bug *Bug) LastOp() Operation {
169	if lo := bug.Entity.LastOp(); lo != nil {
170		return lo.(Operation)
171	}
172	return nil
173}