aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bug/bug.go6
-rw-r--r--bug/comment.go6
-rw-r--r--bug/person.go31
-rw-r--r--commands/commands.go2
-rw-r--r--commands/input/input.go104
-rw-r--r--commands/new.go75
-rw-r--r--commands/pull.go12
-rw-r--r--commands/push.go12
-rw-r--r--repository/git.go30
-rw-r--r--repository/mock_repo.go4
-rw-r--r--repository/repo.go3
11 files changed, 254 insertions, 31 deletions
diff --git a/bug/bug.go b/bug/bug.go
new file mode 100644
index 00000000..08743e85
--- /dev/null
+++ b/bug/bug.go
@@ -0,0 +1,6 @@
+package bug
+
+type Bug struct {
+ Title string
+ Comments []Comment
+}
diff --git a/bug/comment.go b/bug/comment.go
new file mode 100644
index 00000000..f7727709
--- /dev/null
+++ b/bug/comment.go
@@ -0,0 +1,6 @@
+package bug
+
+type Comment struct {
+ Author Person
+ Message string
+}
diff --git a/bug/person.go b/bug/person.go
new file mode 100644
index 00000000..41f37ef4
--- /dev/null
+++ b/bug/person.go
@@ -0,0 +1,31 @@
+package bug
+
+import (
+ "github.com/MichaelMure/git-bug/repository"
+ "github.com/pkg/errors"
+)
+
+type Person struct {
+ Name string
+ Email string
+}
+
+func GetUser(repo repository.Repo) (Person, error) {
+ name, err := repo.GetUserName()
+ if err != nil {
+ return Person{}, err
+ }
+ if name == "" {
+ return Person{}, errors.New("User name is not configured in git yet. Please use `git config --global user.name \"John Doe\"`")
+ }
+
+ email, err := repo.GetUserEmail()
+ if err != nil {
+ return Person{}, err
+ }
+ if email == "" {
+ return Person{}, errors.New("User name is not configured in git yet. Please use `git config --global user.email johndoe@example.com`")
+ }
+
+ return Person{Name: name, Email: email}, nil
+}
diff --git a/commands/commands.go b/commands/commands.go
index 4f892fdd..64b2cbc1 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -6,6 +6,7 @@ import (
)
const bugsRefPattern = "refs/bugs/*"
+const messageFilename = "BUG_MESSAGE_EDITMSG"
// Command represents the definition of a single command.
type Command struct {
@@ -23,6 +24,7 @@ func (cmd *Command) Run(repo repository.Repo, args []string) error {
// CommandMap defines all of the available (sub)commands.
var CommandMap = map[string]*Command{
+ "new": newCmd,
"pull": pullCmd,
"push": pushCmd,
diff --git a/commands/input/input.go b/commands/input/input.go
new file mode 100644
index 00000000..531a4386
--- /dev/null
+++ b/commands/input/input.go
@@ -0,0 +1,104 @@
+// Taken from the git-appraise project
+
+package input
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "github.com/MichaelMure/git-bug/repository"
+ "io/ioutil"
+ "os"
+ "os/exec"
+)
+
+// LaunchEditor launches the default editor configured for the given repo. This
+// method blocks until the editor command has returned.
+//
+// The specified filename should be a temporary file and provided as a relative path
+// from the repo (e.g. "FILENAME" will be converted to ".git/FILENAME"). This file
+// will be deleted after the editor is closed and its contents have been read.
+//
+// This method returns the text that was read from the temporary file, or
+// an error if any step in the process failed.
+func LaunchEditor(repo repository.Repo, fileName string) (string, error) {
+ editor, err := repo.GetCoreEditor()
+ if err != nil {
+ return "", fmt.Errorf("Unable to detect default git editor: %v\n", err)
+ }
+
+ path := fmt.Sprintf("%s/.git/%s", repo.GetPath(), fileName)
+
+ cmd, err := startInlineCommand(editor, path)
+ if err != nil {
+ // Running the editor directly did not work. This might mean that
+ // the editor string is not a path to an executable, but rather
+ // a shell command (e.g. "emacsclient --tty"). As such, we'll try
+ // to run the command through bash, and if that fails, try with sh
+ args := []string{"-c", fmt.Sprintf("%s %q", editor, path)}
+ cmd, err = startInlineCommand("bash", args...)
+ if err != nil {
+ cmd, err = startInlineCommand("sh", args...)
+ }
+ }
+ if err != nil {
+ return "", fmt.Errorf("Unable to start editor: %v\n", err)
+ }
+
+ if err := cmd.Wait(); err != nil {
+ return "", fmt.Errorf("Editing finished with error: %v\n", err)
+ }
+
+ output, err := ioutil.ReadFile(path)
+ if err != nil {
+ os.Remove(path)
+ return "", fmt.Errorf("Error reading edited file: %v\n", err)
+ }
+ os.Remove(path)
+ return string(output), err
+}
+
+// FromFile loads and returns the contents of a given file. If - is passed
+// through, much like git, it will read from stdin. This can be piped data,
+// unless there is a tty in which case the user will be prompted to enter a
+// message.
+func FromFile(fileName string) (string, error) {
+ if fileName == "-" {
+ stat, err := os.Stdin.Stat()
+ if err != nil {
+ return "", fmt.Errorf("Error reading from stdin: %v\n", err)
+ }
+ if (stat.Mode() & os.ModeCharDevice) == 0 {
+ // There is no tty. This will allow us to read piped data instead.
+ output, err := ioutil.ReadAll(os.Stdin)
+ if err != nil {
+ return "", fmt.Errorf("Error reading from stdin: %v\n", err)
+ }
+ return string(output), err
+ }
+
+ fmt.Printf("(reading comment from standard input)\n")
+ var output bytes.Buffer
+ s := bufio.NewScanner(os.Stdin)
+ for s.Scan() {
+ output.Write(s.Bytes())
+ output.WriteRune('\n')
+ }
+ return output.String(), nil
+ }
+
+ output, err := ioutil.ReadFile(fileName)
+ if err != nil {
+ return "", fmt.Errorf("Error reading file: %v\n", err)
+ }
+ return string(output), err
+}
+
+func startInlineCommand(command string, args ...string) (*exec.Cmd, error) {
+ cmd := exec.Command(command, args...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err := cmd.Start()
+ return cmd, err
+}
diff --git a/commands/new.go b/commands/new.go
new file mode 100644
index 00000000..0156477e
--- /dev/null
+++ b/commands/new.go
@@ -0,0 +1,75 @@
+package commands
+
+import (
+ "flag"
+ "fmt"
+ "github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/commands/input"
+ "github.com/MichaelMure/git-bug/repository"
+ "github.com/pkg/errors"
+)
+
+var newFlagSet = flag.NewFlagSet("new", flag.ExitOnError)
+
+var (
+ newMessageFile = newFlagSet.String("F", "", "Take the message from the given file. Use - to read the message from the standard input")
+ newMessage = newFlagSet.String("m", "", "Provide a message to describe the issue")
+)
+
+func newBug(repo repository.Repo, args []string) error {
+ newFlagSet.Parse(args)
+ args = newFlagSet.Args()
+
+ var err error
+
+ if len(args) == 0 {
+ return errors.New("No title provided")
+ }
+ if len(args) > 1 {
+ return errors.New("Only accepting one title is supported")
+ }
+
+ title := args[0]
+
+ if *newMessageFile != "" && *newMessage == "" {
+ *newMessage, err = input.FromFile(*newMessageFile)
+ if err != nil {
+ return err
+ }
+ }
+ if *newMessageFile == "" && *newMessage == "" {
+ *newMessage, err = input.LaunchEditor(repo, messageFilename)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Note: this is very primitive for now
+ author, err := bug.GetUser(repo)
+ if err != nil {
+ return err
+ }
+
+ comment := bug.Comment{
+ Author: author,
+ Message: *newMessage,
+ }
+
+ bug := bug.Bug{
+ Title: title,
+ Comments: []bug.Comment{comment},
+ }
+
+ fmt.Println(bug)
+
+ return nil
+
+}
+
+var newCmd = &Command{
+ Usage: func(arg0 string) {
+ fmt.Printf("Usage: %s new <title> [<option>...]\n\nOptions:\n", arg0)
+ newFlagSet.PrintDefaults()
+ },
+ RunMethod: newBug,
+}
diff --git a/commands/pull.go b/commands/pull.go
index 0f6080d4..20009a00 100644
--- a/commands/pull.go
+++ b/commands/pull.go
@@ -1,14 +1,14 @@
-package commands
+package commands
import (
+ "errors"
"fmt"
"github.com/MichaelMure/git-bug/repository"
- "errors"
)
func pull(repo repository.Repo, args []string) error {
if len(args) > 1 {
- return errors.New("only pulling from one remote at a time is supported")
+ return errors.New("Only pulling from one remote at a time is supported")
}
remote := "origin"
@@ -27,7 +27,5 @@ var pullCmd = &Command{
Usage: func(arg0 string) {
fmt.Printf("Usage: %s pull [<remote>]\n", arg0)
},
- RunMethod: func(repo repository.Repo, args []string) error {
- return pull(repo, args)
- },
-} \ No newline at end of file
+ RunMethod: pull,
+}
diff --git a/commands/push.go b/commands/push.go
index 4465f561..9b808511 100644
--- a/commands/push.go
+++ b/commands/push.go
@@ -1,14 +1,14 @@
-package commands
+package commands
import (
+ "errors"
"fmt"
"github.com/MichaelMure/git-bug/repository"
- "errors"
)
func push(repo repository.Repo, args []string) error {
if len(args) > 1 {
- return errors.New("only pushing to one remote at a time is supported")
+ return errors.New("Only pushing to one remote at a time is supported")
}
remote := "origin"
@@ -27,7 +27,5 @@ var pushCmd = &Command{
Usage: func(arg0 string) {
fmt.Printf("Usage: %s push [<remote>]\n", arg0)
},
- RunMethod: func(repo repository.Repo, args []string) error {
- return push(repo, args)
- },
-} \ No newline at end of file
+ RunMethod: push,
+}
diff --git a/repository/git.go b/repository/git.go
index 8e957523..309641f6 100644
--- a/repository/git.go
+++ b/repository/git.go
@@ -78,6 +78,11 @@ func (repo *GitRepo) GetRepoStateHash() (string, error) {
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")
+}
+
// GetUserEmail returns the email address that the user has used to configure git.
func (repo *GitRepo) GetUserEmail() (string, error) {
return repo.runGitCommand("config", "user.email")
@@ -88,30 +93,25 @@ func (repo *GitRepo) GetCoreEditor() (string, error) {
return repo.runGitCommand("var", "GIT_EDITOR")
}
-
// PullRefs pull git refs from a remote
func (repo *GitRepo) PullRefs(remote string, refPattern string) error {
- refspec := fmt.Sprintf("%s:%s", refPattern, refPattern)
+ fetchRefSpec := fmt.Sprintf("+%s:%s", refPattern, refPattern)
+ err := repo.runGitCommandInline("fetch", remote, fetchRefSpec)
- // 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
+ // TODO: merge new data
+
+ return err
}
// PushRefs push git refs to a remote
func (repo *GitRepo) PushRefs(remote string, refPattern string) error {
- remoteNotesRefPattern := getRemoteNotesRef(remote, refPattern)
- fetchRefSpec := fmt.Sprintf("+%s:%s", refPattern, remoteNotesRefPattern)
- err := repo.runGitCommandInline("fetch", remote, fetchRefSpec)
+ // 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)
if err != nil {
- return err
+ return fmt.Errorf("failed to push to the remote '%s': %v", remote, err)
}
-
- return repo.mergeRemoteNotes(remote, notesRefPattern)
+ return nil
}
/*
diff --git a/repository/mock_repo.go b/repository/mock_repo.go
index 8587d0da..f5d27d12 100644
--- a/repository/mock_repo.go
+++ b/repository/mock_repo.go
@@ -7,7 +7,7 @@ import (
)
// mockRepoForTest defines an instance of Repo that can be used for testing.
-type mockRepoForTest struct {}
+type mockRepoForTest struct{}
// GetPath returns the path to the repo.
func (r *mockRepoForTest) GetPath() string { return "~/mockRepo/" }
@@ -30,4 +30,4 @@ 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
-} \ No newline at end of file
+}
diff --git a/repository/repo.go b/repository/repo.go
index 7329f183..11bb132e 100644
--- a/repository/repo.go
+++ b/repository/repo.go
@@ -6,6 +6,9 @@ type Repo interface {
// GetPath returns the path to the repo.
GetPath() string
+ // GetUserName returns the name the the user has used to configure git
+ GetUserName() (string, error)
+
// GetUserEmail returns the email address that the user has used to configure git.
GetUserEmail() (string, error)