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