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 hold 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 e, err := dag.Read(def, repo, identity.NewSimpleResolver(repo), id)
48 if err != nil {
49 return nil, err
50 }
51 return &Bug{Entity: e}, nil
52}
53
54// ReadWithResolver will read a bug from its Id, with a custom identity.Resolver
55func ReadWithResolver(repo repository.ClockedRepo, identityResolver identity.Resolver, id entity.Id) (*Bug, error) {
56 e, err := dag.Read(def, repo, identityResolver, id)
57 if err != nil {
58 return nil, err
59 }
60 return &Bug{Entity: e}, nil
61}
62
63type StreamedBug struct {
64 Bug *Bug
65 Err error
66}
67
68// ReadAll read and parse all local bugs
69func ReadAll(repo repository.ClockedRepo) <-chan StreamedBug {
70 return readAll(repo, identity.NewSimpleResolver(repo))
71}
72
73// ReadAllWithResolver read and parse all local bugs
74func ReadAllWithResolver(repo repository.ClockedRepo, identityResolver identity.Resolver) <-chan StreamedBug {
75 return readAll(repo, identityResolver)
76}
77
78// Read and parse all available bug with a given ref prefix
79func readAll(repo repository.ClockedRepo, identityResolver identity.Resolver) <-chan StreamedBug {
80 out := make(chan StreamedBug)
81
82 go func() {
83 defer close(out)
84
85 for streamedEntity := range dag.ReadAll(def, repo, identityResolver) {
86 if streamedEntity.Err != nil {
87 out <- StreamedBug{
88 Err: streamedEntity.Err,
89 }
90 } else {
91 out <- StreamedBug{
92 Bug: &Bug{Entity: streamedEntity.Entity},
93 }
94 }
95 }
96 }()
97
98 return out
99}
100
101// ListLocalIds list all the available local bug ids
102func ListLocalIds(repo repository.Repo) ([]entity.Id, error) {
103 return dag.ListLocalIds(def, repo)
104}
105
106// Validate check if the Bug data is valid
107func (bug *Bug) Validate() error {
108 if err := bug.Entity.Validate(); err != nil {
109 return err
110 }
111
112 // The very first Op should be a CreateOp
113 firstOp := bug.FirstOp()
114 if firstOp == nil || firstOp.Type() != CreateOp {
115 return fmt.Errorf("first operation should be a Create op")
116 }
117
118 // Check that there is no more CreateOp op
119 for i, op := range bug.Operations() {
120 if i == 0 {
121 continue
122 }
123 if op.Type() == CreateOp {
124 return fmt.Errorf("only one Create op allowed")
125 }
126 }
127
128 return nil
129}
130
131// Append add a new Operation to the Bug
132func (bug *Bug) Append(op Operation) {
133 bug.Entity.Append(op)
134}
135
136// Operations return the ordered operations
137func (bug *Bug) Operations() []Operation {
138 source := bug.Entity.Operations()
139 result := make([]Operation, len(source))
140 for i, op := range source {
141 result[i] = op.(Operation)
142 }
143 return result
144}
145
146// Compile a bug in a easily usable snapshot
147func (bug *Bug) Compile() Snapshot {
148 snap := Snapshot{
149 id: bug.Id(),
150 Status: OpenStatus,
151 }
152
153 for _, op := range bug.Operations() {
154 op.Apply(&snap)
155 snap.Operations = append(snap.Operations, op)
156 }
157
158 return snap
159}
160
161// Lookup for the very first operation of the bug.
162// For a valid Bug, this operation should be a CreateOp
163func (bug *Bug) FirstOp() Operation {
164 if fo := bug.Entity.FirstOp(); fo != nil {
165 return fo.(Operation)
166 }
167 return nil
168}
169
170// Lookup for the very last operation of the bug.
171// For a valid Bug, should never be nil
172func (bug *Bug) LastOp() Operation {
173 if lo := bug.Entity.LastOp(); lo != nil {
174 return lo.(Operation)
175 }
176 return nil
177}