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/"
 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}