Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
33dc540
♻️ Rename uncommittedChanges to UncommittedChanges, untrackedFiles to…
akm Dec 7, 2024
f9e6291
♻️ Move UncommittedChanges and UntrackedFiles to git package
akm Dec 7, 2024
d6cefd6
♻️ Rename add to Add
akm Dec 7, 2024
c02323e
♻️ Move Add to git package
akm Dec 7, 2024
5ce66f4
♻️ Rename guard to Guard
akm Dec 7, 2024
6b6ffda
♻️ Rename guardResult to GuardResult
akm Dec 7, 2024
ac30c49
♻️ Extract GuardOptions type
akm Dec 7, 2024
e623d5d
♻️ Capitalize GuardResult field names
akm Dec 7, 2024
77fb5fc
♻️ Move guard.go to git package
akm Dec 7, 2024
040f3c3
♻️ Rename commit to Commit
akm Dec 7, 2024
921f96e
♻️ Pass message string to Commit instead of *commitMessage
akm Dec 7, 2024
7f2703a
♻️ Move commit.go to git package
akm Dec 7, 2024
1a8ac70
♻️ Inline splitStringsInto2 function
akm Dec 7, 2024
1319bb4
♻️ Move command.go to command package
akm Dec 7, 2024
542fa27
♻️ Move *runner.go to command package
akm Dec 7, 2024
1e1005b
♻️ Extract newOpt function
akm Dec 7, 2024
2b3a156
♻️ Extract option_type.go
akm Dec 7, 2024
48ba0ae
♻️ Extract option_types.go
akm Dec 7, 2024
04e3976
♻️ Remove SetValue method
akm Dec 7, 2024
9b252ae
♻️ Use Generics for reference from OptionType to Options
akm Dec 7, 2024
692fe94
♻️ Extract opts package with OptionType and OptionTypes
akm Dec 7, 2024
ae41750
♻️ Rename OptionType to Definition, OptionTypes to Definitions
akm Dec 7, 2024
5443296
♻️ Rename option_type.go to definition.go and options_types.go to def…
akm Dec 7, 2024
2c078f5
♻️ Extract opts.BuildKeyMap function
akm Dec 7, 2024
47e274c
♻️ Extract opts.Parse function
akm Dec 7, 2024
f988092
♻️ Remove opts.Definitions type
akm Dec 7, 2024
286be7c
♻️ Rename definitions.go to parse.go
akm Dec 7, 2024
6c989eb
♻️ Extract opts.NewOptions function
akm Dec 7, 2024
7ffc964
♻️ Move newOpt definition
akm Dec 7, 2024
58ed4f1
♻️ Extract boolOpt and strOpt
akm Dec 7, 2024
d97a033
♻️ Use under score for unused type parameter
akm Dec 7, 2024
53d11ab
♻️ Remove variables for optionTypes
akm Dec 7, 2024
2d23a6d
♻️ move const and function into optionTypes function
akm Dec 7, 2024
7f13a6c
♻️ Rename SetFunc to Setter
akm Dec 7, 2024
0a25239
✨ Add getter and help to opts.Definition
akm Dec 7, 2024
0dec557
♻️ Rename setFunc to setter
akm Dec 7, 2024
aefc8be
♻️ Define getter and help message with optionTypes
akm Dec 7, 2024
01ed914
♻️ Extract opts.HelpItemsAndEnvVarMappings
akm Dec 7, 2024
1961c3c
♻️ Extract opts.Factory type
akm Dec 7, 2024
93c8a9d
♻️ Rename process function to Run
akm Dec 7, 2024
efc4b2a
♻️ Extract run.go
akm Dec 7, 2024
58402d1
♻️ Rename parseOptions function to ParseOptions
akm Dec 7, 2024
8724ed5
♻️ Rename help function to Help
akm Dec 7, 2024
186e2e3
♻️ Move business logic to core package
akm Dec 7, 2024
f1069c3
🎉 Bump up from 0.1.1 to 0.1.2
akm Dec 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions args.go

This file was deleted.

15 changes: 8 additions & 7 deletions command.go → command/command.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package command

