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