bug.go

  1package bug
  2
  3import (
  4	"crypto/sha256"
  5	"errors"
  6	"fmt"
  7	"github.com/MichaelMure/git-bug/repository"
  8	"github.com/MichaelMure/git-bug/util"
  9	"github.com/kevinburke/go.uuid"
 10	"strings"
 11)
 12
 13const BugsRefPattern = "refs/bugs/"
 14const BugsRemoteRefPattern = "refs/remote/%s/bugs/"
 15const OpsEntryName = "ops"
 16const RootEntryName = "root"
 17
 18// Bug hold the data of a bug thread, organized in a way close to
 19// how it will be persisted inside Git. This is the datastructure
 20// used for merge of two different version.
 21type Bug struct {
 22	// Id used as unique identifier
 23	id string
 24
 25	lastCommit util.Hash
 26	root       util.Hash
 27
 28	// TODO: need a way to order bugs, probably a Lamport clock
 29
 30	packs []OperationPack
 31
 32	staging OperationPack
 33}
 34
 35// Create a new Bug
 36func NewBug() (*Bug, error) {
 37	// TODO: replace with commit hash of (first commit + some random)
 38
 39	// Creating UUID Version 4
 40	unique, err := uuid.ID4()
 41
 42	if err != nil {
 43		return nil, err
 44	}
 45
 46	// Use it as source of uniqueness
 47	hash := sha256.New().Sum(unique.Bytes())
 48
 49	// format in hex and truncate to 40 char
 50	id := fmt.Sprintf("%.40s", fmt.Sprintf("%x", hash))
 51
 52	return &Bug{
 53		id: id,
 54	}, nil
 55}
 56
 57// Find an existing Bug matching a prefix
 58func FindBug(repo repository.Repo, prefix string) (*Bug, error) {
 59	refs, err := repo.ListRefs(BugsRefPattern)
 60
 61	if err != nil {
 62		return nil, err
 63	}
 64
 65	// preallocate but empty
 66	matching := make([]string, 0, 5)
 67
 68	for _, ref := range refs {
 69		if strings.HasPrefix(ref, prefix) {
 70			matching = append(matching, ref)
 71		}
 72	}
 73
 74	if len(matching) == 0 {
 75		return nil, errors.New("No matching bug found.")
 76	}
 77
 78	if len(matching) > 1 {
 79		return nil, fmt.Errorf("Multiple matching bug found:\n%s", strings.Join(matching, "\n"))
 80	}
 81
 82	return ReadBug(repo, matching[0])
 83}
 84
 85// Read and parse a Bug from git
 86func ReadBug(repo repository.Repo, id string) (*Bug, error) {
 87	hashes, err := repo.ListCommits(BugsRefPattern + id)
 88
 89	if err != nil {
 90		return nil, err
 91	}
 92
 93	bug := Bug{
 94		id: id,
 95	}
 96
 97	for _, hash := range hashes {
 98		entries, err := repo.ListEntries(hash)
 99
100		bug.lastCommit = hash
101
102		if err != nil {
103			return nil, err
104		}
105
106		var opsEntry repository.TreeEntry
107		opsFound := false
108		var rootEntry repository.TreeEntry
109		rootFound := false
110
111		for _, entry := range entries {
112			if entry.Name == OpsEntryName {
113				opsEntry = entry
114				opsFound = true
115				continue
116			}
117			if entry.Name == RootEntryName {
118				rootEntry = entry
119				rootFound = true
120			}
121		}
122
123		if !opsFound {
124			return nil, errors.New("Invalid tree, missing the ops entry")
125		}
126
127		if !rootFound {
128			return nil, errors.New("Invalid tree, missing the root entry")
129		}
130
131		if bug.root == "" {
132			bug.root = rootEntry.Hash
133		}
134
135		data, err := repo.ReadData(opsEntry.Hash)
136
137		if err != nil {
138			return nil, err
139		}
140
141		op, err := ParseOperationPack(data)
142
143		if err != nil {
144			return nil, err
145		}
146
147		bug.packs = append(bug.packs, *op)
148	}
149
150	return &bug, nil
151}
152
153// IsValid check if the Bug data is valid
154func (bug *Bug) IsValid() bool {
155	// non-empty
156	if len(bug.packs) == 0 && bug.staging.IsEmpty() {
157		return false
158	}
159
160	// check if each pack is valid
161	for _, pack := range bug.packs {
162		if !pack.IsValid() {
163			return false
164		}
165	}
166
167	// check if staging is valid if needed
168	if !bug.staging.IsEmpty() {
169		if !bug.staging.IsValid() {
170			return false
171		}
172	}
173
174	// The very first Op should be a CREATE
175	firstOp := bug.firstOp()
176	if firstOp == nil || firstOp.OpType() != CREATE {
177		return false
178	}
179
180	// Check that there is no more CREATE op
181	it := NewOperationIterator(bug)
182	createCount := 0
183	for it.Next() {
184		if it.Value().OpType() == CREATE {
185			createCount++
186		}
187	}
188
189	if createCount != 1 {
190		return false
191	}
192
193	return true
194}
195
196func (bug *Bug) Append(op Operation) {
197	bug.staging.Append(op)
198}
199
200// Write the staging area in Git and move the operations to the packs
201func (bug *Bug) Commit(repo repository.Repo) error {
202	if bug.staging.IsEmpty() {
203		return nil
204	}
205
206	// Write the Ops as a Git blob containing the serialized array
207	hash, err := bug.staging.Write(repo)
208	if err != nil {
209		return err
210	}
211
212	root := bug.root
213	if root == "" {
214		root = hash
215		bug.root = hash
216	}
217
218	// Write a Git tree referencing this blob
219	hash, err = repo.StoreTree([]repository.TreeEntry{
220		{repository.Blob, hash, OpsEntryName},  // the last pack of ops
221		{repository.Blob, root, RootEntryName}, // always the first pack of ops (might be the same)
222	})
223	if err != nil {
224		return err
225	}
226
227	// Write a Git commit referencing the tree, with the previous commit as parent
228	if bug.lastCommit != "" {
229		hash, err = repo.StoreCommitWithParent(hash, bug.lastCommit)
230	} else {
231		hash, err = repo.StoreCommit(hash)
232	}
233
234	if err != nil {
235		return err
236	}
237
238	bug.lastCommit = hash
239
240	// Create or update the Git reference for this bug
241	ref := fmt.Sprintf("%s%s", BugsRefPattern, bug.id)
242	err = repo.UpdateRef(ref, hash)
243
244	if err != nil {
245		return err
246	}
247
248	bug.packs = append(bug.packs, bug.staging)
249	bug.staging = OperationPack{}
250
251	return nil
252}
253
254func (bug *Bug) Id() string {
255	return bug.id
256}
257
258func (bug *Bug) HumanId() string {
259	return fmt.Sprintf("%.8s", bug.id)
260}
261
262func (bug *Bug) firstOp() Operation {
263	for _, pack := range bug.packs {
264		for _, op := range pack.Operations {
265			return op
266		}
267	}
268
269	if !bug.staging.IsEmpty() {
270		return bug.staging.Operations[0]
271	}
272
273	return nil
274}
275
276// Compile a bug in a easily usable snapshot
277func (bug *Bug) Compile() Snapshot {
278	snap := Snapshot{}
279
280	it := NewOperationIterator(bug)
281
282	for it.Next() {
283		snap = it.Value().Apply(snap)
284	}
285
286	return snap
287}