package bug
import (
"fmt"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util"
"github.com/kevinburke/go.uuid"
)
const BugsRefPattern = "refs/bugs/"
const BugsRemoteRefPattern = "refs/remote/%s/bugs/"
// Bug hold the data of a bug thread, organized in a way close to
// how it will be persisted inside Git. This is the datastructure
// used for merge of two different version.
type Bug struct {
// Id used as unique identifier
id uuid.UUID
lastCommit util.Hash
root util.Hash
// TODO: need a way to order bugs, probably a Lamport clock
packs []OperationPack
staging OperationPack
}
// Create a new Bug
func NewBug() (*Bug, error) {
// Creating UUID Version 4
id, err := uuid.ID4()
if err != nil {
return nil, err
}
return &Bug{
id: id,
lastCommit: "",
}, nil
}
// IsValid check if the Bug data is valid
func (bug *Bug) IsValid() bool {
// non-empty
if len(bug.packs) == 0 && bug.staging.IsEmpty() {
return false
}
// check if each pack is valid
for _, pack := range bug.packs {
if !pack.IsValid() {
return false
}
}
// check if staging is valid if needed
if !bug.staging.IsEmpty() {
if !bug.staging.IsValid() {
return false
}
}
// The very first Op should be a CREATE
firstOp := bug.firstOp()
if firstOp == nil || firstOp.OpType() != CREATE {
return false
}
// Check that there is no more CREATE op
it := NewOperationIterator(bug)
createCount := 0
for it.Next() {
if it.Value().OpType() == CREATE {
createCount++
}
}
if createCount != 1 {
return false
}
return true
}
func (bug *Bug) Append(op Operation) {
bug.staging.Append(op)
}
// Write the staging area in Git and move the operations to the packs
func (bug *Bug) Commit(repo repository.Repo) error {
if bug.staging.IsEmpty() {
return nil
}
// Write the Ops as a Git blob containing the serialized array
hash, err := bug.staging.Write(repo)
if err != nil {
return err
}
root := bug.root
if root == "" {
root = hash
}
// Write a Git tree referencing this blob
hash, err = repo.StoreTree(map[string]util.Hash{
"ops": hash, // the last pack of ops
"root": root, // always the first pack of ops (might be the same)
})
if err != nil {
return err
}
// Write a Git commit referencing the tree, with the previous commit as parent
if bug.lastCommit != "" {
hash, err = repo.StoreCommitWithParent(hash, bug.lastCommit)
} else {
hash, err = repo.StoreCommit(hash)
}
if err != nil {
return err
}
// Create or update the Git reference for this bug
ref := fmt.Sprintf("%s%s", BugsRefPattern, bug.id.String())
err = repo.UpdateRef(ref, hash)
if err != nil {
return err
}
bug.packs = append(bug.packs, bug.staging)
bug.staging = OperationPack{}
return nil
}
func (bug *Bug) HumanId() string {
return bug.id.String()
}
func (bug *Bug) firstOp() Operation {
for _, pack := range bug.packs {
for _, op := range pack.Operations {
return op
}
}
if !bug.staging.IsEmpty() {
return bug.staging.Operations[0]
}
return nil
}