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