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}