Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
gabyx committed Oct 10, 2020
1 parent 260621f commit c495637
Show file tree
Hide file tree
Showing 16 changed files with 393 additions and 153 deletions.
2 changes: 2 additions & 0 deletions githooks/Deprecation.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@

Namespace `<namespace>` can be defined in shared hooks in a file `.namespace`. Default is empty.
3. TODO: `githooks.trust.all` move to `githooks.trustAll`
4. Checksum file should not contain, disabled settings as well. There is a bug in `execute_opt_in_checks`: disabling hooks only works if repo `! is_trusted_repo`.
We store this in `.git/.githooks.disabled`
2 changes: 1 addition & 1 deletion githooks/common/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func CombineErrors(err error, errs ...error) error {
for _, e := range errs {
if e != nil {
anyNotNil = true
s += "\n" + e.Error()
s += ",\n " + e.Error()
}
}

Expand Down
2 changes: 1 addition & 1 deletion githooks/common/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type GitContext struct {
}

// GetWorkingDir gets the current working dir of the context
// to implement `ExecContext`
// to implement `IExecContext`
func (c *GitContext) GetWorkingDir() string {
return c.cwd
}
Expand Down
4 changes: 2 additions & 2 deletions githooks/common/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ type ILogContext interface {
WarnIfF(condition bool, format string, args ...interface{})
FatalIf(condition bool, lines ...string)
FatalIfF(condition bool, format string, args ...interface{})
AssertNoErrorWarn(err error, lines ...string)
AssertNoErrorWarnF(err error, format string, args ...interface{})
AssertNoErrorWarn(err error, lines ...string) bool
AssertNoErrorWarnF(err error, format string, args ...interface{}) bool
AssertNoErrorFatal(err error, lines ...string)
AssertNoErrorFatalF(err error, format string, args ...interface{})
}
Expand Down
8 changes: 6 additions & 2 deletions githooks/common/logasserts.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,21 @@ func (c *LogContext) FatalIfF(condition bool, format string, args ...interface{}
}

// AssertNoErrorWarn Assert no error, and otherwise log it.
func (c *LogContext) AssertNoErrorWarn(err error, lines ...string) {
func (c *LogContext) AssertNoErrorWarn(err error, lines ...string) bool {
if err != nil {
c.LogWarn(append(lines, " -> error: ["+err.Error()+"]")...)
return false
}
return true
}

// AssertNoErrorWarnF Assert no error, and otherwise log it.
func (c *LogContext) AssertNoErrorWarnF(err error, format string, args ...interface{}) {
func (c *LogContext) AssertNoErrorWarnF(err error, format string, args ...interface{}) bool {
if err != nil {
c.LogWarnF(format+"\n -> error: ["+err.Error()+"]", args...)
return false
}
return true
}

// AssertNoErrorFatal Assert no error, and otherwise log it.
Expand Down
18 changes: 14 additions & 4 deletions githooks/common/path.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
package common

import "os"
import (
"os"
)

// PathExists Checks if a path exists.
func PathExists(path string) bool {
// IsPathError returns `true` if the error is a `os.PathError`
func IsPathError(err error) bool {
return err != nil && err.(*os.PathError) != nil
}

// PathExists checks if a path exists.
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
return !os.IsNotExist(err)
if os.IsNotExist(err) || IsPathError(err) {
return false, nil
}
return err == nil, err
}

// IsDirectory checks if a path is a existing directory.
Expand Down
6 changes: 3 additions & 3 deletions githooks/common/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import (
"strings"
)

// ExecContext defines the context to execute commands
type ExecContext interface {
// IExecContext defines the context to execute commands
type IExecContext interface {
GetWorkingDir() string
}

// ExecuteScript calls a script `$1`.
// If it is not executable call it as a shell script.
func ExecuteScript(c ExecContext, script string, pipeAll bool, args ...string) (string, error) {
func ExecuteScript(c IExecContext, script string, pipeAll bool, args ...string) (string, error) {

var cmd *exec.Cmd

Expand Down
45 changes: 25 additions & 20 deletions githooks/githooks/githooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package hooks

import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
cm "rycus86/githooks/common"
Expand All @@ -26,27 +25,32 @@ var StagedFilesHookNames = [3]string{"pre-commit", "prepare-commit-msg", "commit
const EnvVariableStagedFiles = "STAGED_FILES"

// GetBugReportingInfo Get the default bug reporting url.
func GetBugReportingInfo(repoPath string) string {
func GetBugReportingInfo(repoPath string) (info string, err error) {
// Set default if needed
defer func() {
if strs.IsEmpty(info) {
info = strs.Fmt("-> Report this bug to: '%s'", DefaultBugReportingURL)
}
}()

// Check in the repo if possible
file := filepath.Join(repoPath, ".githooks", ".bug-report")
if cm.PathExists(file) {
file, err := os.Open(file)
if err != nil {
defer file.Close()
bugReportInfo, err := ioutil.ReadAll(file)
if err != nil {
return string(bugReportInfo)
}
}
exists, e := cm.PathExists(file)
if e != nil {
return info, e
}
// Check global Git config
bugReportInfo := cm.Git().GetConfig("githooks.bugReportInfo", cm.GlobalScope)
if bugReportInfo != "" {
return bugReportInfo

if exists {
data, e := ioutil.ReadFile(file)
if e != nil {
return info, e
}
info = string(data)
}

return strs.Fmt("-> Report this bug to: '%s'", DefaultBugReportingURL)
// Check global Git config
info = cm.Git().GetConfig("githooks.bugReportInfo", cm.GlobalScope)
return
}

// IsGithooksDisabled checks if Githooks is disabled in
Expand All @@ -70,10 +74,11 @@ func GetInstallDir(git *cm.GitContext) string {
}

// GetToolScript gets the tool script associated with the name `tool`
func GetToolScript(name string, installDir string) string {
func GetToolScript(name string, installDir string) (string, error) {
tool := filepath.Join(installDir, "tools", name, "run")
if cm.PathExists(tool) {
return tool
exists, err := cm.PathExists(tool)
if exists {
return tool, nil
}
return ""
return "", err
}
2 changes: 1 addition & 1 deletion githooks/githooks/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package hooks

// Hook contains the data to a executbale hook
type Hook struct {
// The path of the hook.
// The absolute path of the hook.
Path string
// The run command for the hook.
RunCmd []string
Expand Down
82 changes: 62 additions & 20 deletions githooks/githooks/ignores.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,70 @@ import (

// HookIgnorePatterns is the format of the ignore patterns.
type HookIgnorePatterns struct {
Patterns []string
Patterns []string // Shell file patterns (see `filepath.Match`)
HookNames []string // Specific hook names. @todo Introduce namespacing!
}

// GetHookIgnorePatterns gets all ignored hooks in the current repository
func GetHookIgnorePatterns(repoDir string, gitDir string, hookName string) (HookIgnorePatterns, error) {
// IgnorePatterns is the list of possible ignore patterns when running a runner.
type IgnorePatterns struct {
Worktree *HookIgnorePatterns
User *HookIgnorePatterns
}

// CombineIgnorePatterns combines two ignore patterns.
func CombineIgnorePatterns(patterns ...*HookIgnorePatterns) *HookIgnorePatterns {
var pComb *HookIgnorePatterns
for _, p := range patterns {
if p != nil {
if pComb == nil {
pComb = p
} else {
pComb.Combine(*p)
}

}
}
return pComb
}

var patterns1 HookIgnorePatterns
var err1 error
// GetHookIgnorePatternsWorktree gets all ignored hooks in the current worktree
func GetHookIgnorePatternsWorktree(repoDir string, hookName string) (patterns *HookIgnorePatterns, err error) {

file := filepath.Join(repoDir, ".githooks", ".ignore.yaml")
if cm.PathExists(file) {
patterns1, err1 = loadIgnorePatterns(file)
exists1, err := cm.PathExists(file)
if exists1 {
patterns, err = loadIgnorePatterns(file)
}

file = filepath.Join(repoDir, ".githooks", hookName, ".ignore.yaml")
if cm.PathExists(file) {
patterns2, err2 := loadIgnorePatterns(file)
if err2 != nil {
patterns1.Combine(patterns2)
}
cm.CombineErrors(err1, err2)
exists2, e := cm.PathExists(file)
err = cm.CombineErrors(err, e)

if exists2 {
patterns2, e := loadIgnorePatterns(file)
err = cm.CombineErrors(err, e)
patterns = CombineIgnorePatterns(patterns, patterns2)
}

return patterns1, err1
return
}

// GetHookIgnorePatterns gets all ignored hooks in the current Git directorys.
func GetHookIgnorePatterns(gitDir string) (*HookIgnorePatterns, error) {
file := filepath.Join(gitDir, ".githooks.ignore.yaml")
exists, err := cm.PathExists(file)
if exists {
return loadIgnorePatterns(file)
}
return nil, err
}

// LoadIgnorePatterns loads patterns.
func loadIgnorePatterns(file string) (HookIgnorePatterns, error) {
func loadIgnorePatterns(file string) (*HookIgnorePatterns, error) {
var patterns HookIgnorePatterns

err := cm.LoadYAML(file, &patterns)
if err != nil {
return patterns, err
return nil, err
}

// Filter all malformed patterns and report
Expand All @@ -56,16 +87,16 @@ func loadIgnorePatterns(file string) (HookIgnorePatterns, error) {
}
patterns.Patterns = strs.Filter(patterns.Patterns, patternIsValid)

return patterns, err
return &patterns, err
}

// Combine combines two patterns.
func (h *HookIgnorePatterns) Combine(p HookIgnorePatterns) {
h.Patterns = append(h.Patterns, p.Patterns...)
}

// Matches returns true if `hookPath` matches any of the patterns and otherwise `false`
func (h *HookIgnorePatterns) Matches(hookPath string) bool {
// IsIgnored returns true if `hookPath` is ignored and otherwise `false`
func (h *HookIgnorePatterns) IsIgnored(hookPath string) bool {

for _, p := range h.Patterns {
matched, err := filepath.Match(p, hookPath)
Expand All @@ -75,3 +106,14 @@ func (h *HookIgnorePatterns) Matches(hookPath string) bool {

return false
}

// IsIgnored returns `true` is ignored by either the worktree patterns or the user patterns
// and otherwise `false`. The second value is `true` if it was ignored by the user patterns.
func (h *IgnorePatterns) IsIgnored(hookPath string) (bool, bool) {
if h.Worktree != nil && h.Worktree.IsIgnored(hookPath) {
return true, false
} else if h.User != nil && h.User.IsIgnored(hookPath) {
return true, true
}
return false, false
}
57 changes: 36 additions & 21 deletions githooks/githooks/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@ func isTerminal() bool {
// ShowPrompt shows a prompt to the user with `text`
// with the options `shortOptions` and optional long options `longOptions`.
func ShowPrompt(
execCtx cm.ExecContext,
execCtx cm.IExecContext,
installDir string,
text string,
hintText string,
shortOptions string,
longOptions ...string) (string, error) {

var err error
dialogTool := GetToolScript(installDir, "dialog")
isAnswerCorrect := func(answer string) bool {
return strs.Includes(strings.Split(shortOptions, "/"), answer)
}

dialogTool, err := GetToolScript(installDir, "dialog")

if dialogTool != "" {
if strs.IsNotEmpty(dialogTool) {

answer, e := cm.ExecuteScript(execCtx,
dialogTool, true,
Expand All @@ -38,13 +41,13 @@ func ShowPrompt(
longOptions...)...)

if e == nil {
if !strs.Includes(strings.Split(shortOptions, "/"), answer) {
return "", cm.ErrorF("Dialog tool returned wrong answer '%s'", answer)
if !isAnswerCorrect(answer) {
return "", cm.ErrorF("Dialog tool returned wrong answer '%s' not in '%q'", answer, shortOptions)
}
return answer, nil
}

err = cm.ErrorF("Could not execute dialog script '%s'", dialogTool)
err = cm.CombineErrors(e, cm.ErrorF("Could not execute dialog script '%s'", dialogTool))
// else: Runnning fallback ...
}

Expand All @@ -59,23 +62,35 @@ func ShowPrompt(
if color.IsSupportColor() {
text = color.FgGreen.Render(text)
}
fmt.Print(text)

var answer string
reader := bufio.NewReader(os.Stdin)
answer, e := reader.ReadString('\n')

// For visual separation
fmt.Print("\n")

if e == nil {

// For debugging ...
if cm.PrintPromptAnswer {
fmt.Printf("Githooks: answer: '%s'\n", strings.TrimSpace(answer))
try := 0
maxTries := 3
answerIncorrect := true
for answerIncorrect && try < maxTries {

fmt.Print(text)
var answer string
reader := bufio.NewReader(os.Stdin)
answer, e := reader.ReadString('\n')

// For visual separation
fmt.Print("\n")

if e == nil {
answer = strings.TrimSpace(answer)
if !isAnswerCorrect(answer) {
fmt.Printf("Answer not in '%q', try again ...", shortOptions)
}
// For debugging ...
if cm.PrintPromptAnswer {
fmt.Printf("Githooks: answer: '%s'\n", answer)
}

return answer, nil
}

return strings.TrimSpace(answer), nil
fmt.Print("Could not get answer, try again ...")
try++
}

err = cm.CombineErrors(err, cm.Error("Could not read answer from stdin"))
Expand Down

0 comments on commit c495637

Please sign in to comment.