Skip to content

Commit

Permalink
Merge pull request #22 from akm/features/add_skip_guard_option
Browse files Browse the repository at this point in the history
Add --skip-guard option
  • Loading branch information
akm authored Nov 3, 2024
2 parents bd9e0c4 + 508e5b0 commit c060e9e
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 116 deletions.
27 changes: 27 additions & 0 deletions add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import (
"fmt"
"os/exec"
)

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

if len(uncommittedChanges) == 0 && len(untrackedFiles) == 0 {
return fmt.Errorf("No changes to commit and No untracked files")
}

if err := exec.Command("git", "add", ".").Run(); err != nil {
return fmt.Errorf("git add failed: %+v", err)
}

return nil
}
25 changes: 25 additions & 0 deletions commit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"bytes"
"fmt"
"os/exec"
)

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

// See https://tracpath.com/docs/git-commit/
commitCmd := exec.Command("git", "commit", "--file", "-")
commitCmd.Stdin = bytes.NewBufferString(msg)

if err := commitCmd.Run(); err != nil {
return fmt.Errorf("git commit failed: %+v", err)
}

return nil
}
12 changes: 6 additions & 6 deletions diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import (
"os/exec"
)

func hasUncommittedChanges() (bool, error) {
func uncommittedChanges() (string, error) {
output, err := exec.Command("git", "diff").CombinedOutput()
if err != nil {
return false, err
return "", err
}
return len(bytes.TrimSpace(output)) > 0, nil
return string(bytes.TrimSpace(output)), nil
}

func hasUntrackedFiles() (bool, error) {
func untrackedFiles() (string, error) {
cmd := exec.Command("git", "ls-files", "--others", "--exclude-standard")
output, err := cmd.Output()
if err != nil {
return false, err
return "", err
}
return len(bytes.TrimSpace(output)) > 0, nil
return string(bytes.TrimSpace(output)), nil
}
16 changes: 0 additions & 16 deletions env.go

This file was deleted.

90 changes: 45 additions & 45 deletions guard.go
Original file line number Diff line number Diff line change
@@ -1,62 +1,62 @@
package main

type guardError struct {
message string
import (
"fmt"
"strings"
)

type guardResult struct {
uncommittedChanges string
untrackedFiles string
skipped bool
}

func (e *guardError) Error() string {
return e.message
}

func isGuardError(err error) bool {
_, ok := err.(*guardError)
return ok
}

func guard() error {
// 環境変数 GIT_EXEC_SKIP_GUARD, GIT_EXEC_SKIP_GUARD_UNCOMMITTED_CHANGES, GIT_EXEC_SKIP_GUARD_UNTRACKED_FILES には
// 以下の値で真偽値を表す文字列を想定する
// true: "true", "1", "yes", "on"
// false: "false", "0", "no", "off"
// 空文字列 あるいは それ以外の文字列は false として扱う
//
// GIT_EXEC_SKIP_GUARD あるいは GIT_EXEC_SKIP_GUARD_UNCOMMITTED_CHANGES のいずれかが true でなければ、コミットされていない変更があればエラーを返す
// GIT_EXEC_SKIP_GUARD あるいは GIT_EXEC_SKIP_GUARD_UNTRACKED_FILES のいずれかが true でなければ、追跡されていないファイルがあればエラーを返す

if err := guardUncommittedChanges(); err != nil {
return err
func (g *guardResult) Message() string {
var r string
if len(g.uncommittedChanges) > 0 && len(g.untrackedFiles) > 0 {
r = "There are uncommitted changes and untracked files"
} else if len(g.untrackedFiles) > 0 {
r = "There are untracked files"
} else {
r = "There are uncommitted changes"
}
if err := guardUntrackedFiles(); err != nil {
return err
if g.skipped {
r += " but guard was skipped by options"
}

return nil
return r
}

func guardUncommittedChanges() error {
if getEnvBool("GIT_EXEC_SKIP_GUARD") || getEnvBool("GIT_EXEC_SKIP_GUARD_UNCOMMITTED_CHANGES") {
return nil
func (g *guardResult) Format() string {
parts := []string{g.Message()}
if len(g.uncommittedChanges) > 0 {
parts = append(parts, fmt.Sprintf("Uncommitted changes:\n%s", g.uncommittedChanges))
}
diff, err := hasUncommittedChanges()
if err != nil {
return err
if len(g.untrackedFiles) > 0 {
parts = append(parts, fmt.Sprintf("Untracked files:\n%s", g.untrackedFiles))
}
if diff {
return &guardError{"There are uncommitted changes"}
}
return nil
return strings.Join(parts, "\n\n")
}

func guardUntrackedFiles() error {
if getEnvBool("GIT_EXEC_SKIP_GUARD") || getEnvBool("GIT_EXEC_SKIP_GUARD_UNTRACKED_FILES") {
return nil
func guard(opts *Options) (*guardResult, error) {
diff, err := uncommittedChanges()
if err != nil {
return nil, err
}
r, err := hasUntrackedFiles()

untrackedFiles, err := untrackedFiles()
if err != nil {
return err
return nil, err
}
if r {
return &guardError{"There are untracked files"}

if len(diff) == 0 && len(untrackedFiles) == 0 {
return nil, nil
}
return nil

return &guardResult{
uncommittedChanges: diff,
untrackedFiles: untrackedFiles,
skipped: opts.SkipGuard ||
(opts.SkipGuardUncommittedChanges && len(diff) > 0) ||
(opts.SkipGuardUntrackedFiles && len(untrackedFiles) > 0),
}, nil
}
77 changes: 28 additions & 49 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
package main

import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
)

func main() {
// 1. このプログラムに渡された引数をコマンドとして実行する。
// その際には、コマンドの照準出力と標準エラー出力をバッファに格納する。
// 2. "git add ." を実行し、コマンドによって作成・変更されたカレントディレクトリ以下のファイルを staging area に追加する。
// 3. "git commit" を以下のオプションと標準力を指定して実行する。
// オプション : --file -
// 標準入力: "🤖 (実行したコマンド)\n\n(バッファ)"

if len(os.Args) < 2 {
help()
os.Exit(1)
Expand All @@ -35,64 +26,52 @@ func main() {
} else {
showVersionWithExecName(filepath.Base(os.Args[0]))
}
} else if options.Directory != "" {
}

if err := process(options, commandArgs); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
}

func process(options *Options, commandArgs []string) error {
if options.Directory != "" {
if err := os.Chdir(options.Directory); err != nil {
fmt.Fprintf(os.Stderr, "Failed to change directory: %s\n", err.Error())
os.Exit(1)
return fmt.Errorf("Failed to change directory: %s", err.Error())
}
}

if err := guard(); err != nil {
if isGuardError(err) {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
var guardMessage string
if guardResult, err := guard(options); err != nil {
return err
} else if guardResult != nil {
if guardResult.skipped {
guardMessage = guardResult.Format()
fmt.Fprintf(os.Stderr, "Guard skipped: %s\n", guardMessage)
} else {
fmt.Printf("Guard failed: %+v\n", err)
return fmt.Errorf("Quit processing because %s", guardResult.Format())
}
os.Exit(1)
}

command := newCommand(commandArgs)

if err := command.Run(); err != nil {
fmt.Printf("Command execution failed: %+v\n%s", err, command.Output)
return
return fmt.Errorf("Command execution failed: %+v\n%s", err, command.Output)
}

uncommittedChanges, err := hasUncommittedChanges()
if err != nil {
fmt.Printf("git diff failed: %+v\n", err)
return
}
untrackedFiles, err := hasUntrackedFiles()
if err != nil {
fmt.Printf("git ls-files failed: %+v\n", err)
return
if err := add(); err != nil {
return err
}

if !uncommittedChanges && !untrackedFiles {
fmt.Printf("No changes to commit and No untracked files\n")
return
}
commitMessage := newCommitMessage(command, options)

// 2. "git add ." を実行し、コマンドによって作成・変更されたカレントディレクトリ以下のファイルを staging area に追加する。
if err := exec.Command("git", "add", ".").Run(); err != nil {
fmt.Printf("git add failed: %+v\n", err)
return
if guardMessage != "" {
commitMessage.Body = guardMessage + "\n\n" + commitMessage.Body
}

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

// See https://tracpath.com/docs/git-commit/
commitCmd := exec.Command("git", "commit", "--file", "-")
commitCmd.Stdin = bytes.NewBufferString(commitMessage)

if err := commitCmd.Run(); err != nil {
fmt.Printf("git commit failed: %+v\n", err)
return
}
return nil
}
11 changes: 11 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ type Options struct {
Emoji string
Prompt string
Template string

SkipGuard bool
SkipGuardUncommittedChanges bool
SkipGuardUntrackedFiles bool
}

func newOptions() *Options {
Expand Down Expand Up @@ -77,6 +81,10 @@ var (
optEmoji = newOptionType("-e", "--emoji", true, func(o *Options, v string) { o.Emoji = v })
optPrompt = newOptionType("-p", "--prompt", true, func(o *Options, v string) { o.Prompt = v })
optTemplate = newOptionType("-t", "--template", true, func(o *Options, v string) { o.Template = v })

optSkipGuard = newOptionType("", "--skip-guard", false, func(o *Options, _ string) { o.SkipGuard = true })
optSkipGuardUncommittedChanges = newOptionType("", "--skip-guard-uncommitted-changes", false, func(o *Options, _ string) { o.SkipGuardUncommittedChanges = true })
optSkipGuardUntrackedFiles = newOptionType("", "--skip-guard-untracked-files", false, func(o *Options, _ string) { o.SkipGuardUntrackedFiles = true })
)

var optionTypes = []*OptionType{
Expand All @@ -86,6 +94,9 @@ var optionTypes = []*OptionType{
optEmoji,
optPrompt,
optTemplate,
optSkipGuard,
optSkipGuardUncommittedChanges,
optSkipGuardUntrackedFiles,
}

var optionKeyMap = func() map[string]*OptionType {
Expand Down

0 comments on commit c060e9e

Please sign in to comment.