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