more refactoring to have reusable bug action across different UI

Michael Muré created

Change summary

bug/bug.go                     |  2 
bug/bug_actions.go             | 40 ++++++++++++++++----
bug/operations/add_comment.go  |  5 ++
bug/operations/create.go       |  8 ++++
bug/operations/label_change.go | 64 ++++++++++++++++++++++++++++++++
bug/operations/set_status.go   | 10 +++++
bug/operations/set_title.go    |  5 ++
commands/close.go              |  8 +---
commands/comment.go            |  8 +---
commands/label.go              | 70 ++++++-----------------------------
commands/new.go                | 16 ++++---
commands/open.go               |  8 +---
commands/pull.go               | 22 +----------
repository/git.go              | 52 ++++++++++++++++++++++----
repository/mock_repo.go        | 17 ++++++++
repository/repo.go             |  4 ++
16 files changed, 220 insertions(+), 119 deletions(-)

Detailed changes

bug/bug.go 🔗

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

bug/remote_actions.go → bug/bug_actions.go 🔗

@@ -3,13 +3,14 @@ package bug
 import (
 	"fmt"
 	"github.com/MichaelMure/git-bug/repository"
+	"io"
 	"strings"
 )
 
-const MsgNew = "new"
-const MsgInvalid = "invalid data"
-const MsgUpdated = "updated"
-const MsgNothing = "nothing to do"
+const MsgMergeNew = "new"
+const MsgMergeInvalid = "invalid data"
+const MsgMergeUpdated = "updated"
+const MsgMergeNothing = "nothing to do"
 
 func Fetch(repo repository.Repo, remote string) error {
 	remoteRefSpec := fmt.Sprintf(bugsRemoteRefPattern, remote)
@@ -22,6 +23,27 @@ func Push(repo repository.Repo, remote string) error {
 	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 {
 	Err error
 
@@ -73,7 +95,7 @@ func MergeAll(repo repository.Repo, remote string) <-chan MergeResult {
 
 			// Check for error in remote data
 			if !remoteBug.IsValid() {
-				out <- newMergeStatus(id, MsgInvalid)
+				out <- newMergeStatus(id, MsgMergeInvalid)
 				continue
 			}
 
@@ -89,7 +111,7 @@ func MergeAll(repo repository.Repo, remote string) <-chan MergeResult {
 					return
 				}
 
-				out <- newMergeStatus(id, MsgNew)
+				out <- newMergeStatus(id, MsgMergeNew)
 				continue
 			}
 
@@ -108,12 +130,14 @@ func MergeAll(repo repository.Repo, remote string) <-chan MergeResult {
 			}
 
 			if updated {
-				out <- newMergeStatus(id, MsgUpdated)
+				out <- newMergeStatus(id, MsgMergeUpdated)
 			} else {
-				out <- newMergeStatus(id, MsgNothing)
+				out <- newMergeStatus(id, MsgMergeNothing)
 			}
 		}
 	}()
 
 	return out
 }
+
+

bug/operations/add_comment.go 🔗

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

bug/operations/create.go 🔗

@@ -33,3 +33,11 @@ func (op CreateOperation) Apply(snapshot bug.Snapshot) bug.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
+}

bug/operations/label_change.go 🔗

@@ -1,7 +1,9 @@
 package operations
 
 import (
+	"fmt"
 	"github.com/MichaelMure/git-bug/bug"
+	"io"
 	"sort"
 )
 
@@ -54,3 +56,65 @@ AddLoop:
 
 	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
+}

bug/operations/set_status.go 🔗

@@ -25,3 +25,13 @@ func (op SetStatusOperation) Apply(snapshot bug.Snapshot) bug.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)
+}

bug/operations/set_title.go 🔗

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

commands/close.go 🔗

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

commands/comment.go 🔗

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

commands/label.go 🔗

@@ -2,10 +2,10 @@ package commands
 
 import (
 	"errors"
-	"fmt"
 	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/bug/operations"
 	"github.com/spf13/cobra"
+	"os"
 )
 
 var labelRemove bool
@@ -21,6 +21,14 @@ func runLabel(cmd *cobra.Command, args []string) error {
 
 	prefix := args[0]
 
+	var add, remove []string
+
+	if labelRemove {
+		remove = args[1:]
+	} else {
+		add = args[1:]
+	}
+
 	b, err := bug.FindLocalBug(repo, prefix)
 	if err != nil {
 		return err
@@ -31,65 +39,13 @@ func runLabel(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	var added, removed []bug.Label
-
-	snap := b.Compile()
-
-	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 errors.New("no label added or removed")
-	}
-
-	labelOp := operations.NewLabelChangeOperation(author, added, removed)
-
-	b.Append(labelOp)
+	err = operations.ChangeLabels(os.Stdout, b, author, add, remove)
 
-	err = b.Commit(repo)
-
-	return err
-}
-
-func labelExist(labels []bug.Label, label bug.Label) bool {
-	for _, l := range labels {
-		if l == label {
-			return true
-		}
+	if err != nil {
+		return err
 	}
 
-	return false
+	return b.Commit(repo)
 }
 
 var labelCmd = &cobra.Command{

commands/new.go 🔗

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

commands/open.go 🔗

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

commands/pull.go 🔗

@@ -2,9 +2,9 @@ package commands
 
 import (
 	"errors"
-	"fmt"
 	"github.com/MichaelMure/git-bug/bug"
 	"github.com/spf13/cobra"
+	"os"
 )
 
 func runPull(cmd *cobra.Command, args []string) error {
@@ -17,25 +17,7 @@ func runPull(cmd *cobra.Command, args []string) error {
 		remote = args[0]
 	}
 
-	fmt.Printf("Fetching remote ...\n\n")
-
-	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
+	return bug.Pull(repo, os.Stdout, remote)
 }
 
 // showCmd defines the "push" subcommand.

repository/git.go 🔗

@@ -3,7 +3,6 @@ package repository
 
 import (
 	"bytes"
-	"crypto/sha1"
 	"fmt"
 	"github.com/MichaelMure/git-bug/util"
 	"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.
 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.Dir = repo.Path
@@ -74,17 +73,29 @@ func NewGitRepo(path string) (*GitRepo, error) {
 	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.
 func (repo *GitRepo) GetPath() string {
 	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
 func (repo *GitRepo) GetUserName() (string, error) {
 	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
 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)
 	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
 }
+
+// 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
+}

repository/mock_repo.go 🔗

@@ -3,6 +3,8 @@ package repository
 import (
 	"crypto/sha1"
 	"fmt"
+	"strings"
+
 	"github.com/MichaelMure/git-bug/util"
 )
 
@@ -134,6 +136,21 @@ func (r *mockRepoForTest) ListRefs(refspec string) ([]string, error) {
 	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) {
 	var hashes []util.Hash
 

repository/repo.go 🔗

@@ -48,6 +48,10 @@ type Repo interface {
 	// ListRefs will return a list of Git ref matching the given refspec
 	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(ref string) (bool, error)