more refactoring to have reusable bug action across different UI

This commit is contained in:
Michael Muré 2018-07-25 18:01:32 +02:00
parent 49c90eab26
commit 6a12373965
No known key found for this signature in database
GPG Key ID: A4457C029293126F
16 changed files with 220 additions and 119 deletions

View File

@ -41,7 +41,7 @@ func NewBug() *Bug {
// Find an existing Bug matching a prefix // Find an existing Bug matching a prefix
func FindLocalBug(repo repository.Repo, prefix string) (*Bug, error) { func FindLocalBug(repo repository.Repo, prefix string) (*Bug, error) {
ids, err := repo.ListRefs(bugsRefPattern) ids, err := repo.ListIds(bugsRefPattern)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -3,13 +3,14 @@ package bug
import ( import (
"fmt" "fmt"
"github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/repository"
"io"
"strings" "strings"
) )
const MsgNew = "new" const MsgMergeNew = "new"
const MsgInvalid = "invalid data" const MsgMergeInvalid = "invalid data"
const MsgUpdated = "updated" const MsgMergeUpdated = "updated"
const MsgNothing = "nothing to do" const MsgMergeNothing = "nothing to do"
func Fetch(repo repository.Repo, remote string) error { func Fetch(repo repository.Repo, remote string) error {
remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote) remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote)
@ -22,6 +23,27 @@ func Push(repo repository.Repo, remote string) error {
return repo.PushRefs(remote, bugsRefPattern+"*") return repo.PushRefs(remote, bugsRefPattern+"*")
} }
func Pull(repo repository.Repo, out io.Writer, remote string) error {
fmt.Fprintf(out, "Fetching remote ...\n")
if err := Fetch(repo, remote); err != nil {
return err
}
fmt.Fprintf(out, "\nMerging data ...\n")
for merge := range MergeAll(repo, remote) {
if merge.Err != nil {
return merge.Err
}
if merge.Status != MsgMergeNothing {
fmt.Fprintf(out, "%s: %s\n", merge.HumanId, merge.Status)
}
}
return nil
}
type MergeResult struct { type MergeResult struct {
Err error Err error
@ -73,7 +95,7 @@ func MergeAll(repo repository.Repo, remote string) <-chan MergeResult {
// Check for error in remote data // Check for error in remote data
if !remoteBug.IsValid() { if !remoteBug.IsValid() {
out <- newMergeStatus(id, MsgInvalid) out <- newMergeStatus(id, MsgMergeInvalid)
continue continue
} }
@ -89,7 +111,7 @@ func MergeAll(repo repository.Repo, remote string) <-chan MergeResult {
return return
} }
out <- newMergeStatus(id, MsgNew) out <- newMergeStatus(id, MsgMergeNew)
continue continue
} }
@ -108,12 +130,14 @@ func MergeAll(repo repository.Repo, remote string) <-chan MergeResult {
} }
if updated { if updated {
out <- newMergeStatus(id, MsgUpdated) out <- newMergeStatus(id, MsgMergeUpdated)
} else { } else {
out <- newMergeStatus(id, MsgNothing) out <- newMergeStatus(id, MsgMergeNothing)
} }
} }
}() }()
return out return out
} }

View File

@ -31,3 +31,8 @@ func (op AddCommentOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
return snapshot return snapshot
} }
func Comment(b *bug.Bug, author bug.Person, message string) {
addCommentOp := NewAddCommentOp(author, message)
b.Append(addCommentOp)
}

View File

@ -33,3 +33,11 @@ func (op CreateOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
} }
return snapshot return snapshot
} }
func Create(author bug.Person, title, message string) (*bug.Bug, error) {
newBug := bug.NewBug()
createOp := NewCreateOp(author, title, message)
newBug.Append(createOp)
return newBug, nil
}

View File

