diff options
author | Michael Muré <batolettre@gmail.com> | 2018-07-13 21:21:24 +0200 |
---|---|---|
committer | Michael Muré <batolettre@gmail.com> | 2018-07-13 21:21:24 +0200 |
commit | 1779a0f3b92d58654b43444addeaf437a64d77a8 (patch) | |
tree | 9f973413454894f0456d7379425070d468712242 | |
parent | 289f8d53ee960d35c1f0c42e8753ad536737b875 (diff) | |
download | git-bug-1779a0f3b92d58654b43444addeaf437a64d77a8.tar.gz |
serialize a Bug to git as a blob+tree+commit+ref
-rw-r--r-- | bug/bug.go | 86 | ||||
-rw-r--r-- | bug/comment.go | 3 | ||||
-rw-r--r-- | bug/operation.go | 8 | ||||
-rw-r--r-- | bug/operation_iterator.go | 20 | ||||
-rw-r--r-- | bug/operation_pack.go | 39 | ||||
-rw-r--r-- | bug/operations/create.go | 14 | ||||
-rw-r--r-- | bug/operations/set_title.go | 10 | ||||
-rw-r--r-- | commands/commands.go | 1 | ||||
-rw-r--r-- | commands/new.go | 2 | ||||
-rw-r--r-- | commands/pull.go | 3 | ||||
-rw-r--r-- | commands/push.go | 3 | ||||
-rw-r--r-- | git-bug.go | 2 | ||||
-rw-r--r-- | notes | 2 | ||||
-rw-r--r-- | repository/git.go | 801 | ||||
-rw-r--r-- | repository/mock_repo.go | 53 | ||||
-rw-r--r-- | repository/repo.go | 14 | ||||
-rw-r--r-- | tests/bug_test.go | 2 | ||||
-rw-r--r-- | tests/operation_iterator_test.go | 6 | ||||
-rw-r--r-- | tests/operation_pack_test.go | 37 |
19 files changed, 301 insertions, 805 deletions
@@ -1,27 +1,32 @@ 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/" + // 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 + id uuid.UUID + + lastCommit util.Hash - // TODO: need a way to order bugs - // Probably a Lamport clock + // TODO: need a way to order bugs, probably a Lamport clock - Packs []OperationPack + packs []OperationPack - Staging OperationPack + staging OperationPack } // Create a new Bug func NewBug() (*Bug, error) { - // Creating UUID Version 4 id, err := uuid.ID4() @@ -30,27 +35,28 @@ func NewBug() (*Bug, error) { } return &Bug{ - Id: id, + 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() { + if len(bug.packs) == 0 && bug.staging.IsEmpty() { return false } // check if each pack is valid - for _, pack := range bug.Packs { + 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() { + // check if staging is valid if needed + if !bug.staging.IsEmpty() { + if !bug.staging.IsValid() { return false } } @@ -78,27 +84,67 @@ func (bug *Bug) IsValid() bool { } func (bug *Bug) Append(op Operation) { - bug.Staging.Append(op) + bug.staging.Append(op) } -func (bug *Bug) Commit() { - bug.Packs = append(bug.Packs, bug.Staging) - bug.Staging = OperationPack{} +// Write the staging area in Git 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 + } + + // Write a Git tree referencing this blob + hash, err = repo.StoreTree(map[string]util.Hash{ + "ops": hash, + }) + 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() + return bug.id.String() } func (bug *Bug) firstOp() Operation { - for _, pack := range bug.Packs { + for _, pack := range bug.packs { for _, op := range pack.Operations { return op } } - if !bug.Staging.IsEmpty() { - return bug.Staging.Operations[0] + if !bug.staging.IsEmpty() { + return bug.staging.Operations[0] } return nil diff --git a/bug/comment.go b/bug/comment.go index edd5666c..f7727709 100644 --- a/bug/comment.go +++ b/bug/comment.go @@ -1,9 +1,6 @@ package bug -import "github.com/MichaelMure/git-bug/util" - type Comment struct { Author Person Message string - Media []util.Hash } diff --git a/bug/operation.go b/bug/operation.go index f36e9e39..591c7176 100644 --- a/bug/operation.go +++ b/bug/operation.go @@ -13,3 +13,11 @@ type Operation interface { OpType() OperationType Apply(snapshot Snapshot) Snapshot } + +type OpBase struct { + OperationType OperationType `json:"op"` +} + +func (op OpBase) OpType() OperationType { + return op.OperationType +} diff --git a/bug/operation_iterator.go b/bug/operation_iterator.go index fe001d45..0df8b599 100644 --- a/bug/operation_iterator.go +++ b/bug/operation_iterator.go @@ -16,17 +16,17 @@ func NewOperationIterator(bug *Bug) *OperationIterator { func (it *OperationIterator) Next() bool { // Special case of the staging area - if it.packIndex == len(it.bug.Packs) { - pack := it.bug.Staging + if it.packIndex == len(it.bug.packs) { + pack := it.bug.staging it.opIndex++ return it.opIndex < len(pack.Operations) } - if it.packIndex >= len(it.bug.Packs) { + if it.packIndex >= len(it.bug.packs) { return false } - pack := it.bug.Packs[it.packIndex] + pack := it.bug.packs[it.packIndex] it.opIndex++ @@ -39,17 +39,17 @@ func (it *OperationIterator) Next() bool { it.packIndex++ // Special case of the non-empty staging area - if it.packIndex == len(it.bug.Packs) && len(it.bug.Staging.Operations) > 0 { + if it.packIndex == len(it.bug.packs) && len(it.bug.staging.Operations) > 0 { return true } - return it.packIndex < len(it.bug.Packs) + return it.packIndex < len(it.bug.packs) } func (it *OperationIterator) Value() Operation { // Special case of the staging area - if it.packIndex == len(it.bug.Packs) { - pack := it.bug.Staging + if it.packIndex == len(it.bug.packs) { + pack := it.bug.staging if it.opIndex >= len(pack.Operations) { panic("Iterator is not valid anymore") @@ -58,11 +58,11 @@ func (it *OperationIterator) Value() Operation { return pack.Operations[it.opIndex] } - if it.packIndex >= len(it.bug.Packs) { + if it.packIndex >= len(it.bug.packs) { panic("Iterator is not valid anymore") } - pack := it.bug.Packs[it.packIndex] + pack := it.bug.packs[it.packIndex] if it.opIndex >= len(pack.Operations) { panic("Iterator is not valid anymore") diff --git a/bug/operation_pack.go b/bug/operation_pack.go index 21376b9c..67a2a072 100644 --- a/bug/operation_pack.go +++ b/bug/operation_pack.go @@ -1,5 +1,11 @@ package bug +import ( + "encoding/json" + "github.com/MichaelMure/git-bug/repository" + "github.com/MichaelMure/git-bug/util" +) + // OperationPack represent an ordered set of operation to apply // to a Bug. These operations are stored in a single Git commit. // @@ -7,7 +13,22 @@ package bug // inside Git to form the complete ordered chain of operation to // apply to get the final state of the Bug type OperationPack struct { - Operations []Operation + Operations []Operation `json:"ops"` + hash util.Hash +} + +func Parse() (OperationPack, error) { + // TODO + return OperationPack{}, nil +} + +func (opp *OperationPack) Serialize() ([]byte, error) { + jsonBytes, err := json.Marshal(*opp) + if err != nil { + return nil, err + } + + return jsonBytes, nil } // Append a new operation to the pack @@ -22,3 +43,19 @@ func (opp *OperationPack) IsEmpty() bool { func (opp *OperationPack) IsValid() bool { return !opp.IsEmpty() } + +func (opp *OperationPack) Write(repo repository.Repo) (util.Hash, error) { + data, err := opp.Serialize() + + if err != nil { + return "", err + } + + hash, err := repo.StoreData(data) + + if err != nil { + return "", err + } + + return hash, nil +} diff --git a/bug/operations/create.go b/bug/operations/create.go index 57cca907..9911ee89 100644 --- a/bug/operations/create.go +++ b/bug/operations/create.go @@ -10,22 +10,24 @@ import ( var _ bug.Operation = CreateOperation{} type CreateOperation struct { - Title string - Message string - Author bug.Person + bug.OpBase + Title string `json:"t"` + Message string `json:"m"` + Author bug.Person `json:"a"` } func NewCreateOp(author bug.Person, title, message string) CreateOperation { return CreateOperation{ + OpBase: bug.OpBase{OperationType: bug.CREATE}, Title: title, Message: message, Author: author, } } -func (op CreateOperation) OpType() bug.OperationType { - return bug.CREATE -} +//func (op CreateOperation) OpType() bug.OperationType { +// return bug.CREATE +//} func (op CreateOperation) Apply(snapshot bug.Snapshot) bug.Snapshot { empty := bug.Snapshot{} diff --git a/bug/operations/set_title.go b/bug/operations/set_title.go index 1e2ef20a..39f4b332 100644 --- a/bug/operations/set_title.go +++ b/bug/operations/set_title.go @@ -5,19 +5,17 @@ import "github.com/MichaelMure/git-bug/bug" var _ bug.Operation = SetTitleOperation{} type SetTitleOperation struct { - Title string + bug.OpBase + Title string `json:"t"` } func NewSetTitleOp(title string) SetTitleOperation { return SetTitleOperation{ - Title: title, + OpBase: bug.OpBase{OperationType: bug.SET_TITLE}, + Title: title, } } -func (op SetTitleOperation) OpType() bug.OperationType { - return bug.SET_TITLE -} - func (op SetTitleOperation) Apply(snapshot bug.Snapshot) bug.Snapshot { snapshot.Title = op.Title return snapshot diff --git a/commands/commands.go b/commands/commands.go index 2cc16380..343d1c71 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -5,7 +5,6 @@ import ( "github.com/MichaelMure/git-bug/repository" ) -const bugsRefPattern = "refs/bugs/*" const messageFilename = "BUG_MESSAGE_EDITMSG" // Command represents the definition of a single command. diff --git a/commands/new.go b/commands/new.go index f3127fc6..ab237e32 100644 --- a/commands/new.go +++ b/commands/new.go @@ -59,7 +59,7 @@ func RunNewBug(repo repository.Repo, args []string) error { createOp := operations.NewCreateOp(author, title, *newMessage) newbug.Append(createOp) - newbug.Commit() + newbug.Commit(repo) return nil diff --git a/commands/pull.go b/commands/pull.go index 20009a00..b3541c0f 100644 --- a/commands/pull.go +++ b/commands/pull.go @@ -3,6 +3,7 @@ package commands import ( "errors" "fmt" + "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/repository" ) @@ -16,7 +17,7 @@ func pull(repo repository.Repo, args []string) error { remote = args[0] } - if err := repo.PullRefs(remote, bugsRefPattern); err != nil { + if err := repo.PullRefs(remote, bug.BugsRefPattern+"*"); err != nil { return err } return nil diff --git a/commands/push.go b/commands/push.go index 9b808511..54a657b9 100644 --- a/commands/push.go +++ b/commands/push.go @@ -3,6 +3,7 @@ package commands import ( "errors" "fmt" + "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/repository" ) @@ -16,7 +17,7 @@ func push(repo repository.Repo, args []string) error { remote = args[0] } - if err := repo.PushRefs(remote, bugsRefPattern); err != nil { + if err := repo.PushRefs(remote, bug.BugsRefPattern+"*"); err != nil { return err } return nil @@ -43,7 +43,7 @@ func main() { // git bug if len(args) == 1 { - fmt.Println("Not implemented") + fmt.Println("Will list bugs, not implemented yet") //TODO: list bugs return } @@ -17,6 +17,8 @@ git fetch origin "refs/bug/*:refs/bug/*" git show-ref refs/bug +git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt + Bug operations: diff --git a/repository/git.go b/repository/git.go index 0b7fad34..8c20d0b8 100644 --- a/repository/git.go +++ b/repository/git.go @@ -7,11 +7,14 @@ import ( "fmt" "github.com/MichaelMure/git-bug/util" "io" - "os" "os/exec" "strings" ) +// This is used to have a different staging area than the regular git index +// when creating data in git +const gitEnvConfig = "GIT_INDEX_FILE=BUG_STAGING_INDEX" + // GitRepo represents an instance of a (local) git repository. type GitRepo struct { Path string @@ -24,20 +27,23 @@ func (repo *GitRepo) runGitCommandWithIO(stdin io.Reader, stdout, stderr io.Writ cmd.Stdin = stdin cmd.Stdout = stdout cmd.Stderr = stderr + + //cmd.Env = append(cmd.Env, gitEnvConfig) + return cmd.Run() } // Run the given git command and return its stdout, or an error if the command fails. -func (repo *GitRepo) runGitCommandRaw(args ...string) (string, string, error) { +func (repo *GitRepo) runGitCommandRaw(stdin io.Reader, args ...string) (string, string, error) { var stdout bytes.Buffer var stderr bytes.Buffer - err := repo.runGitCommandWithIO(nil, &stdout, &stderr, args...) + err := repo.runGitCommandWithIO(stdin, &stdout, &stderr, args...) return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), err } // Run the given git command and return its stdout, or an error if the command fails. -func (repo *GitRepo) runGitCommand(args ...string) (string, error) { - stdout, stderr, err := repo.runGitCommandRaw(args...) +func (repo *GitRepo) runGitCommandWithStdin(stdin io.Reader, args ...string) (string, error) { + stdout, stderr, err := repo.runGitCommandRaw(stdin, args...) if err != nil { if stderr == "" { stderr = "Error running git command: " + strings.Join(args, " ") @@ -47,16 +53,16 @@ func (repo *GitRepo) runGitCommand(args ...string) (string, error) { return stdout, err } -// Run the given git command using the same stdin, stdout, and stderr as the review tool. -func (repo *GitRepo) runGitCommandInline(args ...string) error { - return repo.runGitCommandWithIO(os.Stdin, os.Stdout, os.Stderr, args...) +// Run the given git command and return its stdout, or an error if the command fails. +func (repo *GitRepo) runGitCommand(args ...string) (string, error) { + return repo.runGitCommandWithStdin(nil, args...) } // NewGitRepo determines if the given working directory is inside of a git repository, // and returns the corresponding GitRepo instance if it is. func NewGitRepo(path string) (*GitRepo, error) { repo := &GitRepo{Path: path} - _, _, err := repo.runGitCommandRaw("rev-parse") + _, err := repo.runGitCommand("rev-parse") if err == nil { return repo, nil } @@ -95,7 +101,7 @@ func (repo *GitRepo) GetCoreEditor() (string, error) { // PullRefs pull git refs from a remote func (repo *GitRepo) PullRefs(remote string, refPattern string) error { fetchRefSpec := fmt.Sprintf("+%s:%s", refPattern, refPattern) - err := repo.runGitCommandInline("fetch", remote, fetchRefSpec) + _, err := repo.runGitCommand("fetch", remote, fetchRefSpec) // TODO: merge new data @@ -106,7 +112,7 @@ func (repo *GitRepo) PullRefs(remote string, refPattern string) error { func (repo *GitRepo) PushRefs(remote string, refPattern string) error { // The push is liable to fail if the user forgot to do a pull first, so // we treat errors as user errors rather than fatal errors. - err := repo.runGitCommandInline("push", remote, refPattern) + _, err := repo.runGitCommand("push", remote, refPattern) if err != nil { return fmt.Errorf("failed to push to the remote '%s': %v", remote, err) } @@ -116,733 +122,54 @@ func (repo *GitRepo) PushRefs(remote string, refPattern string) error { // StoreData will store arbitrary data and return the corresponding hash func (repo *GitRepo) StoreData(data []byte) (util.Hash, error) { var stdin = bytes.NewReader(data) - var stdout bytes.Buffer - var stderr bytes.Buffer - err := repo.runGitCommandWithIO(stdin, &stdout, &stderr, "hash-object", "--stdin", "-w") + stdout, err := repo.runGitCommandWithStdin(stdin, "hash-object", "--stdin", "-w") + + return util.Hash(stdout), err +} + +// StoreTree will store a mapping key-->Hash as a Git tree +func (repo *GitRepo) StoreTree(mapping map[string]util.Hash) (util.Hash, error) { + var buffer bytes.Buffer + + for key, hash := range mapping { + buffer.WriteString(fmt.Sprintf("100644 blob %s\t%s\n", hash, key)) + } + + stdout, err := repo.runGitCommandWithStdin(&buffer, "mktree") + if err != nil { + return "", err + } - return util.Hash(stdout.String()), err + return util.Hash(stdout), nil } -/* -// -//// GetSubmitStrategy returns the way in which a review is submitted -//func (repo *GitRepo) GetSubmitStrategy() (string, error) { -// submitStrategy, _ := repo.runGitCommand("config", "appraise.submit") -// return submitStrategy, nil -//} -// -//// HasUncommittedChanges returns true if there are local, uncommitted changes. -//func (repo *GitRepo) HasUncommittedChanges() (bool, error) { -// out, err := repo.runGitCommand("status", "--porcelain") -// if err != nil { -// return false, err -// } -// if len(out) > 0 { -// return true, nil -// } -// return false, nil -//} -// -//// VerifyCommit verifies that the supplied hash points to a known commit. -//func (repo *GitRepo) VerifyCommit(hash string) error { -// out, err := repo.runGitCommand("cat-file", "-t", hash) -// if err != nil { -// return err -// } -// objectType := strings.TrimSpace(string(out)) -// if objectType != "commit" { -// return fmt.Errorf("Hash %q points to a non-commit object of type %q", hash, objectType) -// } -// return nil -//} -// -//// VerifyGitRef verifies that the supplied ref points to a known commit. -//func (repo *GitRepo) VerifyGitRef(ref string) error { -// _, err := repo.runGitCommand("show-ref", "--verify", ref) -// return err -//} -// -//// GetHeadRef returns the ref that is the current HEAD. -//func (repo *GitRepo) GetHeadRef() (string, error) { -// return repo.runGitCommand("symbolic-ref", "HEAD") -//} -// -//// GetCommitHash returns the hash of the commit pointed to by the given ref. -//func (repo *GitRepo) GetCommitHash(ref string) (string, error) { -// return repo.runGitCommand("show", "-s", "--format=%H", ref) -//} -// -//// ResolveRefCommit returns the commit pointed to by the given ref, which may be a remote ref. -//// -//// This differs from GetCommitHash which only works on exact matches, in that it will try to -//// intelligently handle the scenario of a ref not existing locally, but being known to exist -//// in a remote repo. -//// -//// This method should be used when a command may be performed by either the reviewer or the -//// reviewee, while GetCommitHash should be used when the encompassing command should only be -//// performed by the reviewee. -//func (repo *GitRepo) ResolveRefCommit(ref string) (string, error) { -// if err := repo.VerifyGitRef(ref); err == nil { -// return repo.GetCommitHash(ref) -// } -// if strings.HasPrefix(ref, "refs/heads/") { -// // The ref is a branch. Check if it exists in exactly one remote -// pattern := strings.Replace(ref, "refs/heads", "**", 1) -// matchingOutput, err := repo.runGitCommand("for-each-ref", "--format=%(refname)", pattern) -// if err != nil { -// return "", err -// } -// matchingRefs := strings.Split(matchingOutput, "\n") -// if len(matchingRefs) == 1 && matchingRefs[0] != "" { -// // There is exactly one match -// return repo.GetCommitHash(matchingRefs[0]) -// } -// return "", fmt.Errorf("Unable to find a git ref matching the pattern %q", pattern) -// } -// return "", fmt.Errorf("Unknown git ref %q", ref) -//} -// -//// GetCommitMessage returns the message stored in the commit pointed to by the given ref. -//func (repo *GitRepo) GetCommitMessage(ref string) (string, error) { -// return repo.runGitCommand("show", "-s", "--format=%B", ref) -//} -// -//// GetCommitTime returns the commit time of the commit pointed to by the given ref. -//func (repo *GitRepo) GetCommitTime(ref string) (string, error) { -// return repo.runGitCommand("show", "-s", "--format=%ct", ref) -//} -// -//// GetLastParent returns the last parent of the given commit (as ordered by git). -//func (repo *GitRepo) GetLastParent(ref string) (string, error) { -// return repo.runGitCommand("rev-list", "--skip", "1", "-n", "1", ref) -//} -// -//// GetCommitDetails returns the details of a commit's metadata. -//func (repo GitRepo) GetCommitDetails(ref string) (*CommitDetails, error) { -// var err error -// show := func(formatString string) (result string) { -// if err != nil { -// return "" -// } -// result, err = repo.runGitCommand("show", "-s", ref, fmt.Sprintf("--format=tformat:%s", formatString)) -// return result -// } -// -// jsonFormatString := "{\"tree\":\"%T\", \"time\": \"%at\"}" -// detailsJSON := show(jsonFormatString) -// if err != nil { -// return nil, err -// } -// var details CommitDetails -// err = json.Unmarshal([]byte(detailsJSON), &details) -// if err != nil { -// return nil, err -// } -// details.Author = show("%an") -// details.AuthorEmail = show("%ae") -// details.Summary = show("%s") -// parentsString := show("%P") -// details.Parents = strings.Split(parentsString, " ") -// if err != nil { -// return nil, err -// } -// return &details, nil -//} -// -//// MergeBase determines if the first commit that is an ancestor of the two arguments. -//func (repo *GitRepo) MergeBase(a, b string) (string, error) { -// return repo.runGitCommand("merge-base", a, b) -//} -// -//// IsAncestor determines if the first argument points to a commit that is an ancestor of the second. -//func (repo *GitRepo) IsAncestor(ancestor, descendant string) (bool, error) { -// _, _, err := repo.runGitCommandRaw("merge-base", "--is-ancestor", ancestor, descendant) -// if err == nil { -// return true, nil -// } -// if _, ok := err.(*exec.ExitError); ok { -// return false, nil -// } -// return false, fmt.Errorf("Error while trying to determine commit ancestry: %v", err) -//} -// -//// Diff computes the diff between two given commits. -//func (repo *GitRepo) Diff(left, right string, diffArgs ...string) (string, error) { -// args := []string{"diff"} -// args = append(args, diffArgs...) -// args = append(args, fmt.Sprintf("%s..%s", left, right)) -// return repo.runGitCommand(args...) -//} -// -//// Show returns the contents of the given file at the given commit. -//func (repo *GitRepo) Show(commit, path string) (string, error) { -// return repo.runGitCommand("show", fmt.Sprintf("%s:%s", commit, path)) -//} -// -//// SwitchToRef changes the currently-checked-out ref. -//func (repo *GitRepo) SwitchToRef(ref string) error { -// // If the ref starts with "refs/heads/", then we have to trim that prefix, -// // or else we will wind up in a detached HEAD state. -// if strings.HasPrefix(ref, branchRefPrefix) { -// ref = ref[len(branchRefPrefix):] -// } -// _, err := repo.runGitCommand("checkout", ref) -// return err -//} -// -//// mergeArchives merges two archive refs. -//func (repo *GitRepo) mergeArchives(archive, remoteArchive string) error { -// remoteHash, err := repo.GetCommitHash(remoteArchive) -// if err != nil { -// return err -// } -// if remoteHash == "" { -// // The remote archive does not exist, so we have nothing to do -// return nil -// } -// -// archiveHash, err := repo.GetCommitHash(archive) -// if err != nil { -// return err -// } -// if archiveHash == "" { -// // The local archive does not exist, so we merely need to set it -// _, err := repo.runGitCommand("update-ref", archive, remoteHash) -// return err -// } -// -// isAncestor, err := repo.IsAncestor(archiveHash, remoteHash) -// if err != nil { -// return err -// } -// if isAncestor { -// // The archive can simply be fast-forwarded -// _, err := repo.runGitCommand("update-ref", archive, remoteHash, archiveHash) -// return err -// } -// -// // Create a merge commit of the two archives -// refDetails, err := repo.GetCommitDetails(remoteArchive) -// if err != nil { -// return err -// } -// newArchiveHash, err := repo.runGitCommand("commit-tree", "-p", remoteHash, "-p", archiveHash, "-m", "Merge local and remote archives", refDetails.Tree) -// if err != nil { -// return err -// } -// newArchiveHash = strings.TrimSpace(newArchiveHash) -// _, err = repo.runGitCommand("update-ref", archive, newArchiveHash, archiveHash) -// return err -//} -// -//// ArchiveRef adds the current commit pointed to by the 'ref' argument -//// under the ref specified in the 'archive' argument. -//// -//// Both the 'ref' and 'archive' arguments are expected to be the fully -//// qualified names of git refs (e.g. 'refs/heads/my-change' or -//// 'refs/devtools/archives/reviews'). -//// -//// If the ref pointed to by the 'archive' argument does not exist -//// yet, then it will be created. -//func (repo *GitRepo) ArchiveRef(ref, archive string) error { -// refHash, err := repo.GetCommitHash(ref) -// if err != nil { -// return err -// } -// refDetails, err := repo.GetCommitDetails(ref) -// if err != nil { -// return err -// } -// -// commitTreeArgs := []string{"commit-tree"} -// archiveHash, err := repo.GetCommitHash(archive) -// if err != nil { -// archiveHash = "" -// } else { -// commitTreeArgs = append(commitTreeArgs, "-p", archiveHash) -// } -// commitTreeArgs = append(commitTreeArgs, "-p", refHash, "-m", fmt.Sprintf("Archive %s", refHash), refDetails.Tree) -// newArchiveHash, err := repo.runGitCommand(commitTreeArgs...) -// if err != nil { -// return err -// } -// newArchiveHash = strings.TrimSpace(newArchiveHash) -// updateRefArgs := []string{"update-ref", archive, newArchiveHash} -// if archiveHash != "" { -// updateRefArgs = append(updateRefArgs, archiveHash) -// } -// _, err = repo.runGitCommand(updateRefArgs...) -// return err -//} -// -//// MergeRef merges the given ref into the current one. -//// -//// The ref argument is the ref to merge, and fastForward indicates that the -//// current ref should only move forward, as opposed to creating a bubble merge. -//// The messages argument(s) provide text that should be included in the default -//// merge commit message (separated by blank lines). -//func (repo *GitRepo) MergeRef(ref string, fastForward bool, messages ...string) error { -// args := []string{"merge"} -// if fastForward { -// args = append(args, "--ff", "--ff-only") -// } else { -// args = append(args, "--no-ff") -// } -// if len(messages) > 0 { -// commitMessage := strings.Join(messages, "\n\n") -// args = append(args, "-e", "-m", commitMessage) -// } -// args = append(args, ref) -// return repo.runGitCommandInline(args...) -//} -// -//// RebaseRef rebases the current ref onto the given one. -//func (repo *GitRepo) RebaseRef(ref string) error { -// return repo.runGitCommandInline("rebase", "-i", ref) -//} -// -//// ListCommits returns the list of commits reachable from the given ref. -//// -//// The generated list is in chronological order (with the oldest commit first). -//// -//// If the specified ref does not exist, then this method returns an empty result. -//func (repo *GitRepo) ListCommits(ref string) []string { -// var stdout bytes.Buffer -// var stderr bytes.Buffer -// if err := repo.runGitCommandWithIO(nil, &stdout, &stderr, "rev-list", "--reverse", ref); err != nil { -// return nil -// } -// -// byteLines := bytes.Split(stdout.Bytes(), []byte("\n")) -// var commits []string -// for _, byteLine := range byteLines { -// commits = append(commits, string(byteLine)) -// } -// return commits -//} -// -//// ListCommitsBetween returns the list of commits between the two given revisions. -//// -//// The "from" parameter is the starting point (exclusive), and the "to" -//// parameter is the ending point (inclusive). -//// -//// The "from" commit does not need to be an ancestor of the "to" commit. If it -//// is not, then the merge base of the two is used as the starting point. -//// Admittedly, this makes calling these the "between" commits is a bit of a -//// misnomer, but it also makes the method easier to use when you want to -//// generate the list of changes in a feature branch, as it eliminates the need -//// to explicitly calculate the merge base. This also makes the semantics of the -//// method compatible with git's built-in "rev-list" command. -//// -//// The generated list is in chronological order (with the oldest commit first). -//func (repo *GitRepo) ListCommitsBetween(from, to string) ([]string, error) { -// out, err := repo.runGitCommand("rev-list", "--reverse", from+".."+to) -// if err != nil { -// return nil, err -// } -// if out == "" { -// return nil, nil -// } -// return strings.Split(out, "\n"), nil -//} -// -//// GetNotes uses the "git" command-line tool to read the notes from the given ref for a given revision. -//func (repo *GitRepo) GetNotes(notesRef, revision string) []Note { -// var notes []Note -// rawNotes, err := repo.runGitCommand("notes", "--ref", notesRef, "show", revision) -// if err != nil { -// // We just assume that this means there are no notes -// return nil -// } -// for _, line := range strings.Split(rawNotes, "\n") { -// notes = append(notes, Note([]byte(line))) -// } -// return notes -//} -// -//func stringsReader(s []*string) io.Reader { -// var subReaders []io.Reader -// for _, strPtr := range s { -// subReader := strings.NewReader(*strPtr) -// subReaders = append(subReaders, subReader, strings.NewReader("\n")) -// } -// return io.MultiReader(subReaders...) -//} -// -//// splitBatchCheckOutput parses the output of a 'git cat-file --batch-check=...' command. -//// -//// The output is expected to be formatted as a series of entries, with each -//// entry consisting of: -//// 1. The SHA1 hash of the git object being output, followed by a space. -//// 2. The git "type" of the object (commit, blob, tree, missing, etc), followed by a newline. -//// -//// To generate this format, make sure that the 'git cat-file' command includes -//// the argument '--batch-check=%(objectname) %(objecttype)'. -//// -//// The return value is a map from object hash to a boolean indicating if that object is a commit. -//func splitBatchCheckOutput(out *bytes.Buffer) (map[string]bool, error) { -// isCommit := make(map[string]bool) -// reader := bufio.NewReader(out) -// for { -// nameLine, err := reader.ReadString(byte(' ')) -// if err == io.EOF { -// return isCommit, nil -// } -// if err != nil { -// return nil, fmt.Errorf("Failure while reading the next object name: %v", err) -// } -// nameLine = strings.TrimSuffix(nameLine, " ") -// typeLine, err := reader.ReadString(byte('\n')) -// if err != nil && err != io.EOF { -// return nil, fmt.Errorf("Failure while reading the next object type: %q - %v", nameLine, err) -// } -// typeLine = strings.TrimSuffix(typeLine, "\n") -// if typeLine == "commit" { -// isCommit[nameLine] = true -// } -// } -//} -// -//// splitBatchCatFileOutput parses the output of a 'git cat-file --batch=...' command. -//// -//// The output is expected to be formatted as a series of entries, with each -//// entry consisting of: -//// 1. The SHA1 hash of the git object being output, followed by a newline. -//// 2. The size of the object's contents in bytes, followed by a newline. -//// 3. The objects contents. -//// -//// To generate this format, make sure that the 'git cat-file' command includes -//// the argument '--batch=%(objectname)\n%(objectsize)'. -//func splitBatchCatFileOutput(out *bytes.Buffer) (map[string][]byte, error) { -// contentsMap := make(map[string][]byte) -// reader := bufio.NewReader(out) -// for { -// nameLine, err := reader.ReadString(byte('\n')) -// if strings.HasSuffix(nameLine, "\n") { -// nameLine = strings.TrimSuffix(nameLine, "\n") -// } -// if err == io.EOF { -// return contentsMap, nil -// } -// if err != nil { -// return nil, fmt.Errorf("Failure while reading the next object name: %v", err) -// } -// sizeLine, err := reader.ReadString(byte('\n')) -// if strings.HasSuffix(sizeLine, "\n") { -// sizeLine = strings.TrimSuffix(sizeLine, "\n") -// } -// if err != nil { -// return nil, fmt.Errorf("Failure while reading the next object size: %q - %v", nameLine, err) -// } -// size, err := strconv.Atoi(sizeLine) -// if err != nil { -// return nil, fmt.Errorf("Failure while parsing the next object size: %q - %v", nameLine, err) -// } -// contentBytes := make([]byte, size, size) -// readDest := contentBytes -// len := 0 -// err = nil -// for err == nil && len < size { -// nextLen := 0 -// nextLen, err = reader.Read(readDest) -// len += nextLen -// readDest = contentBytes[len:] -// } -// contentsMap[nameLine] = contentBytes -// if err == io.EOF { -// return contentsMap, nil -// } -// if err != nil { -// return nil, err -// } -// for bs, err := reader.Peek(1); err == nil && bs[0] == byte('\n'); bs, err = reader.Peek(1) { -// reader.ReadByte() -// } -// } -//} -// -//// notesMapping represents the association between a git object and the notes for that object. -//type notesMapping struct { -// ObjectHash *string -// NotesHash *string -//} -// -//// notesOverview represents a high-level overview of all the notes under a single notes ref. -//type notesOverview struct { -// NotesMappings []*notesMapping -// ObjectHashesReader io.Reader -// NotesHashesReader io.Reader -//} -// -//// notesOverview returns an overview of the git notes stored under the given ref. -//func (repo *GitRepo) notesOverview(notesRef string) (*notesOverview, error) { -// var stdout bytes.Buffer -// var stderr bytes.Buffer -// if err := repo.runGitCommandWithIO(nil, &stdout, &stderr, "notes", "--ref", notesRef, "list"); err != nil { -// return nil, err -// } -// -// var notesMappings []*notesMapping -// var objHashes []*string -// var notesHashes []*string -// outScanner := bufio.NewScanner(&stdout) -// for outScanner.Scan() { -// line := outScanner.Text() -// lineParts := strings.Split(line, " ") -// if len(lineParts) != 2 { -// return nil, fmt.Errorf("Malformed output line from 'git-notes list': %q", line) -// } -// objHash := &lineParts[1] -// notesHash := &lineParts[0] -// notesMappings = append(notesMappings, ¬esMapping{ -// ObjectHash: objHash, -// NotesHash: notesHash, -// }) -// objHashes = append(objHashes, objHash) -// notesHashes = append(notesHashes, notesHash) -// } -// err := outScanner.Err() -// if err != nil && err != io.EOF { -// return nil, fmt.Errorf("Failure parsing the output of 'git-notes list': %v", err) -// } -// return ¬esOverview{ -// NotesMappings: notesMappings, -// ObjectHashesReader: stringsReader(objHashes), -// NotesHashesReader: stringsReader(notesHashes), -// }, nil -//} -// -//// getIsCommitMap returns a mapping of all the annotated objects that are commits. -//func (overview *notesOverview) getIsCommitMap(repo *GitRepo) (map[string]bool, error) { -// var stdout bytes.Buffer -// var stderr bytes.Buffer -// if err := repo.runGitCommandWithIO(overview.ObjectHashesReader, &stdout, &stderr, "cat-file", "--batch-check=%(objectname) %(objecttype)"); err != nil { -// return nil, fmt.Errorf("Failure performing a batch file check: %v", err) -// } -// isCommit, err := splitBatchCheckOutput(&stdout) -// if err != nil { -// return nil, fmt.Errorf("Failure parsing the output of a batch file check: %v", err) -// } -// return isCommit, nil -//} -// -//// getNoteContentsMap returns a mapping from all the notes hashes to their contents. -//func (overview *notesOverview) getNoteContentsMap(repo *GitRepo) (map[string][]byte, error) { -// var stdout bytes.Buffer -// var stderr bytes.Buffer -// if err := repo.runGitCommandWithIO(overview.NotesHashesReader, &stdout, &stderr, "cat-file", "--batch=%(objectname)\n%(objectsize)"); err != nil { -// return nil, fmt.Errorf("Failure performing a batch file read: %v", err) -// } -// noteContentsMap, err := splitBatchCatFileOutput(&stdout) -// if err != nil { -// return nil, fmt.Errorf("Failure parsing the output of a batch file read: %v", err) -// } -// return noteContentsMap, nil -//} -// -//// GetAllNotes reads the contents of the notes under the given ref for every commit. -//// -//// The returned value is a mapping from commit hash to the list of notes for that commit. -//// -//// This is the batch version of the corresponding GetNotes(...) method. -//func (repo *GitRepo) GetAllNotes(notesRef string) (map[string][]Note, error) { -// // This code is unfortunately quite complicated, but it needs to be so. -// // -// // Conceptually, this is equivalent to: -// // result := make(map[string][]Note) -// // for _, commit := range repo.ListNotedRevisions(notesRef) { -// // result[commit] = repo.GetNotes(notesRef, commit) -// // } -// // return result, nil -// // -// // However, that logic would require separate executions of the 'git' -// // command for every annotated commit. For a repo with 10s of thousands -// // of reviews, that would mean calling Cmd.Run(...) 10s of thousands of -// // times. That, in turn, would take so long that the tool would be unusable. -// // -// // This method avoids that by taking advantage of the 'git cat-file --batch="..."' -// // command. That allows us to use a single invocation of Cmd.Run(...) to -// // inspect multiple git objects at once. -// // -// // As such, regardless of the number of reviews in a repo, we can get all -// // of the notes using a total of three invocations of Cmd.Run(...): -// // 1. One to list all the annotated objects (and their notes hash) -// // 2. A second one to filter out all of the annotated objects that are not commits. -// // 3. A final one to get the contents of all of the notes blobs. -// overview, err := repo.notesOverview(notesRef) -// if err != nil { -// return nil, err -// } -// isCommit, err := overview.getIsCommitMap(repo) -// if err != nil { -// return nil, fmt.Errorf("Failure building the set of commit objects: %v", err) -// } -// noteContentsMap, err := overview.getNoteContentsMap(repo) -// if err != nil { -// return nil, fmt.Errorf("Failure building the mapping from notes hash to contents: %v", err) -// } -// commitNotesMap := make(map[string][]Note) -// for _, notesMapping := range overview.NotesMappings { -// if !isCommit[*notesMapping.ObjectHash] { -// continue -// } -// noteBytes := noteContentsMap[*notesMapping.NotesHash] -// byteSlices := bytes.Split(noteBytes, []byte("\n")) -// var notes []Note -// for _, slice := range byteSlices { -// notes = append(notes, Note(slice)) -// } -// commitNotesMap[*notesMapping.ObjectHash] = notes -// } -// -// return commitNotesMap, nil -//} -// -//// AppendNote appends a note to a revision under the given ref. -//func (repo *GitRepo) AppendNote(notesRef, revision string, note Note) error { -// _, err := repo.runGitCommand("notes", "--ref", notesRef, "append", "-m", string(note), revision) -// return err -//} -// -//// ListNotedRevisions returns the collection of revisions that are annotated by notes in the given ref. -//func (repo *GitRepo) ListNotedRevisions(notesRef string) []string { -// var revisions []string -// notesListOut, err := repo.runGitCommand("notes", "--ref", notesRef, "list") -// if err != nil { -// return nil -// } -// notesList := strings.Split(notesListOut, "\n") -// for _, notePair := range notesList { -// noteParts := strings.SplitN(notePair, " ", 2) -// if len(noteParts) == 2 { -// objHash := noteParts[1] -// objType, err := repo.runGitCommand("cat-file", "-t", objHash) -// // If a note points to an object that we do not know about (yet), then err will not -// // be nil. We can safely just ignore those notes. -// if err == nil && objType == "commit" { -// revisions = append(revisions, objHash) -// } -// } -// } -// return revisions -//} -// -//// PushNotes pushes git notes to a remote repo. -//func (repo *GitRepo) PushNotes(remote, notesRefPattern string) error { -// refspec := fmt.Sprintf("%s:%s", notesRefPattern, notesRefPattern) -// -// // The push is liable to fail if the user forgot to do a pull first, so -// // we treat errors as user errors rather than fatal errors. -// err := repo.runGitCommandInline("push", remote, refspec) -// if err != nil { -// return fmt.Errorf("Failed to push to the remote '%s': %v", remote, err) -// } -// return nil -//} -// -//// PushNotesAndArchive pushes the given notes and archive refs to a remote repo. -//func (repo *GitRepo) PushNotesAndArchive(remote, notesRefPattern, archiveRefPattern string) error { -// notesRefspec := fmt.Sprintf("%s:%s", notesRefPattern, notesRefPattern) -// archiveRefspec := fmt.Sprintf("%s:%s", archiveRefPattern, archiveRefPattern) -// err := repo.runGitCommandInline("push", remote, notesRefspec, archiveRefspec) -// if err != nil { -// return fmt.Errorf("Failed to push the local archive to the remote '%s': %v", remote, err) -// } -// return nil -//} -// -//func getRemoteNotesRef(remote, localNotesRef string) string { -// relativeNotesRef := strings.TrimPrefix(localNotesRef, "refs/notes/") -// return "refs/notes/" + remote + "/" + relativeNotesRef -//} -// -//func (repo *GitRepo) mergeRemoteNotes(remote, notesRefPattern string) error { -// remoteRefs, err := repo.runGitCommand("ls-remote", remote, notesRefPattern) -// if err != nil { -// return err -// } -// for _, line := range strings.Split(remoteRefs, "\n") { -// lineParts := strings.Split(line, "\t") -// if len(lineParts) == 2 { -// ref := lineParts[1] -// remoteRef := getRemoteNotesRef(remote, ref) -// _, err := repo.runGitCommand("notes", "--ref", ref, "merge", remoteRef, "-s", "cat_sort_uniq") -// if err != nil { -// return err -// } -// } -// } -// return nil -//} -// -//// PullNotes fetches the contents of the given notes ref from a remote repo, -//// and then merges them with the corresponding local notes using the -//// "cat_sort_uniq" strategy. -//func (repo *GitRepo) PullNotes(remote, notesRefPattern string) error { -// remoteNotesRefPattern := getRemoteNotesRef(remote, notesRefPattern) -// fetchRefSpec := fmt.Sprintf("+%s:%s", notesRefPattern, remoteNotesRefPattern) -// err := repo.runGitCommandInline("fetch", remote, fetchRefSpec) -// if err != nil { -// return err -// } -// -// return repo.mergeRemoteNotes(remote, notesRefPattern) -//} -// -//func getRemoteArchiveRef(remote, archiveRefPattern string) string { -// relativeArchiveRef := strings.TrimPrefix(archiveRefPattern, "refs/devtools/archives/") -// return "refs/devtools/remoteArchives/" + remote + "/" + relativeArchiveRef -//} -// -//func (repo *GitRepo) mergeRemoteArchives(remote, archiveRefPattern string) error { -// remoteRefs, err := repo.runGitCommand("ls-remote", remote, archiveRefPattern) -// if err != nil { -// return err -// } -// for _, line := range strings.Split(remoteRefs, "\n") { -// lineParts := strings.Split(line, "\t") -// if len(lineParts) == 2 { -// ref := lineParts[1] -// remoteRef := getRemoteArchiveRef(remote, ref) -// if err := repo.mergeArchives(ref, remoteRef); err != nil { -// return err -// } -// } -// } -// return nil -//} -// -//// PullNotesAndArchive fetches the contents of the notes and archives refs from -//// a remote repo, and merges them with the corresponding local refs. -//// -//// For notes refs, we assume that every note can be automatically merged using -//// the 'cat_sort_uniq' strategy (the git-appraise schemas fit that requirement), -//// so we automatically merge the remote notes into the local notes. -//// -//// For "archive" refs, they are expected to be used solely for maintaining -//// reachability of commits that are part of the history of any reviews, -//// so we do not maintain any consistency with their tree objects. Instead, -//// we merely ensure that their history graph includes every commit that we -//// intend to keep. -//func (repo *GitRepo) PullNotesAndArchive(remote, notesRefPattern, archiveRefPattern string) error { -// remoteArchiveRef := getRemoteArchiveRef(remote, archiveRefPattern) -// archiveFetchRefSpec := fmt.Sprintf("+%s:%s", archiveRefPattern, remoteArchiveRef) -// -// remoteNotesRefPattern := getRemoteNotesRef(remote, notesRefPattern) -// notesFetchRefSpec := fmt.Sprintf("+%s:%s", notesRefPattern, remoteNotesRefPattern) -// -// err := repo.runGitCommandInline("fetch", remote, notesFetchRefSpec, archiveFetchRefSpec) -// if err != nil { -// return err -// } -// -// if err := repo.mergeRemoteNotes(remote, notesRefPattern); err != nil { -// return err -// } -// if err := repo.mergeRemoteArchives(remote, archiveRefPattern); err != nil { -// return err -// } -// return nil -//} -*/ +// StoreCommit will store a Git commit with the given Git tree +func (repo *GitRepo) StoreCommit(treeHash util.Hash) (util.Hash, error) { + stdout, err := repo.runGitCommand("commit-tree", string(treeHash)) + + if err != nil { + return "", err + } + + return util.Hash(stdout), nil +} + +// StoreCommitWithParent will store a Git commit with the given Git tree +func (repo *GitRepo) StoreCommitWithParent(treeHash util.Hash, parent util.Hash) (util.Hash, error) { + stdout, err := repo.runGitCommand("commit-tree", string(treeHash), + "-p", string(parent)) + + if err != nil { + return "", err + } + + return util.Hash(stdout), nil +} + +// UpdateRef will create or update a Git reference +func (repo *GitRepo) UpdateRef(ref string, hash util.Hash) error { + _, err := repo.runGitCommand("update-ref", ref, string(hash)) + + return err +} diff --git a/repository/mock_repo.go b/repository/mock_repo.go index f5d27d12..e747eb24 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -1,33 +1,60 @@ package repository import ( - "crypto/sha1" - "encoding/json" - "fmt" + "github.com/MichaelMure/git-bug/util" ) // mockRepoForTest defines an instance of Repo that can be used for testing. type mockRepoForTest struct{} +func NewMockRepoForTest() Repo { + return &mockRepoForTest{} +} + // GetPath returns the path to the repo. -func (r *mockRepoForTest) GetPath() string { return "~/mockRepo/" } +func (r *mockRepoForTest) GetPath() string { + return "~/mockRepo/" +} -// GetRepoStateHash returns a hash which embodies the entire current state of a repository. -func (r *mockRepoForTest) GetRepoStateHash() (string, error) { - repoJSON, err := json.Marshal(r) - if err != nil { - return "", err - } - return fmt.Sprintf("%x", sha1.Sum([]byte(repoJSON))), nil +func (r *mockRepoForTest) GetUserName() (string, error) { + return "René Descartes", nil } // GetUserEmail returns the email address that the user has used to configure git. -func (r *mockRepoForTest) GetUserEmail() (string, error) { return "user@example.com", nil } +func (r *mockRepoForTest) GetUserEmail() (string, error) { + return "user@example.com", nil +} // GetCoreEditor returns the name of the editor that the user has used to configure git. -func (r *mockRepoForTest) GetCoreEditor() (string, error) { return "vi", nil } +func (r *mockRepoForTest) GetCoreEditor() (string, error) { + return "vi", nil +} // PushRefs push git refs to a remote func (r *mockRepoForTest) PushRefs(remote string, refPattern string) error { return nil } + +func (r *mockRepoForTest) PullRefs(remote string, refPattern string) error { + return nil +} + +func (r *mockRepoForTest) StoreData([]byte) (util.Hash, error) { + return "", nil +} + +func (r *mockRepoForTest) StoreTree(mapping map[string]util.Hash) (util.Hash, error) { + return "", nil +} + +func (r *mockRepoForTest) StoreCommit(treeHash util.Hash) (util.Hash, error) { + return "", nil +} + +func (r *mockRepoForTest) StoreCommitWithParent(treeHash util.Hash, parent util.Hash) (util.Hash, error) { + return "", nil +} + +func (r *mockRepoForTest) UpdateRef(ref string, hash util.Hash) error { + return nil +} diff --git a/repository/repo.go b/repository/repo.go index 2611324f..3a30128f 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -24,5 +24,17 @@ type Repo interface { PushRefs(remote string, refPattern string) error // StoreData will store arbitrary data and return the corresponding hash - StoreData([]byte) (util.Hash, error) + StoreData(data []byte) (util.Hash, error) + + // StoreTree will store a mapping key-->Hash as a Git tree + StoreTree(mapping map[string]util.Hash) (util.Hash, error) + + // StoreCommit will store a Git commit with the given Git tree + StoreCommit(treeHash util.Hash) (util.Hash, error) + + // StoreCommit will store a Git commit with the given Git tree + StoreCommitWithParent(treeHash util.Hash, parent util.Hash) (util.Hash, error) + + // UpdateRef will create or update a Git reference + UpdateRef(ref string, hash util.Hash) error } diff --git a/tests/bug_test.go b/tests/bug_test.go index dfb3ac09..ab7803f9 100644 --- a/tests/bug_test.go +++ b/tests/bug_test.go @@ -38,7 +38,7 @@ func TestBugValidity(t *testing.T) { t.Fatal("Bug with multiple CREATE should be invalid") } - bug1.Commit() + bug1.Commit(mockRepo) if bug1.IsValid() { t.Fatal("Bug with multiple CREATE should be invalid") diff --git a/tests/operation_iterator_test.go b/tests/operation_iterator_test.go index e41fff99..b2f01513 100644 --- a/tests/operation_iterator_test.go +++ b/tests/operation_iterator_test.go @@ -3,6 +3,7 @@ package tests import ( "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/bug/operations" + "github.com/MichaelMure/git-bug/repository" "testing" ) @@ -14,6 +15,7 @@ var ( createOp = operations.NewCreateOp(rene, "title", "message") setTitleOp = operations.NewSetTitleOp("title2") + mockRepo = repository.NewMockRepoForTest() ) func TestOpIterator(t *testing.T) { @@ -26,12 +28,12 @@ func TestOpIterator(t *testing.T) { bug1.Append(createOp) bug1.Append(setTitleOp) - bug1.Commit() + bug1.Commit(mockRepo) bug1.Append(setTitleOp) bug1.Append(setTitleOp) bug1.Append(setTitleOp) - bug1.Commit() + bug1.Commit(mockRepo) bug1.Append(setTitleOp) bug1.Append(setTitleOp) diff --git a/tests/operation_pack_test.go b/tests/operation_pack_test.go new file mode 100644 index 00000000..8ca43c09 --- /dev/null +++ b/tests/operation_pack_test.go @@ -0,0 +1,37 @@ +package tests + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/MichaelMure/git-bug/bug" + "testing" +) + +func TestOperationPackSerialize(t *testing.T) { + opp := bug.OperationPack{} + + opp.Append(createOp) + opp.Append(setTitleOp) + + jsonBytes, err := opp.Serialize() + + if err != nil { + t.Fatal(err) + } + + if len(jsonBytes) == 0 { + t.Fatal("empty json") + } + + fmt.Println(prettyPrintJSON(jsonBytes)) +} + +func prettyPrintJSON(jsonBytes []byte) (string, error) { + var prettyBytes bytes.Buffer + err := json.Indent(&prettyBytes, jsonBytes, "", " ") + if err != nil { + return "", err + } + return prettyBytes.String(), nil +} |