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