Skip to content

Commit

Permalink
Add 'git hooks shared root' command (#42)
Browse files Browse the repository at this point in the history
- Obtaining the current shared hook by given namespace name inside a githooks-enabled repository.
  • Loading branch information
gabyx authored Aug 31, 2021
1 parent e20f3c3 commit 0189029
Show file tree
Hide file tree
Showing 21 changed files with 263 additions and 73 deletions.
2 changes: 1 addition & 1 deletion .githooks/pre-commit/shfmt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fi
SUCCESS=0
for FILE in $STAGED_FILES; do
if echo "$FILE" | grep -qE "\.sh$" &&
! shfmt -p -d -i 4 "$FILE"; then
! shfmt -d -i 4 "$FILE"; then
echo "! shfmt problems detected in $FILE" >&2
SUCCESS=1
fi
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Also it searches for hooks in configured shared hook repositories.
<summary><b>Table of Content (click to expand)</b></summary>
<!-- TOC -->

- [asdasd](#asdasd)
- [Layout and Options](#layout-and-options)
- [Execution](#execution)
- [Hook Run Configuration](#hook-run-configuration)
Expand Down Expand Up @@ -88,6 +89,8 @@ Also it searches for hooks in configured shared hook repositories.
<!-- /TOC -->
</details>

## asdasd

## Layout and Options

Take this snippet of a Git repository layout as an example:
Expand Down Expand Up @@ -382,6 +385,10 @@ The priority to find hooks in a shared hook repository is as follows: consider h

Each of these directories can be of the same format as the normal `.githooks` folder in a single repository.

You can get the root directory of a configured shared repository with namespace `<namespace>` by running
`git hooks shared root ns:<namespace>`. This might be helpful in scripts if you have common
shared functionality inside this shared repository you want to use.

### Shared Repository Namespace

A shared repository can optionally have a namespace associated with it. The name can be stored in a file `.namespace` in any possible hooks directory `<hooksDir>` of the shared repository, see [layout](#layout-of-shared-hook-repositories).
Expand Down
1 change: 1 addition & 0 deletions docs/cli/git_hooks_shared.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ git hooks shared
* [git hooks shared list](git_hooks_shared_list.md) - List shared repositories.
* [git hooks shared purge](git_hooks_shared_purge.md) - Purge shared repositories.
* [git hooks shared remove](git_hooks_shared_remove.md) - Remove shared repositories.
* [git hooks shared root](git_hooks_shared_root.md) - Get the root directory of shared repository in the current repository.
* [git hooks shared update](git_hooks_shared_update.md) - Update shared repositories.

###### Auto generated by spf13/cobra
27 changes: 27 additions & 0 deletions docs/cli/git_hooks_shared_root.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## git hooks shared root

Get the root directory of shared repository in the current repository.

### Synopsis

Returns root directories of shared repository in the current repository
by its namespace name (e.g. `ns:my-namespace`).
Exit-code `1` is returned only if any shared repositories have not been found.
The returned directories may not yet exist and will be empty in that case.
Run `git hooks shared update` for them to exist.

```
git hooks shared root <namespace>...
```

### Options

```
-h, --help help for root
```

### SEE ALSO

* [git hooks shared](git_hooks_shared.md) - Manages the shared hook repositories.

###### Auto generated by spf13/cobra
2 changes: 1 addition & 1 deletion docs/cli/git_hooks_shared_update.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Update shared repositories.

### Synopsis

Update all the shared repositories, either by
Update all shared repositories, either by
running `git pull` on existing ones or `git clone` on new ones.

```
Expand Down
18 changes: 16 additions & 2 deletions githooks/apps/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"path/filepath"

"github.com/gabyx/githooks/githooks/cmd"
ccm "github.com/gabyx/githooks/githooks/cmd/common"
cm "github.com/gabyx/githooks/githooks/common"
"github.com/gabyx/githooks/githooks/hooks"
)
Expand All @@ -17,15 +18,28 @@ func mainRun() (exitCode int) {
log, err := cm.CreateLogContext(false)
cm.AssertOrPanic(err == nil, "Could not create log")

exitCode = 1
panicExitCode := 1
wrapPanicExitCode := func() {
panicExitCode = 111
}

// Handle all panics and report the error
defer func() {
r := recover()
if cm.HandleCLIErrors(r, cwd, log, hooks.GetBugReportingInfo) {
exitCode = 1
exitCode = panicExitCode
}
}()

cmd.Run(log, log)
err = cmd.Run(log, log, wrapPanicExitCode)

// Overwrite the exit code if its a command exit error.
if v, ok := err.(ccm.CmdExit); ok {
exitCode = v.ExitCode
} else if err == nil {
exitCode = 0
}

return
}
Expand Down
16 changes: 1 addition & 15 deletions githooks/apps/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -778,24 +778,10 @@ func getHooksInShared(settings *HookSettings,

hookNamespace := hooks.GetDefaultHooksNamespaceShared(shRepo)

// 1. priority has non-dot folder 'githooks'
dir := hooks.GetSharedGithooksDir(shRepo.RepositoryDir)
if cm.IsDirectory(dir) {
return getHooksIn(settings, uiSettings,
shRepo.RepositoryDir, dir, true, hookNamespace, true, ignores, checksums)
}

// 2. priority is the normal '.githooks' folder.
// This is second, to allow internal development Githooks inside shared repos.
dir = hooks.GetGithooksDir(shRepo.RepositoryDir)
if cm.IsDirectory(dir) {
return getHooksIn(settings, uiSettings,
shRepo.RepositoryDir, dir, true, hookNamespace, true, ignores, checksums)
}

// 3. Fallback to the whole repository.
return getHooksIn(settings, uiSettings,
shRepo.RepositoryDir, shRepo.RepositoryDir, true, hookNamespace, true, ignores, checksums)
shRepo.RepositoryDir, dir, true, hookNamespace, true, ignores, checksums)
}

func logBatches(title string, hooks hooks.HookPrioList) {
Expand Down
21 changes: 21 additions & 0 deletions githooks/cmd/common/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
cm "github.com/gabyx/githooks/githooks/common"
"github.com/gabyx/githooks/githooks/git"
"github.com/gabyx/githooks/githooks/prompt"
strs "github.com/gabyx/githooks/githooks/strings"
)

// CmdContext is the context for the CLI.
Expand All @@ -18,4 +19,24 @@ type CmdContext struct {
LogStats cm.ILogStats // The statistics of the log context.

PromptCtx prompt.IContext // The general prompt context (will be different for install/uninstall).

WrapPanicExitCode func() // Wraps the panic exit code to 111 instead of 1.
}

// CmdExit is generic exit error with exit code.
type CmdExit struct {
ExitCode int // The exit code.
}

// Error returns the error string.
func (e CmdExit) Error() string {
return strs.Fmt("Exit code: '%v'.", e.ExitCode)
}

// NewCmdExit creates a new command error with exit code.
// The error is logged directly.
func (c *CmdContext) NewCmdExit(ec int, format string, args ...interface{}) error {
c.Log.ErrorF(format, args...)

return CmdExit{ExitCode: ec}
}
33 changes: 21 additions & 12 deletions githooks/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import (
)

// NewSettings creates common settings to use for all commands.
func NewSettings(log cm.ILogContext, logStats cm.ILogStats) ccm.CmdContext {
func NewSettings(
log cm.ILogContext,
logStats cm.ILogStats,
wrapPanicExitCode func()) ccm.CmdContext {

var promptCtx prompt.IContext
var err error
Expand All @@ -40,13 +43,14 @@ func NewSettings(log cm.ILogContext, logStats cm.ILogStats) ccm.CmdContext {
log.AssertNoErrorF(err, "Prompt setup failed -> using fallback.")

return ccm.CmdContext{
Cwd: cwd,
GitX: git.CtxC(cwd),
InstallDir: installDir,
CloneDir: hooks.GetReleaseCloneDir(installDir),
PromptCtx: promptCtx,
Log: log,
LogStats: logStats}
Cwd: cwd,
GitX: git.CtxC(cwd),
InstallDir: installDir,
CloneDir: hooks.GetReleaseCloneDir(installDir),
PromptCtx: promptCtx,
Log: log,
LogStats: logStats,
WrapPanicExitCode: wrapPanicExitCode}
}

func addSubCommands(cmd *cobra.Command, ctx *ccm.CmdContext) {
Expand Down Expand Up @@ -98,15 +102,20 @@ func initArgs() {
}

// Run executes the main CLI function.
func Run(log cm.ILogContext, logStats cm.ILogStats) {
func Run(log cm.ILogContext, logStats cm.ILogStats, wrapPanicExitCode func()) error {

ctx := NewSettings(log, logStats)
ctx := NewSettings(log, logStats, wrapPanicExitCode)
cmd := MakeGithooksCtl(&ctx)

err := cmd.Execute()

if err != nil {
_ = cmd.Help()
// If its not a command exit, print the help.
if _, ok := err.(ccm.CmdExit); !ok {
ctx.Log.AssertNoErrorF(err, "Command failed.")
_ = cmd.Help()
}
}

ctx.Log.AssertNoErrorPanic(err, "Command failed.")
return err
}
96 changes: 83 additions & 13 deletions githooks/cmd/shared/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,18 +167,18 @@ func runSharedList(ctx *ccm.CmdContext, opts *sharedOpts) {
ccm.AssertRepoRoot(ctx)
}

shared, err := hooks.LoadConfigSharedHooks(ctx.InstallDir, ctx.GitX, git.LocalScope)
local, err := hooks.LoadConfigSharedHooks(ctx.InstallDir, ctx.GitX, git.LocalScope)
ctx.Log.AssertNoErrorPanicF(err, "Could not load local shared hook list.")

ctx.Log.InfoF("Local shared hook repositories:\n%s", format(shared))
ctx.Log.InfoF("Local shared hook repositories:\n%s", format(local))

}

if opts.Global {
shared, err := hooks.LoadConfigSharedHooks(ctx.InstallDir, ctx.GitX, git.GlobalScope)
ctx.Log.AssertNoErrorPanicF(err, "Could not load local shared hook list.")
global, err := hooks.LoadConfigSharedHooks(ctx.InstallDir, ctx.GitX, git.GlobalScope)
ctx.Log.AssertNoErrorPanicF(err, "Could not load global shared hook list.")

ctx.Log.InfoF("Global shared hook repositories:\n%s", format(shared))
ctx.Log.InfoF("Global shared hook repositories:\n%s", format(global))
}

}
Expand All @@ -198,7 +198,61 @@ func runSharedUpdate(ctx *ccm.CmdContext) {
ctx.Log.InfoF("Update '%v' shared repositories.", updated)
}

func runSharedLocation(ctx *ccm.CmdContext, urls []string) {
func runSharedRoot(ctx *ccm.CmdContext, namespaces []string) (exitCode error) {
ctx.WrapPanicExitCode()
repoDir, _, _ := ccm.AssertRepoRoot(ctx)

for i := range namespaces {
ns := strings.TrimPrefix(namespaces[i], "ns:")
ctx.Log.PanicIfF(ns == namespaces[i],
"Specify namespace name '%s' with suffix 'ns:'.", namespaces[i])
namespaces[i] = ns
}

// Cycle through all shared hooks an return the first with matching namespace.
allRepos, err := hooks.LoadRepoSharedHooks(ctx.InstallDir, repoDir)
ctx.Log.AssertNoErrorPanicF(err, "Could not load shared hook list '%s'.", hooks.GetRepoSharedFileRel())
local, err := hooks.LoadConfigSharedHooks(ctx.InstallDir, ctx.GitX, git.LocalScope)
ctx.Log.AssertNoErrorPanicF(err, "Could not load local shared hook list.")
global, err := hooks.LoadConfigSharedHooks(ctx.InstallDir, ctx.GitX, git.GlobalScope)
ctx.Log.AssertNoErrorPanicF(err, "Could not load local shared hook list.")

allRepos = append(allRepos, local...)
allRepos = append(allRepos, global...)

roots := make([]string, len(namespaces))
found := 0

for rI := range allRepos {
if !cm.IsDirectory(allRepos[rI].RepositoryDir) {
continue
}

hooksDir := hooks.GetSharedGithooksDir(allRepos[rI].RepositoryDir)
ns, err := hooks.GetHooksNamespace(hooksDir)
ctx.Log.AssertNoErrorPanicF(err, "Could not get hook namespace in '%s'", hooksDir)

for nI := range namespaces {
if namespaces[nI] == ns {
roots[nI] = allRepos[rI].RepositoryDir
found++
}
}
}

for i := range roots {
_, err := ctx.Log.GetInfoWriter().Write([]byte(roots[i] + "\n"))
ctx.Log.AssertNoErrorF(err, "Could not write output.")
}

if found != len(roots) {
exitCode = ctx.NewCmdExit(1, "Did not find all shared repositories.")
}

return
}

func runSharedRootFromUrl(ctx *ccm.CmdContext, urls []string) {
for _, url := range urls {
location := hooks.GetSharedCloneDir(ctx.InstallDir, url)
_, err := ctx.Log.GetInfoWriter().Write([]byte(location + "\n"))
Expand Down Expand Up @@ -288,21 +342,36 @@ file is modified in the local repository.`, hooks.GetRepoSharedFileRel())
sharedUpdateCmd := &cobra.Command{
Use: "update",
Short: `Update shared repositories.`,
Long: `Update all the shared repositories, either by
Long: `Update all shared repositories, either by
running 'git pull' on existing ones or 'git clone' on new ones.`,
Aliases: []string{"pull"},
Run: func(cmd *cobra.Command, args []string) {
runSharedUpdate(ctx)
}}

sharedLocationCmd := &cobra.Command{
Use: "location [URL]...",
Short: `Get the clone location of a shared repository URL.`,
Long: `Returns the clone location of a shared repository URL.`,
sharedRootCmd := &cobra.Command{
Use: "root <namespace>...",
Short: `Get the root directory of shared repository in the current repository.`,
Long: `Returns root directories of shared repository in the current repository
by its namespace name (e.g. 'ns:my-namespace').
Exit-code '1' is returned only if any shared repositories have not been found.
The returned directories may not yet exist and will be empty in that case.
Run 'git hooks shared update' for them to exist.`,
PreRun: ccm.PanicIfNotRangeArgs(ctx.Log, 1, -1),
RunE: func(cmd *cobra.Command, args []string) error {
return runSharedRoot(ctx, args)
}}

sharedRootFromUrlCmd := &cobra.Command{
Use: "root-from-url <git-url>...",
Short: `Get the root directory of a shared repository '<git-url>'.`,
Long: `Returns the root locations shared repository '<git-url>'s.
The returned directories may not yet exist exist and will be empty in that case.
To ensure run 'git hooks shared update'.`,
Hidden: true,
PreRun: ccm.PanicIfNotRangeArgs(ctx.Log, 1, -1),
Run: func(cmd *cobra.Command, args []string) {
runSharedLocation(ctx, args)
runSharedRootFromUrl(ctx, args)
}}

addSharedOpts(sharedAddCmd, &opts, false)
Expand All @@ -319,7 +388,8 @@ running 'git pull' on existing ones or 'git clone' on new ones.`,

sharedCmd.AddCommand(ccm.SetCommandDefaults(ctx.Log, sharedPurgeCmd))
sharedCmd.AddCommand(ccm.SetCommandDefaults(ctx.Log, sharedUpdateCmd))
sharedCmd.AddCommand(ccm.SetCommandDefaults(ctx.Log, sharedLocationCmd))
sharedCmd.AddCommand(ccm.SetCommandDefaults(ctx.Log, sharedRootCmd))
sharedCmd.AddCommand(ccm.SetCommandDefaults(ctx.Log, sharedRootFromUrlCmd))

return ccm.SetCommandDefaults(ctx.Log, sharedCmd)
}
6 changes: 3 additions & 3 deletions githooks/common/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ const (
GithooksEmoji = "🦎"

githooksSuffix = "" // If you like you can make it: "Githooks: "
debugSuffix = "🛠 " + githooksSuffix
debugSuffix = "🛠 " + githooksSuffix
infoSuffix = GithooksEmoji + " " + githooksSuffix
warnSuffix = "⛑ " + githooksSuffix
errorSuffix = "⛔ "
warnSuffix = "⛑ " + githooksSuffix
errorSuffix = "⛔ "
promptSuffix = "❓ " + githooksSuffix
indent = " "

Expand Down
Loading

0 comments on commit 0189029

Please sign in to comment.