bug.go

  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}