1package bug
2
3import (
4 "fmt"
5 "github.com/MichaelMure/git-bug/repository"
6 "github.com/MichaelMure/git-bug/util"
7 "github.com/kevinburke/go.uuid"
8)
9
10const BugsRefPattern = "refs/bugs/"
11const BugsRemoteRefPattern = "refs/remote/%s/bugs/"
12
13// Bug hold the data of a bug thread, organized in a way close to
14// how it will be persisted inside Git. This is the datastructure
15// used for merge of two different version.
16type Bug struct {
17 // Id used as unique identifier
18 id uuid.UUID
19
20 lastCommit util.Hash
21 root util.Hash
22
23 // TODO: need a way to order bugs, probably a Lamport clock
24
25 packs []OperationPack
26
27 staging OperationPack
28}
29
30// Create a new Bug
31func NewBug() (*Bug, error) {
32 // Creating UUID Version 4
33 id, err := uuid.ID4()
34
35 if err != nil {
36 return nil, err
37 }
38
39 return &Bug{
40 id: id,
41 lastCommit: "",
42 }, nil
43}
44
45// IsValid check if the Bug data is valid
46func (bug *Bug) IsValid() bool {
47 // non-empty
48 if len(bug.packs) == 0 && bug.staging.IsEmpty() {
49 return false
50 }
51
52 // check if each pack is valid
53 for _, pack := range bug.packs {
54 if !pack.IsValid() {
55 return false
56 }
57 }
58
59 // check if staging is valid if needed
60 if !bug.staging.IsEmpty() {
61 if !bug.staging.IsValid() {
62 return false
63 }
64 }
65
66 // The very first Op should be a CREATE
67 firstOp := bug.firstOp()
68 if firstOp == nil || firstOp.OpType() != CREATE {
69 return false
70 }
71
72 // Check that there is no more CREATE op
73 it := NewOperationIterator(bug)
74 createCount := 0
75 for it.Next() {
76 if it.Value().OpType() == CREATE {
77 createCount++
78 }
79 }
80
81 if createCount != 1 {
82 return false
83 }
84
85 return true
86}
87
88func (bug *Bug) Append(op Operation) {
89 bug.staging.Append(op)
90}
91
92// Write the staging area in Git and move the operations to the packs
93func (bug *Bug) Commit(repo repository.Repo) error {
94 if bug.staging.IsEmpty() {
95 return nil
96 }
97
98 // Write the Ops as a Git blob containing the serialized array
99 hash, err := bug.staging.Write(repo)
100 if err != nil {
101 return err
102 }
103
104 root := bug.root
105 if root == "" {
106 root = hash
107 }
108
109 // Write a Git tree referencing this blob
110 hash, err = repo.StoreTree(map[string]util.Hash{
111 "ops": hash, // the last pack of ops
112 "root": root, // always the first pack of ops (might be the same)
113 })
114 if err != nil {
115 return err
116 }
117
118 // Write a Git commit referencing the tree, with the previous commit as parent
119 if bug.lastCommit != "" {
120 hash, err = repo.StoreCommitWithParent(hash, bug.lastCommit)
121 } else {
122 hash, err = repo.StoreCommit(hash)
123 }
124
125 if err != nil {
126 return err
127 }
128
129 // Create or update the Git reference for this bug
130 ref := fmt.Sprintf("%s%s", BugsRefPattern, bug.id.String())
131 err = repo.UpdateRef(ref, hash)
132
133 if err != nil {
134 return err
135 }
136
137 bug.packs = append(bug.packs, bug.staging)
138 bug.staging = OperationPack{}
139
140 return nil
141}
142
143func (bug *Bug) HumanId() string {
144 return bug.id.String()
145}
146
147func (bug *Bug) firstOp() Operation {
148 for _, pack := range bug.packs {
149 for _, op := range pack.Operations {
150 return op
151 }
152 }
153
154 if !bug.staging.IsEmpty() {
155 return bug.staging.Operations[0]
156 }
157
158 return nil
159}