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