import (
"strings"
Expand All @@ -10,7 +10,7 @@ type Command struct {
Output string
}

func newCommand(args []string) *Command {
func NewCommand(args []string) *Command {
envs, commandArgs := splitArgsToEnvsAndCommand(args)
return &Command{
Envs: envs,
Expand All @@ -20,12 +20,13 @@ func newCommand(args []string) *Command {

func splitArgsToEnvsAndCommand(args []string) ([]string, []string) {
equalNotFound := false
return splitStringsInto2(args, func(arg string) bool {
var a, b []string
for _, arg := range args {
if !equalNotFound && strings.Contains(arg, "=") {
return true
a = append(a, arg)
} else {
equalNotFound = true
return false
b = append(b, arg)
}
})
}
return a, b
}
2 changes: 1 addition & 1 deletion command_test.go → command/command_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package command

import (
"fmt"
Expand Down
2 changes: 1 addition & 1 deletion runner.go → command/runner.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package command

type Runner interface {
Run(c *Command) error
Expand Down
4 changes: 2 additions & 2 deletions standard_runner.go → command/standard_runner.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package command

import (
"bytes"
Expand All @@ -13,7 +13,7 @@ type StandardRunner struct {

var _ Runner = (*StandardRunner)(nil)

func newStandardRunner(debugLog bool) *StandardRunner {
func NewStandardRunner(debugLog bool) *StandardRunner {
return &StandardRunner{
debugLog: debugLog,
}
Expand Down
4 changes: 2 additions & 2 deletions tmux_runner.go → command/tmux_runner.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package command

import (
"bytes"
Expand All @@ -25,7 +25,7 @@ type TmuxRunner struct {

var _ Runner = (*TmuxRunner)(nil)

func newTmuxRunner(debugLog bool) *TmuxRunner {
func NewTmuxRunner(debugLog bool) *TmuxRunner {
doneStringPrefix := "git-exec-done"
return &TmuxRunner{
session: "git-exec-session",
Expand Down
6 changes: 4 additions & 2 deletions commit_message.go → core/commit_message.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package main
package core

import (
"bytes"
"fmt"
"strings"
"text/template"

"github.com/akm/git-exec/command"
)

type commitMessage struct {
Expand All @@ -17,7 +19,7 @@ type commitMessage struct {
Body string
}

func newCommitMessage(command *Command, options *Options) *commitMessage {
func newCommitMessage(command *command.Command, options *Options) *commitMessage {
argParts := make([]string, len(command.Args))
for i, arg := range command.Args {
if strings.Contains(arg, " ") && !(strings.HasPrefix(arg, "'") && strings.HasSuffix(arg, "'")) {
Expand Down
6 changes: 4 additions & 2 deletions commit_message_test.go → core/commit_message_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package main
package core

import (
"os"
"testing"

"github.com/akm/git-exec/command"

"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -87,7 +89,7 @@ func TestCommitMessage(t *testing.T) {
getLocation, bkGetLocation = func() (string, error) { return ptn.location, nil }, getLocation
defer func() { getLocation = bkGetLocation }()

command := &Command{Envs: ptn.envs, Args: ptn.args, Output: ptn.output}
command := &command.Command{Envs: ptn.envs, Args: ptn.args, Output: ptn.output}
commitMsg := newCommitMessage(command, newOptions())

actual, err := commitMsg.Build()
Expand Down
30 changes: 30 additions & 0 deletions core/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package core

import (
"fmt"
"strings"

"github.com/akm/git-exec/opts"
)

func Help() {
firstLine := `Usage: git-exec [options ...] [key=value ...] <command> [args ...]`
examples := `Examples:
* Specify environment variables.
git exec FOO=fooooo make args1 args2

* Use shell to work with redirect operator.
git exec /bin/bash -c 'echo "foo" >> README.md'

* Use interactive mode for command which requires input such as "npx sv create" for SvelteKit.
git exec -i npx sv create my-app
`
optionItems, envVarItems := opts.HelpItemsAndEnvVarMappings[Options](defaultOptions, optionTypes)

options := "Options:\n" + strings.Join(optionItems, "\n")
envVars := "Environment variable mapping:\n" + strings.Join(envVarItems, "\n")

// git-exec は <command>よりも前に 複数のキーと値の組み合わせを指定可能で、
// <command> 以後は 複数の引数を指定可能です。
fmt.Println(firstLine + "\n\n" + options + "\n\n" + envVars + "\n\n" + examples)
}
2 changes: 1 addition & 1 deletion location.go → core/location.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package core

import (
"os"
Expand Down
87 changes: 87 additions & 0 deletions core/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package core

import (
"github.com/akm/git-exec/git"
"github.com/akm/git-exec/opts"
)

type Options struct {
Help bool
Version bool
Directory string
Emoji string
Prompt string
Template string

git.GuardOptions

DebugLog bool
Interactive bool
}

var defaultOptions = &Options{
Help: false,
Version: false,
Directory: "",
Emoji: "🤖",
Prompt: "$",
Template: `{{.Emoji}} [{{.Location}}] {{.Prompt}} {{.Command}}`,
DebugLog: false,
Interactive: false,
}

var optionTypes = func() []*opts.Definition[Options] {
f := opts.NewFactory[Options]("GIT_EXEC_")

return []*opts.Definition[Options]{
f.String("-C", "--directory", "Specify the directory where the command is executed.",
func(o *Options) string { return o.Directory },
func(o *Options, v string) { o.Directory = v },
).WithoutEnv(),
f.String("-e", "--emoji", "Specify the emoji used in commit message.",
func(o *Options) string { return o.Emoji },
func(o *Options, v string) { o.Emoji = v },
),
f.String("-p", "--prompt", "Specify the prompt used in commit message.",
func(o *Options) string { return o.Prompt },
func(o *Options, v string) { o.Prompt = v },
),
f.String("-t", "--template", "Specify the template to build commit message.",
func(o *Options) string { return o.Template },
func(o *Options, v string) { o.Template = v },
),

f.Bool("", "--skip-guard", "Skip the guard check for uncommitted changes and untracked files before executing command.",
func(o *Options) bool { return o.SkipGuard },
func(o *Options) { o.SkipGuard = true },
),
f.Bool("", "--skip-guard-uncommitted-changes", "Skip the guard check for uncommitted changes before executing command.",
func(o *Options) bool { return o.SkipGuardUncommittedChanges },
func(o *Options) { o.SkipGuardUncommittedChanges = true },
),
f.Bool("", "--skip-guard-untracked-files", "Skip the guard check for untracked files before executing command.",
func(o *Options) bool { return o.SkipGuardUntrackedFiles },
func(o *Options) { o.SkipGuardUntrackedFiles = true },
),

f.Bool("-D", "--debug-log", "Output debug log.",
func(o *Options) bool { return o.DebugLog },
func(o *Options) { o.DebugLog = true },
),
f.Bool("-i", "--interactive", "Interactive mode for command which requires input. tmux is required to use.",
func(o *Options) bool { return o.Interactive },
func(o *Options) { o.Interactive = true },
),

f.Bool("-h", "--help", "Show this message.", nil, func(o *Options) { o.Help = true }).WithoutEnv(),
f.Bool("-v", "--version", "Show version.", nil, func(o *Options) { o.Version = true }).WithoutEnv(),
}
}()

func newOptions() *Options {
return opts.NewOptions(optionTypes, defaultOptions)
}

func ParseOptions(args []string) (*Options, []string, error) {
return opts.Parse(defaultOptions, optionTypes, args...)
}
4 changes: 2 additions & 2 deletions options_test.go → core/options_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package core

import (
"fmt"
Expand Down Expand Up @@ -189,7 +189,7 @@ func TestParseOptions(t *testing.T) {
defer func() { os.Setenv(key, envBackup) }()
}

options, commandArgs, err := parseOptions(ptn.args)
options, commandArgs, err := ParseOptions(ptn.args)
assert.Equal(t, ptn.options, options)
assert.Equal(t, ptn.commandArgs, commandArgs)
if ptn.error == "" {
Expand Down
91 changes: 91 additions & 0 deletions core/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package core

import (
"fmt"
"log/slog"
"os"

"github.com/akm/git-exec/command"
"github.com/akm/git-exec/git"
)

func Run(options *Options, commandArgs []string) error {
var guardMessage string
if guardResult, err := git.Guard(&options.GuardOptions); err != nil {
return err
} else if guardResult != nil {
if guardResult.Skipped {
guardMessage = guardResult.Format()
fmt.Fprintf(os.Stderr, "Guard skipped: %s\n", guardMessage)
} else {
return fmt.Errorf("Quit processing because %s", guardResult.Format())
}
}

cmd := command.NewCommand(commandArgs)
var runner command.Runner
if options.Interactive {
runner = command.NewTmuxRunner(options.DebugLog)
} else {
runner = command.NewStandardRunner(options.DebugLog)
}

var commitMessage *commitMessage
if err := changeDir((options.Directory), func() error {
if err := runner.Run(cmd); err != nil {
slog.Error("Command execution failed", "error", err)
return fmt.Errorf("Command execution failed: %+v\n%s", err, cmd.Output)
}
commitMessage = newCommitMessage(cmd, options)
return nil
}); err != nil {
return err
}

if err := git.Add(); err != nil {
return err
}

if guardMessage != "" {
commitMessage.Body = guardMessage + "\n\n" + commitMessage.Body
}

// 3. "git commit" を以下のオプションと標準力を指定して実行する。
msg, err := commitMessage.Build()
if err != nil {
return fmt.Errorf("Failed to build commit message: %+v", err)
}

if err := git.Commit(msg); err != nil {
return err
}

return nil
}

func changeDir(dir string, cb func() error) (rerr error) {
if dir == "" {
return cb()
}
var origDir string
if dir != "" {
{
var err error
origDir, err = os.Getwd()
if err != nil {
return fmt.Errorf("Failed to get current directory: %s", err.Error())
}
}
if err := os.Chdir(dir); err != nil {
return fmt.Errorf("Failed to change directory: %s", err.Error())
}
}
if origDir != "" {
defer func() {
if err := os.Chdir(origDir); err != nil {
rerr = fmt.Errorf("Failed to change directory: %s", err.Error())
}
}()
}
return cb()
}
8 changes: 4 additions & 4 deletions add.go → git/add.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package main
package git

import (
"fmt"
"os/exec"
)

func add() error {
uncommittedChanges, err := uncommittedChanges()
func Add() error {
uncommittedChanges, err := UncommittedChanges()
if err != nil {
return fmt.Errorf("git diff failed: %+v", err)
}
untrackedFiles, err := untrackedFiles()
untrackedFiles, err := UntrackedFiles()
if err != nil {
return fmt.Errorf("git ls-files failed: %+v", err)
}
Expand Down
Loading