@ -1,7 +1,9 @@
package operations package operations
import ( import (
"fmt"
"github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/bug"
"io"
"sort" "sort"
) )
@ -54,3 +56,65 @@ AddLoop:
return snapshot return snapshot
} }
func ChangeLabels(out io.Writer, b *bug.Bug, author bug.Person, add, remove []string) error {
var added, removed []bug.Label
snap := b.Compile()
for _, str := range add {
label := bug.Label(str)
// check for duplicate
if labelExist(added, label) {
fmt.Fprintf(out, "label \"%s\" is a duplicate\n", str)
continue
}
// check that the label doesn't already exist
if labelExist(snap.Labels, label) {
fmt.Fprintf(out, "label \"%s\" is already set on this bug\n", str)
continue
}
added = append(added, label)
}
for _, str := range remove {
label := bug.Label(str)
// check for duplicate
if labelExist(removed, label) {
fmt.Fprintf(out, "label \"%s\" is a duplicate\n", str)
continue
}
// check that the label actually exist
if !labelExist(snap.Labels, label) {
fmt.Fprintf(out, "label \"%s\" doesn't exist on this bug\n", str)
continue
}
removed = append(removed, label)
}
if len(added) == 0 && len(removed) == 0 {
return fmt.Errorf("no label added or removed")
}
labelOp := NewLabelChangeOperation(author, added, removed)
b.Append(labelOp)
return nil
}
func labelExist(labels []bug.Label, label bug.Label) bool {
for _, l := range labels {
if l == label {
return true
}
}
return false
}

View File

@ -25,3 +25,13 @@ func (op SetStatusOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
return snapshot return snapshot
} }
func Open(b *bug.Bug, author bug.Person) {
op := NewSetStatusOp(author, bug.OpenStatus)
b.Append(op)
}
func Close(b *bug.Bug, author bug.Person) {
op := NewSetStatusOp(author, bug.ClosedStatus)
b.Append(op)
}

View File

@ -25,3 +25,8 @@ func (op SetTitleOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
return snapshot return snapshot
} }
func SetTitle(b *bug.Bug, author bug.Person, title string) {
setTitleOp := NewSetTitleOp(author, title)
b.Append(setTitleOp)
}

View File

