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