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