@ -28,13 +28,9 @@ func runCloseBug(cmd *cobra.Command, args []string) error {
return err return err
} }
op := operations.NewSetStatusOp(author, bug.ClosedStatus) operations.Close(b, author)
b.Append(op) return b.Commit(repo)
err = b.Commit(repo)
return err
} }
var closeCmd = &cobra.Command{ var closeCmd = &cobra.Command{

View File

@ -49,13 +49,9 @@ func runComment(cmd *cobra.Command, args []string) error {
return err return err
} }
addCommentOp := operations.NewAddCommentOp(author, commentMessage) operations.Comment(b, author, commentMessage)
b.Append(addCommentOp) return b.Commit(repo)
err = b.Commit(repo)
return err
} }
var commentCmd = &cobra.Command{ var commentCmd = &cobra.Command{

View File

@ -2,10 +2,10 @@ package commands
import ( import (
"errors" "errors"
"fmt"
"github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/bug/operations" "github.com/MichaelMure/git-bug/bug/operations"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os"
) )
var labelRemove bool var labelRemove bool
@ -21,6 +21,14 @@ func runLabel(cmd *cobra.Command, args []string) error {
prefix := args[0] prefix := args[0]
var add, remove []string
if labelRemove {
remove = args[1:]
} else {
add = args[1:]
}
b, err := bug.FindLocalBug(repo, prefix) b, err := bug.FindLocalBug(repo, prefix)
if err != nil { if err != nil {
return err return err
@ -31,65 +39,13 @@ func runLabel(cmd *cobra.Command, args []string) error {
return err return err
} }
var added, removed []bug.Label err = operations.ChangeLabels(os.Stdout, b, author, add, remove)
snap := b.Compile() if err != nil {
return err
for _, arg := range args[1:] {
label := bug.Label(arg)
if labelRemove {
// check for duplicate
if labelExist(removed, label) {
fmt.Printf("label \"%s\" is a duplicate\n", arg)
continue
}
// check that the label actually exist
if !labelExist(snap.Labels, label) {
fmt.Printf("label \"%s\" doesn't exist on this bug\n", arg)
continue
}
removed = append(removed, label)
} else {
// check for duplicate
if labelExist(added, label) {
fmt.Printf("label \"%s\" is a duplicate\n", arg)
continue
}
// check that the label doesn't already exist
if labelExist(snap.Labels, label) {
fmt.Printf("label \"%s\" is already set on this bug\n", arg)
continue
}
added = append(added, label)
}
} }
if len(added) == 0 && len(removed) == 0 { return b.Commit(repo)
return errors.New("no label added or removed")
}
labelOp := operations.NewLabelChangeOperation(author, added, removed)
b.Append(labelOp)
err = b.Commit(repo)
return err
}
func labelExist(labels []bug.Label, label bug.Label) bool {
for _, l := range labels {
if l == label {
return true
}
}
return false
} }
var labelCmd = &cobra.Command{ var labelCmd = &cobra.Command{

View File

@ -44,18 +44,20 @@ func runNewBug(cmd *cobra.Command, args []string) error {
return err return err
} }
newBug := bug.NewBug() newBug, err := operations.Create(author, title, newMessage)
if err != nil {
createOp := operations.NewCreateOp(author, title, newMessage) return err
}
newBug.Append(createOp)
err = newBug.Commit(repo) err = newBug.Commit(repo)
if err != nil {
return err
}
fmt.Printf("%s created\n", newBug.HumanId()) fmt.Printf("%s created\n", newBug.HumanId())
return err return nil
} }
var newCmd = &cobra.Command{ var newCmd = &cobra.Command{

View File

@ -28,13 +28,9 @@ func runOpenBug(cmd *cobra.Command, args []string) error {
return err return err
} }
op := operations.NewSetStatusOp(author, bug.OpenStatus) operations.Open(b, author)
b.Append(op) return b.Commit(repo)
err = b.Commit(repo)
return err
} }
var openCmd = &cobra.Command{ var openCmd = &cobra.Command{

View File

@ -2,9 +2,9 @@ package commands
import ( import (
"errors" "errors"
"fmt"
"github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/bug"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os"
) )
func runPull(cmd *cobra.Command, args []string) error { func runPull(cmd *cobra.Command, args []string) error {
@ -17,25 +17,7 @@ func runPull(cmd *cobra.Command, args []string) error {
remote = args[0] remote = args[0]
} }
fmt.Printf("Fetching remote ...\n\n") return bug.Pull(repo, os.Stdout, remote)
if err := bug.Fetch(repo, remote); err != nil {
return err
}
fmt.Printf("\nMerging data ...\n\n")
for merge := range bug.MergeAll(repo, remote) {
if merge.Err != nil {
return merge.Err
}
if merge.Status != bug.MsgNothing {
fmt.Printf("%s: %s\n", merge.HumanId, merge.Status)
}
}
return nil
} }
// showCmd defines the "push" subcommand. // showCmd defines the "push" subcommand.

View File

@ -3,7 +3,6 @@ package repository
import ( import (
"bytes" "bytes"
"crypto/sha1"
"fmt" "fmt"
"github.com/MichaelMure/git-bug/util" "github.com/MichaelMure/git-bug/util"
"io" "io"
@ -19,7 +18,7 @@ type GitRepo struct {
// Run the given git command with the given I/O reader/writers, returning an error if it fails. // Run the given git command with the given I/O reader/writers, returning an error if it fails.
func (repo *GitRepo) runGitCommandWithIO(stdin io.Reader, stdout, stderr io.Writer, args ...string) error { func (repo *GitRepo) runGitCommandWithIO(stdin io.Reader, stdout, stderr io.Writer, args ...string) error {
fmt.Println("Running git", strings.Join(args, " ")) //fmt.Println("Running git", strings.Join(args, " "))
cmd := exec.Command("git", args...) cmd := exec.Command("git", args...)
cmd.Dir = repo.Path cmd.Dir = repo.Path
@ -74,17 +73,29 @@ func NewGitRepo(path string) (*GitRepo, error) {
return nil, err return nil, err
} }
func InitGitRepo(path string) (*GitRepo, error) {
repo := &GitRepo{Path: path}
_, err := repo.runGitCommand("init", path)
if err != nil {
return nil, err
}
return repo, nil
}
func InitBareGitRepo(path string) (*GitRepo, error) {
repo := &GitRepo{Path: path}
_, err := repo.runGitCommand("init", "--bare", path)
if err != nil {
return nil, err
}
return repo, nil
}
// GetPath returns the path to the repo. // GetPath returns the path to the repo.
func (repo *GitRepo) GetPath() string { func (repo *GitRepo) GetPath() string {
return repo.Path return repo.Path
} }
// GetRepoStateHash returns a hash which embodies the entire current state of a repository.
func (repo *GitRepo) GetRepoStateHash() (string, error) {
stateSummary, err := repo.runGitCommand("show-ref")
return fmt.Sprintf("%x", sha1.Sum([]byte(stateSummary))), err
}
// GetUserName returns the name the the user has used to configure git // GetUserName returns the name the the user has used to configure git
func (repo *GitRepo) GetUserName() (string, error) { func (repo *GitRepo) GetUserName() (string, error) {
return repo.runGitCommand("config", "user.name") return repo.runGitCommand("config", "user.name")
@ -189,6 +200,24 @@ func (repo *GitRepo) UpdateRef(ref string, hash util.Hash) error {
// ListRefs will return a list of Git ref matching the given refspec // ListRefs will return a list of Git ref matching the given refspec
func (repo *GitRepo) ListRefs(refspec string) ([]string, error) { func (repo *GitRepo) ListRefs(refspec string) ([]string, error) {
stdout, err := repo.runGitCommand("for-each-ref", "--format=%(refname)", refspec)
if err != nil {
return nil, err
}
splitted := strings.Split(stdout, "\n")
if len(splitted) == 1 && splitted[0] == "" {
return []string{}, nil
}
return splitted, nil
}
// ListIds will return a list of Git ref matching the given refspec,
// stripped to only the last part of the ref
func (repo *GitRepo) ListIds(refspec string) ([]string, error) {
// the format option will strip the ref name to keep only the last part (ie, the bug id) // the format option will strip the ref name to keep only the last part (ie, the bug id)
stdout, err := repo.runGitCommand("for-each-ref", "--format=%(refname:lstrip=-1)", refspec) stdout, err := repo.runGitCommand("for-each-ref", "--format=%(refname:lstrip=-1)", refspec)
@ -274,3 +303,10 @@ func (repo *GitRepo) GetTreeHash(commit util.Hash) (util.Hash, error) {
return util.Hash(stdout), nil return util.Hash(stdout), nil
} }
// Add a new remote to the repository
func (repo *GitRepo) AddRemote(name string, url string) error {
_, err := repo.runGitCommand("remote", "add", name, url)
return err
}

View File

@ -3,6 +3,8 @@ package repository
import ( import (
"crypto/sha1" "crypto/sha1"
"fmt" "fmt"
"strings"
"github.com/MichaelMure/git-bug/util" "github.com/MichaelMure/git-bug/util"
) )
@ -134,6 +136,21 @@ func (r *mockRepoForTest) ListRefs(refspec string) ([]string, error) {
return keys, nil return keys, nil
} }
// ListIds will return a list of Git ref matching the given refspec,
// stripped to only the last part of the ref
func (r *mockRepoForTest) ListIds(refspec string) ([]string, error) {
keys := make([]string, len(r.refs))
i := 0
for k := range r.refs {
splitted := strings.Split(k, "/")
keys[i] = splitted[len(splitted)-1]
i++
}
return keys, nil
}
func (r *mockRepoForTest) ListCommits(ref string) ([]util.Hash, error) { func (r *mockRepoForTest) ListCommits(ref string) ([]util.Hash, error) {
var hashes []util.Hash var hashes []util.Hash

View File

@ -48,6 +48,10 @@ type Repo interface {
// ListRefs will return a list of Git ref matching the given refspec // ListRefs will return a list of Git ref matching the given refspec
ListRefs(refspec string) ([]string, error) ListRefs(refspec string) ([]string, error)
// ListIds will return a list of Git ref matching the given refspec,
// stripped to only the last part of the ref
ListIds(refspec string) ([]string, error)
// RefExist will check if a reference exist in Git // RefExist will check if a reference exist in Git
RefExist(ref string) (bool, error) RefExist(ref string) (bool, error)