Compare commits

...

13 Commits
v0.1.0 ... main

Author SHA1 Message Date
5f0db640df support config clone timeout, do upgrades 2025-04-23 11:01:52 -04:00
d291489247 Update README
All checks were successful
Build and Publish / release (push) Successful in 1m32s
2024-12-31 21:37:26 -05:00
30c65042dd Add multi-arch builds
All checks were successful
Build and Publish / release (push) Successful in 2m21s
2024-12-31 21:16:05 -05:00
f14bc47c7b Get version from built metadata
All checks were successful
Build and Publish / release (push) Successful in 1m5s
2024-12-31 20:58:24 -05:00
870760443e Add version to build
All checks were successful
Build and Publish / release (push) Successful in 1m6s
2024-12-31 20:51:04 -05:00
0cc5542ab6 Merge pull request 'avoid-project-id-collisions' (#2) from avoid-project-id-collisions into main
All checks were successful
Build and Publish / release (push) Successful in 1m35s
Reviewed-on: #2
2025-01-01 01:23:08 +00:00
2f0e1b0d46 Implement cache migrations 2024-12-31 20:12:02 -05:00
30d86d72ed Implement cache migrations 2024-12-31 17:45:45 -05:00
70027a9880 Begin move to project/alias by unique ID 2024-12-30 16:42:45 -05:00
11a2ca434c Merge pull request 'refactor-cmd-pkgs' (#1) from refactor-cmd-pkgs into main
Reviewed-on: #1
2024-12-30 20:56:38 +00:00
b9d7d5a4f2 Package subcommand code 2024-12-30 15:50:31 -05:00
96378d047e Package subcommand code 2024-12-30 14:54:32 -05:00
888ee5ea4a Fix code analyzer warnings 2024-12-30 13:20:27 -05:00
77 changed files with 1442 additions and 982 deletions

View File

@ -9,6 +9,7 @@ env:
BINARY_NAME: git-project-manager BINARY_NAME: git-project-manager
GO_MOD_PATH: gitea.libretechconsulting.com/rmcguire/git-project-manager GO_MOD_PATH: gitea.libretechconsulting.com/rmcguire/git-project-manager
GO_GIT_HOST: gitea.libretechconsulting.com GO_GIT_HOST: gitea.libretechconsulting.com
PLATFORMS: linux/amd64 linux/arm64 darwin/amd64 darwin/arm64
jobs: jobs:
release: release:
@ -23,22 +24,29 @@ jobs:
go-version: '1.23' go-version: '1.23'
- name: Build Binary - name: Build Binary
env:
VERSION: ${{ github.ref_name }}
run: make all run: make all
- name: Upload Binary to Generic Registry - name: Upload Binaries to Generic Registry
env: env:
API_TOKEN: ${{ secrets.API_TOKEN }} API_TOKEN: ${{ secrets.API_TOKEN }}
run: | run: |
echo "Pushing ./$BINARY_PATH/$BINARY_NAME to ${GITHUB_SERVER_URL} packages for ${GITHUB_REPOSITORY_OWNER} as ${PACKAGE_NAME}@${{ github.ref_name }}" for platform in $PLATFORMS; do
if [ -f ./${BINARY_PATH}/${BINARY_NAME} ]; then OS=$(echo $platform | cut -d/ -f1)
curl -X PUT \ ARCH=$(echo $platform | cut -d/ -f2)
-H "Authorization: token ${API_TOKEN}" \ BINARY_FILE="${BINARY_PATH}/${PACKAGE_NAME}-${OS}-${ARCH}"
--upload-file ./${BINARY_PATH}/${BINARY_NAME} \ echo "Uploading $BINARY_FILE"
"${GITHUB_SERVER_URL}/api/packages/${GITHUB_REPOSITORY_OWNER}/generic/${PACKAGE_NAME}/${{ github.ref_name }}/${BINARY_NAME}" if [ -f "$BINARY_FILE" ]; then
else curl -X PUT \
echo "Error: Binary ./${BINARY_PATH}/${BINARY_NAME} not found." -H "Authorization: token ${API_TOKEN}" \
exit 1 --upload-file "$BINARY_FILE" \
fi "${GITHUB_SERVER_URL}/api/packages/${GITHUB_REPOSITORY_OWNER}/generic/${PACKAGE_NAME}/${{ github.ref_name }}/${PACKAGE_NAME}-${OS}-${ARCH}"
else
echo "Error: Binary $BINARY_FILE not found."
exit 1
fi
done
- name: Generate and Upload Package to Go Registry - name: Generate and Upload Package to Go Registry
env: env:

10
.vscode/launch.json vendored
View File

@ -14,6 +14,16 @@
"https://gitea.libretechconsulting.com" "https://gitea.libretechconsulting.com"
] ]
}, },
{
"name": "List Aliases",
"type": "delve",
"request": "launch",
"program": "${workspaceFolder}",
"args": [
"alias",
"ls"
]
},
{ {
"name": "Generate Docs", "name": "Generate Docs",
"type": "delve", "type": "delve",

View File

@ -2,16 +2,29 @@ CMD_NAME := git-project-manager
.PHONY: all test build install docs clean .PHONY: all test build install docs clean
VERSION ?= development # Default to "development" if VERSION is not set
PLATFORMS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64
OUTPUT_DIR := bin
PKG := gitea.libretechconsulting.com/rmcguire/git-project-manager
all: test build install docs all: test build install docs
test: test:
go test -v ./... go test -v ./...
build: test build: test
go build -o bin/${CMD_NAME} @echo "Building for platforms: $(PLATFORMS)"
@for platform in $(PLATFORMS); do \
OS=$$(echo $$platform | cut -d/ -f1); \
ARCH=$$(echo $$platform | cut -d/ -f2); \
OUTPUT="$(OUTPUT_DIR)/$(CMD_NAME)-$$OS-$$ARCH"; \
GOOS=$$OS GOARCH=$$ARCH go build -ldflags "-X $(PKG)/cmd.Version=$(VERSION)" -o $$OUTPUT; \
echo "Built $$OUTPUT"; \
done
go build -ldflags "-X $(PKG)/cmd.Version=$(VERSION)" -o bin/${CMD_NAME}
install: install:
go install -v . go install -v -ldflags "-X $(PKG)/cmd.Version=$(VERSION)" .
docs: docs:
bin/${CMD_NAME} docs md bin/${CMD_NAME} docs md

View File

@ -8,6 +8,36 @@ and shortcuts.
This supports GitHub, GitLab, and Gitea remotes. This supports GitHub, GitLab, and Gitea remotes.
## Installation
Install git-project-manager using `go`:
`go install -v gitea.libretechconsulting.com/rmcguire/git-project-manager@latest`
OR Download a pre-built binary:
[https://gitea.libretechconsulting.com/rmcguire/-/packages/generic/git-project-manager](https://gitea.libretechconsulting.com/rmcguire/-/packages/generic/git-project-manager)
## Setup
**Generate a new config file:**
`git-project-manager config generate --write`
**For handy aliases and shell helpers:**
- Download `https://gitea.libretechconsulting.com/rmcguire/git-project-manager/raw/branch/main/contrib/gpm_func_omz.zsh`
- Implement however you like (e.g. copy to ~/.oh-my-zsh/custom/)
For just completion only, source completions from git-project-manager (e.g. for zsh, `source <(git-project-manager completion zsh)`)
_The following commands assume you've loaded the gpm alias and help funcs._
**Index your git repos**
`gpm cache load`
**Add your first project**
`padd` (gpm project add)
**Go to your project, fuzzily**
`pgo`
## Documentation ## Documentation
[Full documentation is available in docs/](./docs/git-project-manager.md) [Full documentation is available in docs/](./docs/git-project-manager.md)
@ -40,15 +70,6 @@ The basic workflow looks like this:
1. **List** -- get a list of your configured projects any time with `plist` 1. **List** -- get a list of your configured projects any time with `plist`
1. **Reward** -- buy the author a beer, because this thing is a time saver 1. **Reward** -- buy the author a beer, because this thing is a time saver
## Installing
1. Install into your go path by running `go install .`
1. Copy `contrib/gpm_func_omz.zsh` into your `~/.oh-my-zsh/custom/` path, or just source from your bashrc/zshrc
1. Generate config file: `gpm config gen --write`
1. You can run this any time to update settings
1. It will only add one Git remote, so update the file for multiple
1. Run `gpm cache load` (if aliases is in-place, otherwise `git-project-manager cache load`)
### Config Sample ### Config Sample
```yaml ```yaml
remotes: remotes:

View File

@ -1,25 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
)
var aliasCmd = &cobra.Command{
Use: "alias",
Aliases: []string{"aliases", "a"},
Short: "Manage project aliases",
Long: aliasCmdLong,
// Just re-use the hooks for project
PersistentPreRun: initProjectCmd,
PersistentPostRun: postProjectCmd,
}
func init() {
rootCmd.AddCommand(aliasCmd)
}
func mustHaveAliases(cmd *cobra.Command, args []string) {
if len(projectCache.Aliases) == 0 {
plog.Fatal("No aliases set, nothing to " + cmd.Name())
}
}

38
cmd/alias/alias.go Normal file
View File

@ -0,0 +1,38 @@
package alias
import (
"github.com/spf13/cobra"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
)
var AliasCmd = &cobra.Command{
Use: "alias",
Aliases: []string{"aliases", "a"},
Short: "Manage project aliases",
Long: util.AliasCmdLong,
// Just re-use the hooks for project
PersistentPreRun: util.InitProjects,
PersistentPostRun: util.PostProjectCmd,
}
var utils *util.Utils
func aliasCmdPreRun(cmd *cobra.Command, args []string) {
utils = util.MustFromCtx(cmd.Context())
util.InitProjects(cmd, args)
}
func mustHaveAliases(cmd *cobra.Command, args []string) {
utils = util.MustFromCtx(cmd.Context())
if len(utils.Cache().Aliases) == 0 {
utils.Logger().Fatal("No aliases set, nothing to " + cmd.Name())
}
}
func init() {
AliasCmd.AddCommand(aliasAddCmd)
AliasCmd.AddCommand(aliasListCmd)
AliasCmd.AddCommand(aliasDeleteCmd)
}

116
cmd/alias/alias_add.go Normal file
View File

@ -0,0 +1,116 @@
package alias
import (
"fmt"
"strings"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
)
var aliasAddCmd = &cobra.Command{
Use: "add",
Aliases: []string{"set", "a", "s"},
Short: "Add a project alias",
Args: cobra.ArbitraryArgs,
Long: util.AliasAddCmdLong,
Run: runAddAliasCmd,
}
func runAddAliasCmd(cmd *cobra.Command, args []string) {
var project *projects.Project
// Check by flag
if projectID := viper.GetInt(util.ViperAliasAddPID); projectID > 0 {
utils.Logger().Debug(fmt.Sprintf("Adding for inbound project ID %d", projectID))
projects := utils.Cache().GetProjectsByID(projectID)
if len(projects) > 0 {
project = projects[0]
}
if len(projects) > 1 {
utils.Logger().
Warn(fmt.Sprintf("found %d remotes with same ID, using first remote", len(projects)))
}
}
// Check by arg
if len(args) > 0 {
project = utils.FzfFindProject(&util.FzfProjectOpts{Ctx: cmd.Context(), Search: utils.SearchStringFromArgs(args)})
}
// Collect by fzf
if project == nil {
var err error
project, err = utils.FzfProject(&util.FzfProjectOpts{Ctx: cmd.Context()})
if err != nil || project == nil {
utils.Logger().Fatal("No project to alias, nothing to do", utils.Logger().Args("error", err))
}
}
AddNewAliases(cmd, project.GetID())
}
func AddNewAliases(cmd *cobra.Command, projectID string) {
u := util.MustFromCtx(cmd.Context())
project := u.Cache().GetProjectByID(projectID)
if project == nil {
u.Logger().Error("Failed to find project to alias", u.Logger().Args("projectID", projectID))
return
}
// Collect the aliases
aliases := PromptAliasesForProject(cmd, project)
// Add aliases
for _, a := range aliases {
a = strings.Trim(a, " '\"%<>|`")
if a == "" {
continue
}
if err := u.Cache().AddAlias(a, project); err != nil {
u.Logger().Debug("Skipping alias add", u.Logger().Args(
"error", err,
"alias", a,
))
} else {
u.Logger().Info("Successfully added alias to project", u.Logger().Args(
"project", project.String(),
"alias", a,
))
}
}
}
func PromptAliasesForProject(cmd *cobra.Command, p *projects.Project) []string {
u := util.MustFromCtx(cmd.Context())
aliases := u.Cache().GetProjectAliases(p)
if len(aliases) > 0 {
u.Logger().Info("Adding aliases to project", u.Logger().Args(
"project", p.String(),
"existingAliases", cache.ProjectAliasesString(aliases),
))
} else {
pterm.Info.Printfln("Adding aliases to %s", p.Name)
}
response, _ := pterm.DefaultInteractiveTextInput.
WithMultiLine(false).
WithDefaultValue(p.Path + " ").
Show("Enter aliases separated by space")
return strings.Split(response, " ")
}
func init() {
aliasAddCmd.PersistentFlags().Int(util.FlagProjectID, 0, "Specify a project by ID")
aliasAddCmd.RegisterFlagCompletionFunc(util.FlagProjectID, util.ValidProjectIdFunc)
viper.BindPFlag(util.ViperAliasAddPID, aliasAddCmd.Flag(util.FlagProjectID))
}

View File

@ -1,19 +1,20 @@
package cmd package alias
import ( import (
"fmt" "fmt"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
"github.com/pterm/pterm" "github.com/pterm/pterm"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
) )
var aliasDeleteCmd = &cobra.Command{ var aliasDeleteCmd = &cobra.Command{
Use: "delete [fuzzy project or alias]", Use: "delete [fuzzy project or alias]",
Aliases: []string{"rm", "del", "d"}, Aliases: []string{"rm", "del", "d"},
Short: "Delete a project alias", Short: "Delete a project alias",
Long: aliasDeleteCmdLong, Long: util.AliasDeleteCmdLong,
PreRun: mustHaveAliases, PreRun: mustHaveAliases,
Run: runDeleteAliasCmd, Run: runDeleteAliasCmd,
} }
@ -22,32 +23,32 @@ func runDeleteAliasCmd(cmd *cobra.Command, args []string) {
var project *projects.Project var project *projects.Project
var err error var err error
fzfOpts := &fzfProjectOpts{ fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(), Ctx: cmd.Context(),
MustHaveAlias: true, MustHaveAlias: true,
} }
if len(args) > 0 { if len(args) > 0 {
fzfOpts.Search = searchStringFromArgs(args) fzfOpts.Search = utils.SearchStringFromArgs(args)
project = fzfFindProject(fzfOpts) project = utils.FzfFindProject(fzfOpts)
} else { } else {
project, err = fzfProject(fzfOpts) project, err = utils.FzfProject(fzfOpts)
} }
if project == nil || err != nil { if project == nil || err != nil {
plog.Fatal("Failed to find project to delete aliases from", plog.Args( utils.Logger().Fatal("Failed to find project to delete aliases from", utils.Logger().Args(
"error", err, "error", err,
)) ))
} }
aliasStrings := projectCache.GetProjectAliasStrings(project) aliasStrings := utils.Cache().GetProjectAliasStrings(project)
deletionCandidates, err := pterm.DefaultInteractiveMultiselect. deletionCandidates, err := pterm.DefaultInteractiveMultiselect.
WithOptions(aliasStrings). WithOptions(aliasStrings).
Show() Show()
if err != nil || len(deletionCandidates) < 1 { if err != nil || len(deletionCandidates) < 1 {
plog.Fatal("Failed to find project to delete aliases from", plog.Args( utils.Logger().Fatal("Failed to find project to delete aliases from", utils.Logger().Args(
"error", err, "error", err,
)) ))
} }
@ -61,26 +62,25 @@ func runDeleteAliasCmd(cmd *cobra.Command, args []string) {
Show() Show()
if !confirm { if !confirm {
plog.Warn("Alias deletion cancelled") utils.Logger().Warn("Alias deletion cancelled")
continue continue
} }
plog.Info("Deleting alias", plog.Args( utils.Logger().Info("Deleting alias", utils.Logger().Args(
"project", project.String(), "project", project.String(),
"alias", a, "alias", a,
)) ))
projectCache.DeleteAlias(projectCache.GetAliasByName(a)) utils.Cache().DeleteAlias(utils.Cache().GetAliasByName(a))
} }
fmt.Println(projectCache.ProjectString(project)) fmt.Println(utils.Cache().ProjectString(project))
} }
func init() { func init() {
aliasCmd.AddCommand(aliasDeleteCmd)
aliasDeleteCmd.PersistentFlags().Int("projectID", 0, "Specify a project by ID") aliasDeleteCmd.PersistentFlags().Int("projectID", 0, "Specify a project by ID")
aliasDeleteCmd.RegisterFlagCompletionFunc("projectID", validProjectIdFunc) aliasDeleteCmd.RegisterFlagCompletionFunc("projectID", util.ValidProjectIdFunc)
viper.BindPFlag("alias.delete.projectID", aliasDeleteCmd.Flag("projectID")) viper.BindPFlag("alias.delete.projectID", aliasDeleteCmd.Flag("projectID"))
} }

View File

@ -1,4 +1,4 @@
package cmd package alias
import ( import (
"fmt" "fmt"
@ -6,6 +6,8 @@ import (
"github.com/pterm/pterm" "github.com/pterm/pterm"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
// listCmd represents the list command // listCmd represents the list command
@ -14,20 +16,24 @@ var aliasListCmd = &cobra.Command{
Aliases: []string{"dump", "show", "ls", "ll", "l"}, Aliases: []string{"dump", "show", "ls", "ll", "l"},
Short: "List Aliases", Short: "List Aliases",
PreRun: mustHaveAliases, PreRun: mustHaveAliases,
Long: aliasListCmdLong, Long: util.AliasListCmdLong,
Run: runListAliasCmd, Run: runListAliasCmd,
} }
func runListAliasCmd(cmd *cobra.Command, args []string) { func runListAliasCmd(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
pterm.DefaultBox. aliases := utils.Cache().AliasesByProjectString(remotes...)
printer := pterm.DefaultBox.
WithLeftPadding(5).WithRightPadding(5). WithLeftPadding(5).WithRightPadding(5).
WithBoxStyle(&pterm.Style{pterm.FgLightBlue}). WithBoxStyle(&pterm.Style{pterm.FgLightBlue}).
WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Aliases by Project"))). WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Aliases by Project")))
Print("\n" + projectCache.AliasesByProjectString(remotes...))
if len(aliases) < 1 {
printer.Print("\n" + "No Aliases Found")
} else {
printer.Print("\n" + aliases)
}
fmt.Print("\n\n") fmt.Print("\n\n")
} }
func init() {
aliasCmd.AddCommand(aliasListCmd)
}

View File

@ -1,106 +0,0 @@
package cmd
import (
"fmt"
"strings"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
)
var aliasAddCmd = &cobra.Command{
Use: "add",
Aliases: []string{"set", "a", "s"},
Short: "Add a project alias",
Args: cobra.ArbitraryArgs,
Long: aliasAddCmdLong,
Run: runAddAliasCmd,
}
func runAddAliasCmd(cmd *cobra.Command, args []string) {
var project *projects.Project
// Check by flag
if projectID := viper.GetInt(ViperAliasAddPID); projectID > 0 {
plog.Debug(fmt.Sprintf("Adding for inbound project ID %d", projectID))
project = projectCache.GetProjectByID(projectID)
}
// Check by arg
if len(args) > 0 {
project = fzfFindProject(&fzfProjectOpts{Ctx: cmd.Context(), Search: searchStringFromArgs(args)})
}
// Collect by fzf
if project == nil {
var err error
project, err = fzfProject(&fzfProjectOpts{Ctx: cmd.Context()})
if err != nil || project == nil {
plog.Fatal("No project to alias, nothing to do", plog.Args("error", err))
}
}
addNewAliases(project.ID)
}
func addNewAliases(projectID int) {
project := projectCache.GetProjectByID(projectID)
if project == nil {
plog.Error("Failed to find project to alias", plog.Args("projectID", projectID))
return
}
// Collect the aliases
aliases := promptAliasesForProject(project)
// Add aliases
for _, a := range aliases {
a = strings.Trim(a, " '\"%<>|`")
if a == "" {
continue
}
if err := projectCache.AddAlias(a, project.ID, project.Remote); err != nil {
plog.Debug("Skipping alias add", plog.Args(
"error", err,
"alias", a,
))
} else {
plog.Info("Successfully added alias to project", plog.Args(
"project", project.String(),
"alias", a,
))
}
}
}
func promptAliasesForProject(p *projects.Project) []string {
aliases := projectCache.GetProjectAliases(p)
if len(aliases) > 0 {
plog.Info("Adding aliases to project", plog.Args(
"project", p.String(),
"existingAliases", cache.ProjectAliasesString(aliases),
))
} else {
pterm.Info.Printfln("Adding aliases to %s", p.Name)
}
response, _ := pterm.DefaultInteractiveTextInput.
WithMultiLine(false).
WithDefaultValue(p.Path + " ").
Show("Enter aliases separated by space")
return strings.Split(response, " ")
}
func init() {
aliasCmd.AddCommand(aliasAddCmd)
aliasAddCmd.PersistentFlags().Int(FlagProjectID, 0, "Specify a project by ID")
aliasAddCmd.RegisterFlagCompletionFunc(FlagProjectID, validProjectIdFunc)
viper.BindPFlag(ViperAliasAddPID, aliasAddCmd.Flag(FlagProjectID))
}

View File

@ -1,38 +0,0 @@
package cmd
import (
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache"
)
var projectCache *cache.Cache
var cacheCmd = &cobra.Command{
Use: "cache",
Aliases: []string{"a", "ln"},
Short: "Manage Git project cache",
Long: cacheCmdLong,
}
func runCacheCmd(cmd *cobra.Command, args []string) {
initProjectCache(cmd, args)
projectCache.LockCache()
}
func postCacheCmd(cmd *cobra.Command, args []string) {
postProjectCache(cmd, args)
projectCache.UnlockCache()
}
func init() {
rootCmd.AddCommand(cacheCmd)
cacheCmd.PersistentFlags().Duration("ttl", 48*time.Hour,
"Duration before cache is re-built in go time.Duration format")
viper.BindPFlags(cacheCmd.Flags())
}

43
cmd/cache/cache.go vendored Normal file
View File

@ -0,0 +1,43 @@
package cache
import (
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
)
var CacheCmd = &cobra.Command{
Use: "cache",
Aliases: []string{"a", "ln"},
Short: "Manage Git project cache",
Long: util.CacheCmdLong,
}
var utils *util.Utils
func runCacheCmd(cmd *cobra.Command, args []string) {
utils = util.MustFromCtx(cmd.Context())
utils.InitProjectCache(cmd, args)
utils.Cache().LockCache()
}
func postCacheCmd(cmd *cobra.Command, args []string) {
utils.PostProjectCache(cmd, args)
utils.Cache().UnlockCache()
}
func init() {
CacheCmd.PersistentFlags().Duration("ttl", 48*time.Hour,
"Duration before cache is re-built in go time.Duration format")
viper.BindPFlags(CacheCmd.Flags())
CacheCmd.AddCommand(clearCmd)
CacheCmd.AddCommand(dumpCmd)
CacheCmd.AddCommand(loadCmd)
CacheCmd.AddCommand(unlockCmd)
}

View File

@ -1,4 +1,4 @@
package cmd package cache
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -21,11 +21,10 @@ var clearCmd = &cobra.Command{
func clearCache(cmd *cobra.Command, args []string) { func clearCache(cmd *cobra.Command, args []string) {
slog.Debug("Preparing to clear local cache") slog.Debug("Preparing to clear local cache")
projectCache.Clear(conf.Cache.Clear.ClearAliases) utils.Cache().Clear(utils.Config().Cache.Clear.ClearAliases)
} }
func init() { func init() {
cacheCmd.AddCommand(clearCmd)
clearCmd.Flags().Bool("clearAliases", false, "Will also clear aliases from the cache, use with caution") clearCmd.Flags().Bool("clearAliases", false, "Will also clear aliases from the cache, use with caution")
viper.BindPFlag("cache.clear.clearAliases", clearCmd.LocalFlags().Lookup("clearAliases")) viper.BindPFlag("cache.clear.clearAliases", clearCmd.LocalFlags().Lookup("clearAliases"))
} }

View File

@ -1,10 +1,12 @@
package cmd package cache
import ( import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var dumpCmd = &cobra.Command{ var dumpCmd = &cobra.Command{
@ -17,16 +19,15 @@ var dumpCmd = &cobra.Command{
} }
func runCacheDunpCmd(cmd *cobra.Command, args []string) { func runCacheDunpCmd(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
if conf.Dump.Full { if utils.Config().Dump.Full {
fmt.Println(projectCache.DumpString(true, searchStringFromArgs(args), remotes...)) fmt.Println(utils.Cache().DumpString(true, utils.SearchStringFromArgs(args), remotes...))
} else { } else {
plog.Info(projectCache.String()) utils.Logger().Info(utils.Cache().String())
} }
} }
func init() { func init() {
cacheCmd.AddCommand(dumpCmd)
dumpCmd.PersistentFlags().BoolP("full", "f", false, "Dumps entire cache") dumpCmd.PersistentFlags().BoolP("full", "f", false, "Dumps entire cache")
viper.BindPFlag("dump.full", dumpCmd.LocalFlags().Lookup("full")) viper.BindPFlag("dump.full", dumpCmd.LocalFlags().Lookup("full"))
} }

View File

@ -1,8 +1,10 @@
package cmd package cache
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var loadCmd = &cobra.Command{ var loadCmd = &cobra.Command{
@ -17,15 +19,13 @@ wants to find a new project.`,
} }
func loadCache(cmd *cobra.Command, args []string) { func loadCache(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
projectCache.Refresh(remotes...) utils.Cache().Refresh(remotes...)
} }
func init() { func init() {
cacheCmd.AddCommand(loadCmd) loadCmd.PersistentFlags().Bool(util.FlagOwnerOnly, true,
loadCmd.PersistentFlags().Bool(FlagOwnerOnly, true,
"Only load projects that you are owner of") "Only load projects that you are owner of")
viper.BindPFlag(ViperCacheLoadOwnerOnly, loadCmd.Flag(FlagOwnerOnly)) viper.BindPFlag(util.ViperCacheLoadOwnerOnly, loadCmd.Flag(util.FlagOwnerOnly))
} }

33
cmd/cache/cache_unlock.go vendored Normal file
View File

@ -0,0 +1,33 @@
package cache
import (
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
)
var unlockCmd = &cobra.Command{
Use: "unlock",
Short: "unlock Git project cache",
Long: `unlocks cache to display`,
Run: func(cmd *cobra.Command, args []string) {
utils = util.MustFromCtx(cmd.Context())
utils.InitProjectCache(cmd, args)
if viper.GetBool(util.ViperCacheUnlockForce) {
utils.Cache().UnlockCache()
} else if yes, _ := pterm.DefaultInteractiveConfirm.
WithDefaultValue(false).
Show("Are you sure you want to manually unlock?"); yes {
utils.Cache().UnlockCache()
} else {
utils.Logger().Error("You failed to confirm cache unlock")
}
},
}
func init() {
unlockCmd.PersistentFlags().BoolP(util.FlagCacheForce, "f", false, "force unlocks cache (don't ask)")
viper.BindPFlag(util.ViperCacheUnlockForce, unlockCmd.LocalFlags().Lookup(util.FlagCacheForce))
}

View File

@ -1,31 +0,0 @@
package cmd
import (
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var unlockCmd = &cobra.Command{
Use: "unlock",
Short: "unlock Git project cache",
Long: `unlocks cache to display`,
Run: func(cmd *cobra.Command, args []string) {
initProjectCache(cmd, args)
if viper.GetBool(ViperCacheUnlockForce) {
projectCache.UnlockCache()
} else if yes, _ := pterm.DefaultInteractiveConfirm.
WithDefaultValue(false).
Show("Are you sure you want to manually unlock?"); yes {
projectCache.UnlockCache()
} else {
plog.Error("You failed to confirm cache unlock")
}
},
}
func init() {
cacheCmd.AddCommand(unlockCmd)
unlockCmd.PersistentFlags().BoolP(FlagCacheForce, "f", false, "force unlocks cache (don't ask)")
viper.BindPFlag(ViperCacheUnlockForce, unlockCmd.LocalFlags().Lookup(FlagCacheForce))
}

View File

@ -1,16 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
)
var configCmd = &cobra.Command{
Use: "config",
Short: "Git Project Manager Configuration",
Aliases: []string{"conf"},
Long: configCmdLong,
}
func init() {
rootCmd.AddCommand(configCmd)
}

26
cmd/config/config.go Normal file
View File

@ -0,0 +1,26 @@
package cmd
import (
"github.com/spf13/cobra"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
)
var ConfigCmd = &cobra.Command{
Use: "config",
Short: "Git Project Manager Configuration",
Aliases: []string{"conf"},
Long: util.ConfigCmdLong,
PersistentPreRun: configCmdPreRun,
}
var utils *util.Utils
func configCmdPreRun(cmd *cobra.Command, args []string) {
utils = util.MustFromCtx(cmd.Context())
}
func init() {
ConfigCmd.AddCommand(configGenerateCmd)
ConfigCmd.AddCommand(configShowCmd)
}

View File

@ -10,6 +10,7 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/info" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/info"
) )
@ -18,7 +19,7 @@ var configGenerateCmd = &cobra.Command{
Use: "generate", Use: "generate",
Short: "Generate a default configuration", Short: "Generate a default configuration",
Aliases: []string{"gen", "new", "default"}, Aliases: []string{"gen", "new", "default"},
Long: configGenCmdLong, Long: util.ConfigGenCmdLong,
Run: runConfigGenerateCmd, Run: runConfigGenerateCmd,
} }
@ -27,34 +28,34 @@ func runConfigGenerateCmd(cmd *cobra.Command, args []string) {
if viper.ConfigFileUsed() != "" { if viper.ConfigFileUsed() != "" {
configFile = viper.ConfigFileUsed() configFile = viper.ConfigFileUsed()
} else { } else {
configFile = defConfigPath configFile = util.DefConfigPath
} }
plog.Debug("Using config file " + configFile) utils.Logger().Debug("Using config file " + configFile)
configFile, _ = resolvePath(configFile) configFile, _ = util.ResolvePath(configFile)
newConf := promptConfigSettings(&conf) newConf := promptConfigSettings(utils.Config())
if write, _ := cmd.Flags().GetBool(FlagWrite); write { if write, _ := cmd.Flags().GetBool(util.FlagWrite); write {
write, _ := pterm.DefaultInteractiveContinue. write, _ := pterm.DefaultInteractiveContinue.
WithDefaultText("Really write config file?"). WithDefaultText("Really write config file?").
WithOptions([]string{"yes", "no"}). WithOptions([]string{"yes", "no"}).
Show() Show()
if write != "yes" { if write != "yes" {
plog.Fatal("Aborting config file write") utils.Logger().Fatal("Aborting config file write")
} }
writeConfigFile(newConf, configFile) writeConfigFile(newConf, configFile)
plog.Info("Wrote config to file", utils.Logger().Info("Wrote config to file",
plog.Args("file", configFile)) utils.Logger().Args("file", configFile))
} else { } else {
var c bytes.Buffer var c bytes.Buffer
enc := yaml.NewEncoder(&c) enc := yaml.NewEncoder(&c)
enc.SetIndent(2) enc.SetIndent(2)
enc.Encode(newConf) enc.Encode(newConf)
plog.Info("Suggest running with --write or redirect (> " + configFile + ")") utils.Logger().Info("Suggest running with --write or redirect (> " + configFile + ")")
fmt.Print(c.String()) fmt.Print(c.String())
} }
} }
@ -62,11 +63,11 @@ func runConfigGenerateCmd(cmd *cobra.Command, args []string) {
func writeConfigFile(c *config.Config, path string) { func writeConfigFile(c *config.Config, path string) {
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o640) file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o640)
if err != nil { if err != nil {
plog.Error("Failed to prepare config for writing", plog.Args("error", err)) utils.Logger().Error("Failed to prepare config for writing", utils.Logger().Args("error", err))
} }
enc := yaml.NewEncoder(file) enc := yaml.NewEncoder(file)
if err := enc.Encode(c); err != nil { if err := enc.Encode(c); err != nil {
plog.Fatal("Failed writing config file", plog.Args("file", path)) utils.Logger().Fatal("Failed writing config file", utils.Logger().Args("file", path))
} }
} }
@ -149,7 +150,6 @@ func promptConfigSettings(c *config.Config) *config.Config {
} }
func init() { func init() {
configCmd.AddCommand(configGenerateCmd) configGenerateCmd.PersistentFlags().Bool(util.FlagPrompt, false, "Prompt for settings")
configGenerateCmd.PersistentFlags().Bool(FlagPrompt, false, "Prompt for settings") configGenerateCmd.PersistentFlags().Bool(util.FlagWrite, false, "Write config to file")
configGenerateCmd.PersistentFlags().Bool(FlagWrite, false, "Write config to file")
} }

View File

@ -6,6 +6,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var configShowCmd = &cobra.Command{ var configShowCmd = &cobra.Command{
@ -16,16 +18,16 @@ var configShowCmd = &cobra.Command{
} }
func runConfigShowCmd(cmd *cobra.Command, args []string) { func runConfigShowCmd(cmd *cobra.Command, args []string) {
c := conf c := *utils.Config()
showSensitive, _ := cmd.Flags().GetBool(FlagSensitive) showSensitive, _ := cmd.Flags().GetBool(util.FlagSensitive)
if !showSensitive { if !showSensitive {
plog.Info("Sensitive fields hidden, do not use unreviewed as config") utils.Logger().Info("Sensitive fields hidden, do not use unreviewed as config")
for _, r := range c.Remotes { for _, r := range c.Remotes {
r.Token = strings.Repeat("*", len(r.Token)) r.Token = strings.Repeat("*", len(r.Token))
} }
} else { } else {
plog.Warn("Displaying sensitive fields!") utils.Logger().Warn("Displaying sensitive fields!")
} }
enc := yaml.NewEncoder(os.Stdout) enc := yaml.NewEncoder(os.Stdout)
@ -34,6 +36,5 @@ func runConfigShowCmd(cmd *cobra.Command, args []string) {
} }
func init() { func init() {
configCmd.AddCommand(configShowCmd) configShowCmd.Flags().BoolP(util.FlagSensitive, "s", false, "Set to show sensitive fields")
configShowCmd.Flags().BoolP(FlagSensitive, "s", false, "Set to show sensitive fields")
} }

View File

@ -5,6 +5,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/cobra/doc" "github.com/spf13/cobra/doc"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var docsCmd = &cobra.Command{ var docsCmd = &cobra.Command{
@ -17,9 +19,9 @@ var docsCmd = &cobra.Command{
} }
func runDocsCmd(cmd *cobra.Command, args []string) { func runDocsCmd(cmd *cobra.Command, args []string) {
outDir, err := cmd.Flags().GetString(FlagDocsPath) outDir, err := cmd.Flags().GetString(util.FlagDocsPath)
if err != nil { if err != nil {
plog.Error("missing docs path") utils.Logger().Error("missing docs path")
} }
prepareDocsDir(cmd, outDir) prepareDocsDir(cmd, outDir)
@ -35,10 +37,10 @@ func runDocsCmd(cmd *cobra.Command, args []string) {
case "yaml": case "yaml":
err = doc.GenYamlTree(cmd.Root(), outDir) err = doc.GenYamlTree(cmd.Root(), outDir)
default: default:
plog.Error("invalid docs type", plog.Args("type", args[0])) utils.Logger().Error("invalid docs type", utils.Logger().Args("type", args[0]))
} }
plog.Info("docs generation complete", plog.Args( utils.Logger().Info("docs generation complete", utils.Logger().Args(
"type", args[0], "docsDir", outDir, "err", err)) "type", args[0], "docsDir", outDir, "err", err))
} }
@ -47,14 +49,14 @@ func prepareDocsDir(_ *cobra.Command, outDir string) {
if err != nil { if err != nil {
err = os.Mkdir(outDir, 0o755) err = os.Mkdir(outDir, 0o755)
if err != nil { if err != nil {
plog.Error("failed to create docs path", plog.Args( utils.Logger().Error("failed to create docs path", utils.Logger().Args(
"error", err.Error(), "docsDir", outDir)) "error", err.Error(), "docsDir", outDir))
} }
} }
} }
func init() { func init() {
docsCmd.PersistentFlags().StringP(FlagDocsPath, "d", "./docs", "specify output directory for documentation") docsCmd.PersistentFlags().StringP(util.FlagDocsPath, "d", "./docs", "specify output directory for documentation")
rootCmd.AddCommand(docsCmd) rootCmd.AddCommand(docsCmd)
} }

View File

@ -1,67 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
)
var projectCmd = &cobra.Command{
Use: "project [fuzzy alias search]",
Short: "Use a Git project",
Aliases: []string{"proj", "projects", "p"},
Args: cobra.MaximumNArgs(1),
ArgAliases: []string{"alias"},
Long: projCmdLong,
PersistentPreRun: initProjectCmd,
PersistentPostRun: postProjectCmd,
// Run: projectGoCmdRun,
}
func getProject(args []string) *projects.Project {
remotes := viper.GetStringSlice(FlagRemote)
fzfOpts := &fzfProjectOpts{
Ctx: rootCmd.Context(),
Search: searchStringFromArgs(args),
Remotes: remotes,
}
project := fzfFindProject(fzfOpts)
if project == nil {
plog.Fatal("Failed to find a project, nothing to do")
} else {
plog.Debug("Houston, we have a project", plog.Args(
"project", project.String(),
"aliases", cache.ProjectAliasesString(
projectCache.GetProjectAliases(project)),
))
}
if len(projectCache.GetProjectAliases(project)) == 0 {
plog.Info("New project, set aliases or press enter for default")
addNewAliases(project.ID)
}
return project
}
func initProjectCmd(cmd *cobra.Command, args []string) {
initProjectCache(cmd, args)
mustHaveProjects(cmd, args)
}
func postProjectCmd(cmd *cobra.Command, args []string) {
postProjectCache(cmd, args)
}
func init() {
rootCmd.AddCommand(projectCmd)
}
func mustHaveProjects(cmd *cobra.Command, _ []string) {
if len(projectCache.Projects) == 0 {
plog.Fatal("No projects to " + cmd.Name() + ", try running cache load")
}
}

66
cmd/project/project.go Normal file
View File

@ -0,0 +1,66 @@
package project
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/alias"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
)
var ProjectCmd = &cobra.Command{
Use: "project [fuzzy alias search]",
Short: "Use a Git project",
Aliases: []string{"proj", "projects", "p"},
Args: cobra.MaximumNArgs(1),
ArgAliases: []string{"alias"},
Long: util.ProjCmdLong,
PersistentPreRun: projectCmdPreRun,
PersistentPostRun: util.PostProjectCmd,
// Run: projectGoCmdRun,
}
var utils *util.Utils
func projectCmdPreRun(cmd *cobra.Command, args []string) {
utils = util.MustFromCtx(cmd.Context())
util.InitProjects(cmd, args)
}
func getProject(cmd *cobra.Command, args []string) *projects.Project {
remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(),
Search: utils.SearchStringFromArgs(args),
Remotes: remotes,
}
project := utils.FzfFindProject(fzfOpts)
if project == nil {
utils.Logger().Fatal("Failed to find a project, nothing to do")
} else {
utils.Logger().Debug("Houston, we have a project", utils.Logger().Args(
"project", project.String(),
"aliases", cache.ProjectAliasesString(
utils.Cache().GetProjectAliases(project)),
))
}
if len(utils.Cache().GetProjectAliases(project)) == 0 {
utils.Logger().Info("New project, set aliases or press enter for default")
alias.AddNewAliases(cmd, project.GetID())
}
return project
}
func init() {
ProjectCmd.AddCommand(projectAddCmd)
ProjectCmd.AddCommand(projectGoCmd)
ProjectCmd.AddCommand(projectListCmd)
ProjectCmd.AddCommand(projectOpenCmd)
ProjectCmd.AddCommand(projectRunCmd)
ProjectCmd.AddCommand(projectShowCmd)
}

View File

@ -1,21 +1,19 @@
package cmd package project
import ( import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var projectAddCmd = &cobra.Command{ var projectAddCmd = &cobra.Command{
Use: "add", Use: "add",
Short: "Add a new Git project", Short: "Add a new Git project",
Aliases: []string{"a", "alias"}, Aliases: []string{"a", "alias"},
Long: projAddCmdLong, Long: util.ProjAddCmdLong,
Run: projectAddCmdRun, Run: projectAddCmdRun,
} }
func projectAddCmdRun(cmd *cobra.Command, args []string) { func projectAddCmdRun(cmd *cobra.Command, args []string) {
getProject(args) getProject(cmd, args)
}
func init() {
projectCmd.AddCommand(projectAddCmd)
} }

View File

@ -1,4 +1,4 @@
package cmd package project
import ( import (
"fmt" "fmt"
@ -7,6 +7,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var projectGoCmd = &cobra.Command{ var projectGoCmd = &cobra.Command{
@ -15,30 +17,30 @@ var projectGoCmd = &cobra.Command{
Aliases: []string{"goto", "cd"}, Aliases: []string{"goto", "cd"},
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
ArgAliases: []string{"project"}, ArgAliases: []string{"project"},
ValidArgsFunction: validAliasesFunc, ValidArgsFunction: util.ValidAliasesFunc,
Long: projGoCmdLong, Long: util.ProjGoCmdLong,
Run: projectGoCmdRun, Run: projectGoCmdRun,
} }
func projectGoCmdRun(cmd *cobra.Command, args []string) { func projectGoCmdRun(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &fzfProjectOpts{ fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(), Ctx: cmd.Context(),
Search: searchStringFromArgs(args), Search: utils.SearchStringFromArgs(args),
MustHaveAlias: true, MustHaveAlias: true,
Remotes: remotes, Remotes: remotes,
} }
project := fzfSearchProjectAliases(fzfOpts) project := utils.FzfSearchProjectAliases(fzfOpts)
if project == nil { if project == nil {
plog.Fatal("No project selected, nowhere to go") utils.Logger().Fatal("No project selected, nowhere to go")
} }
projectCache.GoTo(project) utils.Cache().GoTo(project)
project.SetRepo(projectCache.OpenProject(cmd.Context(), project)) project.SetRepo(utils.Cache().OpenProject(cmd.Context(), project))
plog.Debug("Project ready", plog.Args( utils.Logger().Debug("Project ready", utils.Logger().Args(
"path", projectCache.GetProjectPath(project), "path", utils.Cache().GetProjectPath(project),
"project", project, "project", project,
)) ))
@ -46,10 +48,6 @@ func projectGoCmdRun(cmd *cobra.Command, args []string) {
// This should be read by any source command, for instance // This should be read by any source command, for instance
// `cd "$(git-project-manager projects cd somealias)"` // `cd "$(git-project-manager projects cd somealias)"`
fmt.Println(projectCache.GetProjectPath(project)) fmt.Println(utils.Cache().GetProjectPath(project))
exec.Command("cd", projectCache.GetProjectPath(project)).Run() exec.Command("cd", utils.Cache().GetProjectPath(project)).Run()
}
func init() {
projectCmd.AddCommand(projectGoCmd)
} }

View File

@ -0,0 +1,28 @@
package project
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
)
var projectListCmd = &cobra.Command{
Use: "list",
Short: "List Git Projects",
Aliases: []string{"ls", "l"},
Long: util.ProjListCmdLong,
Run: projectListCmdRun,
}
func projectListCmdRun(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(util.FlagRemote)
fmt.Println(utils.Cache().DumpString(viper.GetBool(util.ViperProjectListAll), utils.SearchStringFromArgs(args), remotes...))
}
func init() {
projectListCmd.PersistentFlags().Bool(util.FlagAll, false, "List all, not just cloned locally")
viper.BindPFlag(util.ViperProjectListAll, projectListCmd.Flag(util.FlagAll))
}

View File

@ -1,6 +1,7 @@
package cmd package project
import ( import (
"context"
"errors" "errors"
"os" "os"
"os/exec" "os/exec"
@ -8,6 +9,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var projectOpenCmd = &cobra.Command{ var projectOpenCmd = &cobra.Command{
@ -15,8 +18,8 @@ var projectOpenCmd = &cobra.Command{
Short: "Open project in your IDE", Short: "Open project in your IDE",
Aliases: []string{"goto", "cd"}, Aliases: []string{"goto", "cd"},
Args: cobra.OnlyValidArgs, Args: cobra.OnlyValidArgs,
ValidArgsFunction: validAliasesFunc, ValidArgsFunction: util.ValidAliasesFunc,
Long: projOpenCmdLong, Long: util.ProjOpenCmdLong,
Run: projectOpenCmdRun, Run: projectOpenCmdRun,
} }
@ -48,47 +51,47 @@ func projectOpenCmdRun(cmd *cobra.Command, args []string) {
// Find an editor // Find an editor
editor := findEditor() editor := findEditor()
if editor == "" { if editor == "" {
plog.Fatal("No usable editor found") utils.Logger().Fatal("No usable editor found")
} }
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &fzfProjectOpts{ fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(), Ctx: cmd.Context(),
Search: searchStringFromArgs(args), Search: utils.SearchStringFromArgs(args),
Remotes: remotes, Remotes: remotes,
} }
project := fzfCwdOrSearchProjectAliases(fzfOpts) project := utils.FzfCwdOrSearchProjectAliases(fzfOpts)
if project == nil { if project == nil {
plog.Fatal("No project to open, nothing to do") utils.Logger().Fatal("No project to open, nothing to do")
} }
// Check the project // Check the project
path := projectCache.GetProjectPath(project) path := utils.Cache().GetProjectPath(project)
if _, err := os.Stat(path); err != nil { if _, err := os.Stat(path); err != nil {
plog.Fatal("Unable to open project", plog.Args("error", err)) utils.Logger().Fatal("Unable to open project", utils.Logger().Args("error", err))
} }
// Open the project with the editor // Open the project with the editor
file := getEntrypointFile(path) file := getEntrypointFile(path)
openEditor(editor, path+"/"+file) openEditor(cmd.Context(), editor, path+"/"+file)
} }
func openEditor(editor string, path string) { func openEditor(ctx context.Context, editor string, path string) {
// Compile arguments // Compile arguments
args := make([]string, 0, 1) args := make([]string, 0, 1)
if conf.Editor.OpenFlags != "" { if utils.Config().Editor.OpenFlags != "" {
args = append(args, conf.Editor.OpenFlags) args = append(args, utils.Config().Editor.OpenFlags)
} }
args = append(args, path) args = append(args, path)
// Launch editor // Launch editor
cmd := exec.CommandContext(rootCmd.Context(), editor, args...) cmd := exec.CommandContext(ctx, editor, args...)
cmd.Dir = filepath.Dir(path) cmd.Dir = filepath.Dir(path)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
plog.Error("Failed to open project", plog.Args( utils.Logger().Error("Failed to open project", utils.Logger().Args(
"error", err.Error(), "error", err.Error(),
"command", cmd.String(), "command", cmd.String(),
)) ))
@ -100,7 +103,7 @@ func openEditor(editor string, path string) {
func getEntrypointFile(projectPath string) string { func getEntrypointFile(projectPath string) string {
if err := os.Chdir(projectPath); err != nil { if err := os.Chdir(projectPath); err != nil {
return "" return ""
} else if conf.Editor.OpenDirectory { } else if utils.Config().Editor.OpenDirectory {
return "." return "."
} }
@ -119,10 +122,10 @@ func findEditor() string {
var err error var err error
// First try configured editor // First try configured editor
if conf.Editor.Binary != "" { if utils.Config().Editor.Binary != "" {
editor, err = getEditor(conf.Editor.Binary) editor, err = getEditor(utils.Config().Editor.Binary)
if err != nil || editor == "" { if err != nil || editor == "" {
plog.Error("Configured editor is not usable, finding others", plog.Args( utils.Logger().Error("Configured editor is not usable, finding others", utils.Logger().Args(
"error", err, "error", err,
)) ))
} }
@ -130,7 +133,7 @@ func findEditor() string {
// Then try to find a known editor // Then try to find a known editor
if editor == "" || err != nil { if editor == "" || err != nil {
conf.Editor.OpenFlags = "" utils.Config().Editor.OpenFlags = ""
for _, e := range knownEditors { for _, e := range knownEditors {
path, err := getEditor(e) path, err := getEditor(e)
if err == nil && path != "" { if err == nil && path != "" {
@ -140,9 +143,9 @@ func findEditor() string {
} }
} }
plog.Debug("Editor found for open", plog.Args( utils.Logger().Debug("Editor found for open", utils.Logger().Args(
"editor", editor, "editor", editor,
"command", editor+" "+conf.Editor.OpenFlags, "command", editor+" "+utils.Config().Editor.OpenFlags,
)) ))
return editor return editor
@ -164,7 +167,7 @@ func getEditorPath(editor string) (string, error) {
editor, _ = exec.LookPath(editor) editor, _ = exec.LookPath(editor)
} }
return resolvePath(editor) return util.ResolvePath(editor)
} }
func isEditorExecutable(editor string) bool { func isEditorExecutable(editor string) bool {
@ -180,7 +183,6 @@ func isEditorExecutable(editor string) bool {
} }
func init() { func init() {
projectCmd.AddCommand(projectOpenCmd)
projectOpenCmd.PersistentFlags().String("displayName", "", "Set display name of editor (meant for config file)") projectOpenCmd.PersistentFlags().String("displayName", "", "Set display name of editor (meant for config file)")
projectOpenCmd.PersistentFlags().String("binary", "", "Path to editor binary") projectOpenCmd.PersistentFlags().String("binary", "", "Path to editor binary")
projectOpenCmd.PersistentFlags().String("openFlags", "", "Optional flags when opening project (e.g. --reuse-window)") projectOpenCmd.PersistentFlags().String("openFlags", "", "Optional flags when opening project (e.g. --reuse-window)")

View File

@ -1,4 +1,4 @@
package cmd package project
import ( import (
"fmt" "fmt"
@ -7,35 +7,37 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
) )
var projectRunCmd = &cobra.Command{ var projectRunCmd = &cobra.Command{
Use: "run", Use: "run",
Short: "Run the project (e.g. go run .)", Short: "Run the project (e.g. go run .)",
Aliases: []string{"exec", "r"}, Aliases: []string{"exec", "r"},
Long: projRunCmdLong, Long: util.ProjRunCmdLong,
Run: projectRunCmdRun, Run: projectRunCmdRun,
} }
func projectRunCmdRun(cmd *cobra.Command, args []string) { func projectRunCmdRun(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &fzfProjectOpts{ fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(), Ctx: cmd.Context(),
Search: searchStringFromArgs(args), Search: utils.SearchStringFromArgs(args),
Remotes: remotes, Remotes: remotes,
} }
project := fzfCwdOrSearchProjectAliases(fzfOpts) project := utils.FzfCwdOrSearchProjectAliases(fzfOpts)
if project == nil { if project == nil {
plog.Fatal("No project selected, nothing to open") utils.Logger().Fatal("No project selected, nothing to open")
} }
lang := project.GetLanguage() lang := project.GetLanguage()
if lang == nil { if lang == nil {
plog.Fatal("Git remote isn't sure what language this project is... can't run.") utils.Logger().Fatal("Git remote isn't sure what language this project is... can't run.")
} }
plog.Debug(fmt.Sprintf("Project is written in %s, %.2f%% coverage", lang.Name, lang.Percentage)) utils.Logger().Debug(fmt.Sprintf("Project is written in %s, %.2f%% coverage", lang.Name, lang.Percentage))
switch lang.Name { switch lang.Name {
case "Go": case "Go":
@ -50,5 +52,5 @@ func projectRunCmdRun(cmd *cobra.Command, args []string) {
} }
func init() { func init() {
projectCmd.AddCommand(projectRunCmd) ProjectCmd.AddCommand(projectRunCmd)
} }

View File

@ -1,4 +1,4 @@
package cmd package project
import ( import (
"fmt" "fmt"
@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
) )
@ -16,7 +17,7 @@ var projectShowCmd = &cobra.Command{
Short: "Show detail for a Git project", Short: "Show detail for a Git project",
Aliases: []string{"cat", "s"}, Aliases: []string{"cat", "s"},
Args: cobra.ArbitraryArgs, Args: cobra.ArbitraryArgs,
Long: projShowCmdLong, Long: util.ProjShowCmdLong,
Run: projectShowCmdRun, Run: projectShowCmdRun,
} }
@ -24,24 +25,24 @@ func projectShowCmdRun(cmd *cobra.Command, args []string) {
var project *projects.Project var project *projects.Project
var inCwd bool var inCwd bool
remotes := viper.GetStringSlice(FlagRemote) remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &fzfProjectOpts{ fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(), Ctx: cmd.Context(),
Search: searchStringFromArgs(args), Search: utils.SearchStringFromArgs(args),
Remotes: remotes, Remotes: remotes,
} }
// Try to find project from current directory // Try to find project from current directory
if viper.GetBool(ViperProjectShowCurrent) { if viper.GetBool(util.ViperProjectShowCurrent) {
var err error var err error
project, err = projectCache.GetProjectFromCwd() project, err = utils.Cache().GetProjectFromCwd()
if err != nil { if err != nil {
// Not an error because we're still going to try to find a project // Not an error because we're still going to try to find a project
plog.Warn("Failed to get project from current directory", plog.Args( utils.Logger().Warn("Failed to get project from current directory", utils.Logger().Args(
"error", err, "error", err,
)) ))
} else if project == nil { } else if project == nil {
plog.Warn("Failed to use --current flag, project not found in current path") utils.Logger().Warn("Failed to use --current flag, project not found in current path")
} else { } else {
inCwd = true inCwd = true
} }
@ -49,15 +50,15 @@ func projectShowCmdRun(cmd *cobra.Command, args []string) {
// Otherwise find from the given search string // Otherwise find from the given search string
if project == nil { if project == nil {
project = fzfFindProject(fzfOpts) project = utils.FzfFindProject(fzfOpts)
} }
// Do a full fuzzy find if all else fails // Do a full fuzzy find if all else fails
if project == nil { if project == nil {
var err error var err error
project, err = fzfProject(fzfOpts) project, err = utils.FzfProject(fzfOpts)
if err != nil || project == nil { if err != nil || project == nil {
plog.Fatal("Failed to find project, nothing to show", plog.Args( utils.Logger().Fatal("Failed to find project, nothing to show", utils.Logger().Args(
"error", err, "error", err,
)) ))
} }
@ -68,17 +69,17 @@ func projectShowCmdRun(cmd *cobra.Command, args []string) {
WithLeftPadding(5).WithRightPadding(5). WithLeftPadding(5).WithRightPadding(5).
WithBoxStyle(&pterm.Style{pterm.FgLightBlue}). WithBoxStyle(&pterm.Style{pterm.FgLightBlue}).
WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Project Information"))). WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Project Information"))).
Println(projectCache.ProjectString(project)) Println(utils.Cache().ProjectString(project))
fmt.Println() fmt.Println()
if inCwd { if inCwd {
project.SetRepo(projectCache.OpenProject(cmd.Context(), project)) project.SetRepo(utils.Cache().OpenProject(cmd.Context(), project))
fmt.Fprintln(os.Stderr, project.GetGitInfo()+"\n") fmt.Fprintln(os.Stderr, project.GetGitInfo()+"\n")
} }
} }
func init() { func init() {
projectCmd.AddCommand(projectShowCmd) ProjectCmd.AddCommand(projectShowCmd)
projectShowCmd.PersistentFlags().Bool(FlagCurrent, false, "Use project in CWD rather than fuzzy find") projectShowCmd.PersistentFlags().Bool(util.FlagCurrent, false, "Use project in CWD rather than fuzzy find")
viper.BindPFlag(ViperProjectShowCurrent, projectShowCmd.Flag(FlagCurrent)) viper.BindPFlag(util.ViperProjectShowCurrent, projectShowCmd.Flag(util.FlagCurrent))
} }

View File

@ -1,27 +0,0 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var projectListCmd = &cobra.Command{
Use: "list",
Short: "List Git Projects",
Aliases: []string{"ls", "l"},
Long: projListCmdLong,
Run: projectListCmdRun,
}
func projectListCmdRun(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(FlagRemote)
fmt.Println(projectCache.DumpString(viper.GetBool(ViperProjectListAll), searchStringFromArgs(args), remotes...))
}
func init() {
projectCmd.AddCommand(projectListCmd)
projectListCmd.PersistentFlags().Bool(FlagAll, false, "List all, not just cloned locally")
viper.BindPFlag(ViperProjectListAll, projectListCmd.Flag(FlagAll))
}

View File

@ -6,34 +6,40 @@ import (
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime/debug"
"strings" "strings"
"github.com/pterm/pterm" "github.com/pterm/pterm"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/alias"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/cache"
conf "gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/config"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/project"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config"
) )
var (
conf config.Config
plog *pterm.Logger
)
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "git-project-manager", Use: "git-project-manager",
Aliases: []string{"gpm"}, Aliases: []string{"gpm"},
Short: "Find and use Git projects locally", Short: "Find and use Git projects locally",
Long: rootCmdLong, Long: util.RootCmdLong,
PersistentPreRun: initRootCmd, PersistentPreRun: initRootCmd,
} }
var configExemptCommands = regexp.MustCompile(`^(doc|conf)`) var (
Version = "development"
configExemptCommands = regexp.MustCompile(`^(doc|conf)`)
utils *util.Utils
)
// Hook traversal is enabled, so this will be run for all // Hook traversal is enabled, so this will be run for all
// sub-commands regardless of their registered pre-hooks // sub-commands regardless of their registered pre-hooks
func initRootCmd(cmd *cobra.Command, args []string) { func initRootCmd(cmd *cobra.Command, args []string) {
initProjectPath(cmd, args) cmd.SetContext(util.AddToCtx(cmd.Context(), utils))
utils.InitProjectPath(cmd, args)
} }
// Execute adds all child commands to the root command and sets flags appropriately. // Execute adds all child commands to the root command and sets flags appropriately.
@ -51,73 +57,95 @@ func Execute() {
func init() { func init() {
cobra.EnableTraverseRunHooks = true cobra.EnableTraverseRunHooks = true
cobra.OnInitialize(initConfig)
utils = &util.Utils{}
cobra.OnInitialize(getInitConfigFunc(utils))
// Global flags // Global flags
rootCmd.PersistentFlags().String(FlagConfig, "", rootCmd.PersistentFlags().String(util.FlagConfig, "",
"config file (default is "+defConfigPath+")") "config file (default is "+util.DefConfigPath+")")
rootCmd.PersistentFlags().String(FlagPath, "", rootCmd.PersistentFlags().String(util.FlagPath, "",
"Sets a path for local clones of projects") "Sets a path for local clones of projects")
rootCmd.PersistentFlags().String(FlagLogLevel, defLogLevel, rootCmd.PersistentFlags().String(util.FlagLogLevel, util.DefLogLevel,
"Default log level -- info, warn, error, debug") "Default log level -- info, warn, error, debug")
rootCmd.PersistentFlags().StringSlice(FlagRemote, []string{}, rootCmd.PersistentFlags().StringSlice(util.FlagRemote, []string{},
"Specify remotes by host for any sub-command. Provide multiple times or comma delimited.") "Specify remotes by host for any sub-command. Provide multiple times or comma delimited.")
// Flag autocompletion
rootCmd.RegisterFlagCompletionFunc(FlagLogLevel, validLogLevelsFunc)
rootCmd.RegisterFlagCompletionFunc(FlagRemote, validRemotesFunc)
viper.BindPFlags(rootCmd.PersistentFlags()) viper.BindPFlags(rootCmd.PersistentFlags())
// Flag autocompletion
rootCmd.RegisterFlagCompletionFunc(util.FlagLogLevel, util.ValidLogLevelsFunc)
rootCmd.RegisterFlagCompletionFunc(util.FlagRemote, util.ValidRemotesFunc)
// Subcommands
rootCmd.AddCommand(alias.AliasCmd)
rootCmd.AddCommand(cache.CacheCmd)
rootCmd.AddCommand(conf.ConfigCmd)
rootCmd.AddCommand(project.ProjectCmd)
// Version
rootCmd.Version = getVersion()
} }
// initConfig reads in config file and ENV variables if set. // initConfig reads in config file and ENV variables if set.
func initConfig() { func getInitConfigFunc(utils *util.Utils) func() {
cfgFile := viper.GetString(FlagConfig) return func() {
if cfgFile != "" { cfgFile := viper.GetString(util.FlagConfig)
// Use config file from the flag. if cfgFile != "" {
viper.SetConfigFile(cfgFile) // Use config file from the flag.
} else { viper.SetConfigFile(cfgFile)
// Find home directory. } else {
home, err := os.UserHomeDir() // Find home directory.
cobra.CheckErr(err) home, err := os.UserHomeDir()
cobra.CheckErr(err)
// Search config in home directory with name ".git-project-manager" (without extension). // Search config in home directory with name ".git-project-manager" (without extension).
configPath := filepath.Join(home, ".config") configPath := filepath.Join(home, ".config")
viper.AddConfigPath(configPath) viper.AddConfigPath(configPath)
viper.SetConfigType("yaml") viper.SetConfigType("yaml")
viper.SetConfigName(getConfigName(configPath)) viper.SetConfigName(util.GetConfigName(configPath))
}
viper.AutomaticEnv()
viper.ReadInConfig()
// Configure pretty logger
plog := pterm.DefaultLogger.
WithLevel(getPtermLogLevel(viper.GetString(util.FlagLogLevel))).
WithWriter(os.Stderr)
if plog.Level == pterm.LogLevelDebug {
pterm.EnableDebugMessages()
}
utils.SetLogger(plog)
// Load into struct to not be so darn pythonic, retrieving
// settings by untyped string "name"
conf := config.NewConfig()
if err := viper.Unmarshal(&conf); err != nil {
plog.Fatal("Failed loading config", plog.Args("error", err))
}
// Perform config updates
if err := conf.Prepare(); err != nil {
plog.Fatal("failed to parse clone timeout", plog.Args("error", err))
}
utils.SetConfig(conf)
if len(os.Args) > 0 && configExemptCommands.Match([]byte(os.Args[1])) {
plog.Debug("Permitting missing config for config sub-command")
return
} else if conf.ProjectPath == "" {
plog.Fatal("Minimal configuration missing, must have projectPath", plog.Args(
"do",
"Try running `git-project-manager config default > "+util.DefConfigPath,
))
}
checkConfigPerms(viper.ConfigFileUsed()) // Abort on world-readable config
utils.Logger().Debug("Configuration loaded", plog.Args("conf", conf))
} }
viper.AutomaticEnv()
viper.ReadInConfig()
// Configure pretty logger
plog = pterm.DefaultLogger.
WithLevel(getPtermLogLevel(viper.GetString(FlagLogLevel))).
WithWriter(os.Stderr)
if plog.Level == pterm.LogLevelDebug {
pterm.EnableDebugMessages()
}
// Load into struct to not be so darn pythonic, retrieving
// settings by untyped string "name"
if err := viper.Unmarshal(&conf); err != nil {
plog.Error("Failed loading config", plog.Args("err", err))
}
if len(os.Args) > 0 && configExemptCommands.Match([]byte(os.Args[1])) {
plog.Debug("Permitting missing config for config sub-command")
return
} else if conf.ProjectPath == "" {
plog.Fatal("Minimal configuration missing, must have projectPath", plog.Args(
"do",
"Try running `git-project-manager config default > "+defConfigPath,
))
}
checkConfigPerms(viper.ConfigFileUsed()) // Abort on world-readable config
plog.Debug("Configuration loaded", plog.Args("conf", conf))
} }
func getPtermLogLevel(level string) pterm.LogLevel { func getPtermLogLevel(level string) pterm.LogLevel {
@ -141,12 +169,19 @@ func getPtermLogLevel(level string) pterm.LogLevel {
func checkConfigPerms(file string) { func checkConfigPerms(file string) {
stat, err := os.Stat(file) stat, err := os.Stat(file)
if err != nil { if err != nil {
plog.Error("Failure reading configuration", plog.Args("err", err)) utils.Logger().Error("Failure reading configuration", utils.Logger().Args("err", err))
return return
} }
if stat.Mode().Perm()&0o004 == 0o004 { if stat.Mode().Perm()&0o004 == 0o004 {
plog.Error("Configuration is world-readable. Recomment 0400.", utils.Logger().Error("Configuration is world-readable. Recomment 0400.",
plog.Args("mode", stat.Mode().String())) utils.Logger().Args("mode", stat.Mode().String()))
os.Exit(1) os.Exit(1)
} }
} }
func getVersion() string {
if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "(devel)" {
return info.Main.Version
}
return Version
}

121
cmd/util/util.go Normal file
View File

@ -0,0 +1,121 @@
// Common utilities used by various subcommands
package util
import (
"context"
"errors"
"sync"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config"
"github.com/pterm/pterm"
)
type Utils struct {
ctx context.Context
config *config.Config
logger *pterm.Logger
cache *cache.Cache
sync.RWMutex
}
type UtilOpts struct {
Ctx context.Context
Config *config.Config
Logger *pterm.Logger
Cache *cache.Cache
}
type utilsCtxKey uint8
const UtilsKey utilsCtxKey = iota
func AddToCtx(ctx context.Context, u *Utils) context.Context {
return context.WithValue(ctx, UtilsKey, u)
}
func MustFromCtx(ctx context.Context) *Utils {
utils, err := FromCtx(ctx)
if err != nil {
panic(err)
}
return utils
}
func FromCtx(ctx context.Context) (*Utils, error) {
utils, avail := ctx.Value(UtilsKey).(*Utils)
if !avail {
return nil, errors.New("invalid util in context")
} else if utils == nil {
return nil, errors.New("util never set in context")
}
return utils, nil
}
func (u *Utils) Init(opts *UtilOpts) {
u.ctx = opts.Ctx
u.SetConfig(opts.Config)
u.SetLogger(opts.Logger)
u.SetCache(opts.Cache)
}
func (u *Utils) SetCache(c *cache.Cache) {
if c == nil {
return
}
u.Lock()
defer u.Unlock()
u.cache = c
}
func (u *Utils) Cache() *cache.Cache {
u.RLock()
defer u.RUnlock()
return u.cache
}
func (u *Utils) SetLogger(l *pterm.Logger) {
if l == nil {
return
}
u.Lock()
defer u.Unlock()
u.logger = l
}
func (u *Utils) Logger() *pterm.Logger {
u.RLock()
defer u.RUnlock()
return u.logger
}
func (u *Utils) SetConfig(conf *config.Config) {
if conf == nil {
return
}
u.Lock()
defer u.Unlock()
u.config = conf
}
func (u *Utils) Config() *config.Config {
u.RLock()
defer u.RUnlock()
return u.config
}
func (u *Utils) SetContext(c context.Context) {
u.Lock()
defer u.Unlock()
u.ctx = c
}
func (u *Utils) Context() context.Context {
u.RLock()
defer u.RUnlock()
return u.ctx
}

View File

@ -0,0 +1,74 @@
package util
import (
"strconv"
"strings"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)
func ValidProjectsFunc(cmd *cobra.Command, args []string, toComplete string) (
[]string, cobra.ShellCompDirective,
) {
u := MustFromCtx(cmd.Context())
u.InitProjectCache(cmd, args)
return u.Cache().ProjectStrings(toComplete), cobra.ShellCompDirectiveNoFileComp
}
func ValidAliasesFunc(cmd *cobra.Command, args []string, toComplete string) (
[]string, cobra.ShellCompDirective,
) {
utils := MustFromCtx(cmd.Context())
utils.InitProjectCache(cmd, args)
return utils.Cache().AliasStrings(toComplete), cobra.ShellCompDirectiveNoFileComp
}
func (u *Utils) ValidProjectsOrAliasesFunc(cmd *cobra.Command, args []string, toComplete string) (
[]string, cobra.ShellCompDirective,
) {
projectStrings, _ := ValidAliasesFunc(cmd, args, toComplete)
aliasStrings, _ := ValidProjectsFunc(cmd, args, toComplete)
return append(projectStrings, aliasStrings...), cobra.ShellCompDirectiveDefault
}
func ValidRemotesFunc(cmd *cobra.Command, _ []string, toComplete string) (
[]string, cobra.ShellCompDirective,
) {
u := MustFromCtx(cmd.Context())
remotes := make([]string, 0, len(u.Config().Remotes))
for _, remote := range u.Config().Remotes {
if strings.HasPrefix(remote.Host, toComplete) {
remotes = append(remotes, remote.Host)
}
}
return remotes, cobra.ShellCompDirectiveNoFileComp
}
func ValidLogLevelsFunc(_ *cobra.Command, _ []string, toComplete string) (
[]string, cobra.ShellCompDirective,
) {
levels := []string{"info", "warn", "error", "debug"}
matchingLevels := make([]string, 0, len(levels))
for _, level := range levels {
if strings.HasPrefix(level, toComplete) {
matchingLevels = append(matchingLevels, level)
}
}
return matchingLevels, cobra.ShellCompDirectiveNoFileComp
}
func ValidProjectIdFunc(cmd *cobra.Command, args []string, toComplete string) (
[]string, cobra.ShellCompDirective,
) {
u := MustFromCtx(cmd.Context())
u.InitProjectCache(cmd, args)
matchingIds := make([]string, 0, len(u.Cache().Projects))
for _, p := range u.Cache().Projects {
idString := strconv.FormatInt(int64(p.ID), 10)
if strings.HasPrefix(idString, toComplete) {
matchingIds = append(matchingIds, idString)
}
}
return slices.Clip(matchingIds), cobra.ShellCompDirectiveNoFileComp
}

View File

@ -1,4 +1,4 @@
package cmd package util
const ( const (
// Cobra Flags // Cobra Flags
@ -25,62 +25,62 @@ const (
) )
const ( const (
defGitlabHost = "https://gitlab.com" DefGitlabHost = "https://gitlab.com"
defLogLevel = "info" DefLogLevel = "info"
defConfigPath = "~/.config/git-project-manager.yaml" DefConfigPath = "~/.config/git-project-manager.yaml"
configName = "git-project-manager" ConfigName = "git-project-manager"
legacyConfigName = "gitlab-project-manager" LegacyConfigName = "gitlab-project-manager"
) )
const aliasCmdLong = `Manages project aliases, with options for const AliasCmdLong = `Manages project aliases, with options for
listing, adding, and deleting.` listing, adding, and deleting.`
const aliasListCmdLong = `Lists all aliases by project` const AliasListCmdLong = `Lists all aliases by project`
const aliasAddCmdLong = `Adds a project alias to a project const AliasAddCmdLong = `Adds a project alias to a project
project ID can be provided, or will otherwise use fuzzy find` project ID can be provided, or will otherwise use fuzzy find`
const aliasDeleteCmdLong = `Deletes aliases from projects const AliasDeleteCmdLong = `Deletes aliases from projects
project ID can be provided, or will otherwise use fuzzy find` project ID can be provided, or will otherwise use fuzzy find`
const cacheCmdLong = `Contains sub-commands for managing project cache. const CacheCmdLong = `Contains sub-commands for managing project cache.
The project cache keeps this speedy, without smashing against the Git The project cache keeps this speedy, without smashing against the Git
API every time a new project is added / searched for` API every time a new project is added / searched for`
const rootCmdLong = `Finds Git projects using fuzzy-find, remembering const RootCmdLong = `Finds Git projects using fuzzy-find, remembering
your chosen term for the project as an alias, and offers helpful your chosen term for the project as an alias, and offers helpful
shortcuts for moving around in projects and opening your code` shortcuts for moving around in projects and opening your code`
const projCmdLong = `Switches to a Git project by name or alias const ProjCmdLong = `Switches to a Git project by name or alias
If not found, will enter fzf mode. If not cloned, will clone If not found, will enter fzf mode. If not cloned, will clone
the project locally.` the project locally.`
const projGoCmdLong = `Go to a project, searching by alias const ProjGoCmdLong = `Go to a project, searching by alias
If project is not already cloned, its path will be built and it If project is not already cloned, its path will be built and it
will be cloned from source control. will be cloned from source control.
If conf.projects.alwaysPull, a git pull will be ran automatically` If conf.projects.alwaysPull, a git pull will be ran automatically`
const projRunCmdLong = `Runs the current project. Tries to detect const ProjRunCmdLong = `Runs the current project. Tries to detect
the language and runs accordingly (e.g. go run .)` the language and runs accordingly (e.g. go run .)`
const projListCmdLong = `List locally cloned projects. Optionally const ProjListCmdLong = `List locally cloned projects. Optionally
lists all projects in project cache` lists all projects in project cache`
const projAddCmdLong = `Adds a new project to the local project path const ProjAddCmdLong = `Adds a new project to the local project path
uses fuzzy find to locate the project` uses fuzzy find to locate the project`
const projShowCmdLong = `Shows detail for a particular project const ProjShowCmdLong = `Shows detail for a particular project
Will always fuzzy find` Will always fuzzy find`
const projOpenCmdLong = `Opens the given project directory in the editor const ProjOpenCmdLong = `Opens the given project directory in the editor
of your choice. Will find certain well-known entrypoints (e.g. main.go). of your choice. Will find certain well-known entrypoints (e.g. main.go).
If your editor is set in your config file, it will be used, otherwise If your editor is set in your config file, it will be used, otherwise
one will be found in your path from a list of known defaults.` one will be found in your path from a list of known defaults.`
const configCmdLong = `Commands for managing configuration, particulary const ConfigCmdLong = `Commands for managing configuration, particulary
useful for seeding a new config file` useful for seeding a new config file`
const configGenCmdLong = `Produces yaml to stdout that can be used const ConfigGenCmdLong = `Produces yaml to stdout that can be used
to seed the configuration file` to seed the configuration file`

View File

@ -1,4 +1,4 @@
package cmd package util
import ( import (
"context" "context"
@ -10,7 +10,7 @@ import (
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
) )
type fzfProjectOpts struct { type FzfProjectOpts struct {
Ctx context.Context Ctx context.Context
Search string Search string
MustHaveAlias bool MustHaveAlias bool
@ -19,14 +19,14 @@ type fzfProjectOpts struct {
// This will try to find a project by alias if a search term // This will try to find a project by alias if a search term
// is given, otherwise will fuzzy find by project // is given, otherwise will fuzzy find by project
func fzfFindProject(opts *fzfProjectOpts) *projects.Project { func (u *Utils) FzfFindProject(opts *FzfProjectOpts) *projects.Project {
var project *projects.Project var project *projects.Project
if opts.Search != "" { if opts.Search != "" {
project = fzfSearchProjectAliases(opts) project = u.FzfSearchProjectAliases(opts)
} else { } else {
var err error var err error
project, err = fzfProject(opts) project, err = u.FzfProject(opts)
if project == nil || err != nil { if project == nil || err != nil {
return nil return nil
} }
@ -38,35 +38,35 @@ func fzfFindProject(opts *fzfProjectOpts) *projects.Project {
// If . is given as a project, will open project from the // If . is given as a project, will open project from the
// current working directory. Otherwise, will attempt to fuzzy-find // current working directory. Otherwise, will attempt to fuzzy-find
// a project given a search term if provided // a project given a search term if provided
func fzfCwdOrSearchProjectAliases(opts *fzfProjectOpts) *projects.Project { func (u *Utils) FzfCwdOrSearchProjectAliases(opts *FzfProjectOpts) *projects.Project {
var project *projects.Project var project *projects.Project
if opts.Search == "." { if opts.Search == "." {
project, _ = projectCache.GetProjectFromCwd() project, _ = u.Cache().GetProjectFromCwd()
} else { } else {
project = fzfSearchProjectAliases(opts) project = u.FzfSearchProjectAliases(opts)
} }
return project return project
} }
// This will fuzzy search only aliases, preferring an exact // This will fuzzy search only aliases, preferring an exact
// match if one is given // match if one is given
func fzfSearchProjectAliases(opts *fzfProjectOpts) *projects.Project { func (u *Utils) FzfSearchProjectAliases(opts *FzfProjectOpts) *projects.Project {
var project *projects.Project var project *projects.Project
var alias *cache.ProjectAlias var alias *cache.ProjectAlias
if alias = projectCache.GetAliasByName(opts.Search, opts.Remotes...); alias != nil { if alias = u.Cache().GetAliasByName(opts.Search, opts.Remotes...); alias != nil {
project = projectCache.GetProjectByAlias(alias) project = u.Cache().GetProjectByAlias(alias)
plog.Info("Perfect alias match... flawless") u.Logger().Info("Perfect alias match... flawless")
} else { } else {
// Get fuzzy if we don't have an exact match // Get fuzzy if we don't have an exact match
aliases := projectCache.FuzzyFindAlias(opts.Search) aliases := u.Cache().FuzzyFindAlias(opts.Search)
if len(aliases) > 1 { if len(aliases) > 1 {
// If multiple aliases were found, switch over to project // If multiple aliases were found, switch over to project
// by alias mode with merging // by alias mode with merging
// alias = fzfAliasFromAliases(rootCmd.Context(), aliases) // alias = fzfAliasFromAliases(rootCmd.Context(), aliases)
project, _ = fzfProjectFromAliases(opts, aliases) project, _ = u.FzfProjectFromAliases(opts, aliases)
} else if len(aliases) == 1 { } else if len(aliases) == 1 {
alias = aliases[0] alias = aliases[0]
project = projectCache.GetProjectByAlias(alias) project = u.Cache().GetProjectByAlias(alias)
} }
} }
return project return project
@ -76,18 +76,18 @@ func fzfSearchProjectAliases(opts *fzfProjectOpts) *projects.Project {
// a single one. Replaced by fzfProjectFromAliases in fzfSearchProjectAliases // a single one. Replaced by fzfProjectFromAliases in fzfSearchProjectAliases
// as merging is preferred, but can be used if it's ever desirable to // as merging is preferred, but can be used if it's ever desirable to
// return a single alias from all aliases // return a single alias from all aliases
func fzfAliasFromAliases(opts *fzfProjectOpts, aliases []*cache.ProjectAlias) *cache.ProjectAlias { func (u *Utils) FzfAliasFromAliases(opts *FzfProjectOpts, aliases []*cache.ProjectAlias) *cache.ProjectAlias {
var alias *cache.ProjectAlias var alias *cache.ProjectAlias
i, err := fzf.Find( i, err := fzf.Find(
aliases, aliases,
func(i int) string { func(i int) string {
return aliases[i].Alias + " -> " + projectCache.GetProjectByAlias(aliases[i]).PathWithNamespace return aliases[i].Alias + " -> " + u.Cache().GetProjectByAlias(aliases[i]).PathWithNamespace
}, },
fzf.WithContext(opts.Ctx), fzf.WithContext(opts.Ctx),
fzf.WithHeader("Choose an Alias"), fzf.WithHeader("Choose an Alias"),
) )
if err != nil { if err != nil {
plog.Error("Failed to fzf alias slice", plog.Args("error", err)) u.Logger().Error("Failed to fzf alias slice", u.Logger().Args("error", err))
} else { } else {
alias = aliases[i] alias = aliases[i]
} }
@ -96,21 +96,21 @@ func fzfAliasFromAliases(opts *fzfProjectOpts, aliases []*cache.ProjectAlias) *c
// Given a list of aliases, merge them together and use the resulting // Given a list of aliases, merge them together and use the resulting
// list of projects to return a project // list of projects to return a project
func fzfProjectFromAliases(opts *fzfProjectOpts, aliases []*cache.ProjectAlias) ( func (u *Utils) FzfProjectFromAliases(opts *FzfProjectOpts, aliases []*cache.ProjectAlias) (
*projects.Project, error, *projects.Project, error,
) { ) {
mergedProjects := projectsFromAliases(aliases) mergedProjects := u.projectsFromAliases(aliases)
if len(mergedProjects) == 1 { if len(mergedProjects) == 1 {
return mergedProjects[0], nil return mergedProjects[0], nil
} }
return fzfProjectFromProjects(opts, mergedProjects) return u.FzfProjectFromProjects(opts, mergedProjects)
} }
func projectsFromAliases(aliases []*cache.ProjectAlias) []*projects.Project { func (u *Utils) projectsFromAliases(aliases []*cache.ProjectAlias) []*projects.Project {
projects := make([]*projects.Project, 0, len(aliases)) projects := make([]*projects.Project, 0, len(aliases))
for _, a := range aliases { for _, a := range aliases {
project := projectCache.GetProjectByAlias(a) project := u.Cache().GetProjectByAlias(a)
if project != nil && !slices.Contains(projects, project) { if project != nil && !slices.Contains(projects, project) {
projects = append(projects, project) projects = append(projects, project)
} }
@ -121,30 +121,30 @@ func projectsFromAliases(aliases []*cache.ProjectAlias) []*projects.Project {
// If opts.MustHaveAlias, will only allow selection of projects // If opts.MustHaveAlias, will only allow selection of projects
// that have at least one alias defined // that have at least one alias defined
func fzfProject(opts *fzfProjectOpts) (*projects.Project, error) { func (u *Utils) FzfProject(opts *FzfProjectOpts) (*projects.Project, error) {
var searchableProjects []*projects.Project var searchableProjects []*projects.Project
if opts.MustHaveAlias { if opts.MustHaveAlias {
searchableProjects = projectCache.GetProjectsWithAliases() searchableProjects = u.Cache().GetProjectsWithAliases()
} else { } else {
searchableProjects = projectCache.Projects searchableProjects = u.Cache().Projects
} }
// Filter out unwanted remotes if provided // Filter out unwanted remotes if provided
searchableProjects = filterProjectsWithRemotes(searchableProjects, opts.Remotes...) searchableProjects = u.FilterProjectsWithRemotes(searchableProjects, opts.Remotes...)
return fzfProjectFromProjects(opts, searchableProjects) return u.FzfProjectFromProjects(opts, searchableProjects)
} }
// Takes a list of projects and performs a fuzzyfind // Takes a list of projects and performs a fuzzyfind
func fzfProjectFromProjects(opts *fzfProjectOpts, projects []*projects.Project) ( func (u *Utils) FzfProjectFromProjects(opts *FzfProjectOpts, projects []*projects.Project) (
*projects.Project, error, *projects.Project, error,
) { ) {
i, err := fzf.Find(projects, i, err := fzf.Find(projects,
func(i int) string { func(i int) string {
// Display the project along with its aliases // Display the project along with its aliases
return projectCache.GetProjectStringWithAliases(projects[i]) return u.Cache().GetProjectStringWithAliases(projects[i])
}, },
fzf.WithPreviewWindow( fzf.WithPreviewWindow(
func(i, width, height int) string { func(i, width, height int) string {
return projectCache.ProjectString(projects[i]) return u.Cache().ProjectString(projects[i])
}, },
), ),
fzf.WithContext(opts.Ctx), fzf.WithContext(opts.Ctx),
@ -156,12 +156,12 @@ func fzfProjectFromProjects(opts *fzfProjectOpts, projects []*projects.Project)
return projects[i], nil return projects[i], nil
} }
func fzfPreviewWindow(i, w, h int) string { func (u *Utils) FzfPreviewWindow(i, _, _ int) string {
p := projectCache.Projects[i] p := u.Cache().Projects[i]
return projectCache.ProjectString(p) return u.Cache().ProjectString(p)
} }
func filterProjectsWithRemotes(gitProjects []*projects.Project, remotes ...string) []*projects.Project { func (u *Utils) FilterProjectsWithRemotes(gitProjects []*projects.Project, remotes ...string) []*projects.Project {
filteredProjects := make([]*projects.Project, 0, len(gitProjects)) filteredProjects := make([]*projects.Project, 0, len(gitProjects))
if len(remotes) > 0 { if len(remotes) > 0 {
for _, p := range gitProjects { for _, p := range gitProjects {
@ -177,7 +177,7 @@ func filterProjectsWithRemotes(gitProjects []*projects.Project, remotes ...strin
// Nearly useless function that simply returns either an // Nearly useless function that simply returns either an
// empty string, or a string from the first arg if one is provided // empty string, or a string from the first arg if one is provided
func searchStringFromArgs(args []string) string { func (u *Utils) SearchStringFromArgs(args []string) string {
var term string var term string
if len(args) > 0 { if len(args) > 0 {
term = args[0] term = args[0]

163
cmd/util/util_init.go Normal file
View File

@ -0,0 +1,163 @@
// This file contains init methods that may be used by
// multiple sub-commands. For instance, the cach and projects
// sub-commands both depend on a cache and may both call the initProjectCache
// func (u *Util) from their PersistentPreRun commands
package util
import (
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"golang.org/x/sys/unix"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes"
gitearemote "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/gitea"
githubremote "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/github"
gitlabremote "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/gitlab"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/remote"
)
func InitProjects(cmd *cobra.Command, args []string) {
utils, _ := FromCtx(cmd.Context())
utils.InitProjectCache(cmd, args)
utils.mustHaveProjects(cmd, args)
}
func (u *Utils) mustHaveProjects(cmd *cobra.Command, _ []string) {
if len(u.Cache().Projects) == 0 {
u.Logger().Fatal("No projects to " + cmd.Name() + ", try running cache load")
}
}
func (u *Utils) InitProjectCache(cmd *cobra.Command, _ []string) {
var err error
u.Logger().Debug("Running pre-run for cacheCmd")
u.Config().Cache.File = u.Config().ProjectPath + "/.cache.yaml"
gitRemotes := remotes.NewRemotes()
gitRemotes.AddRemotes(*u.GetRemotes(cmd)...)
cacheOpts := &cache.CacheOpts{
ProjectsPath: u.Config().ProjectPath,
Path: u.Config().Cache.File,
TTL: u.Config().Cache.Ttl,
Logger: u.Logger(),
Remotes: gitRemotes,
Config: u.Config(),
}
projectCache, err := cache.NewProjectCache(cacheOpts)
if err != nil {
u.Logger().Error("Failed to prepare project cache", u.Logger().Args("error", err))
os.Exit(1)
}
u.SetCache(projectCache)
if err := u.Cache().Read(); err != nil {
u.Logger().Error("Cache load failed", u.Logger().Args("error", err))
os.Exit(1)
}
u.Logger().Debug("Remotes Loaded", u.Logger().Args("remotes", cacheOpts.Remotes))
}
// Generically loads remotes from info.RemoteInfo in config.Remotes
func (u *Utils) GetRemotes(cmd *cobra.Command) *remotes.Remotes {
gitRemotes := new(remotes.Remotes)
*gitRemotes = make([]remote.Remote, 0)
for _, r := range u.Config().Remotes {
// Create a copy, set context
gitRemoteInfo := r
gitRemoteInfo.SetContext(cmd.Context())
var gitRemote remote.Remote
var err error
switch r.Type {
case "gitlab":
gitRemote, err = gitlabremote.NewGitlabRemote(&gitRemoteInfo)
case "gitea":
gitRemote, err = gitearemote.NewGiteaRemote(&gitRemoteInfo)
case "github":
gitRemote, err = githubremote.NewGithubRemote(&gitRemoteInfo)
}
if err != nil {
u.Logger().Error("Failed to prepare remote", u.Logger().Args(
"error", err,
"type", r.Type))
} else {
*gitRemotes = append(*gitRemotes, gitRemote)
}
}
return gitRemotes
}
func PostProjectCmd(cmd *cobra.Command, args []string) {
utils, _ := FromCtx(cmd.Context())
utils.PostProjectCache(cmd, args)
}
func (u *Utils) PostProjectCache(_ *cobra.Command, _ []string) {
u.Cache().Write()
}
func (u *Utils) InitProjectPath(_ *cobra.Command, _ []string) {
u.Logger().Debug("Running persistent pre-run for rootCmd")
var err error
if u.Config().ProjectPath == "" {
u.Config().ProjectPath = config.DefaultConfig.ProjectPath
return
}
if u.Config().ProjectPath, err = ResolvePath(u.Config().ProjectPath); err != nil {
u.Logger().Error("Failed to determine project path", u.Logger().Args("error", err))
os.Exit(1)
}
_, err = os.Stat(u.Config().ProjectPath)
if err != nil {
u.Logger().Error("Failed to stat project path, trying to create", u.Logger().Args("error", err))
if err = os.MkdirAll(u.Config().ProjectPath, 0o750); err != nil {
u.Logger().Error("Failed to create project path", u.Logger().Args("error", err))
os.Exit(1)
}
u.Logger().Info("Project path created", u.Logger().Args("path", u.Config().ProjectPath))
} else {
if err = unix.Access(u.Config().ProjectPath, unix.W_OK); err != nil {
u.Logger().Error("Unable to write to project path", u.Logger().Args(
"path", u.Config().ProjectPath,
"error", err))
os.Exit(1)
}
}
}
func ResolvePath(path string) (string, error) {
if strings.HasPrefix(path, "~/") {
usr, _ := user.Current()
path = filepath.Join(usr.HomeDir, path[2:])
}
return filepath.Abs(path)
}
func GetConfigName(configPath string) string {
// Check existing config
for _, ext := range []string{"yml", "yaml"} {
configFile := fmt.Sprintf("%s/%s.%s", configPath, ConfigName, ext)
legacyConfigFile := fmt.Sprintf("%s/%s.%s", configPath, LegacyConfigName, ext)
if _, err := os.Stat(configFile); err == nil {
return ConfigName
} else if _, err := os.Stat(legacyConfigFile); err == nil {
pterm.DefaultLogger.WithWriter(os.Stderr).
Warn(fmt.Sprintf("using legacy config path, suggest using %s/%s.yaml", configPath, ConfigName))
return LegacyConfigName
}
}
// Nothing found, do what we want
return ConfigName
}

View File

@ -1,64 +0,0 @@
package cmd
import (
"strconv"
"strings"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)
func validProjectsFunc(cmd *cobra.Command, args []string, toComplete string) (
[]string, cobra.ShellCompDirective) {
initProjectCache(cmd, args)
return projectCache.ProjectStrings(toComplete), cobra.ShellCompDirectiveNoFileComp
}
func validAliasesFunc(cmd *cobra.Command, args []string, toComplete string) (
[]string, cobra.ShellCompDirective) {
initProjectCache(cmd, args)
return projectCache.AliasStrings(toComplete), cobra.ShellCompDirectiveNoFileComp
}
func validProjectsOrAliasesFunc(cmd *cobra.Command, args []string, toComplete string) (
[]string, cobra.ShellCompDirective) {
projectStrings, _ := validAliasesFunc(cmd, args, toComplete)
aliasStrings, _ := validProjectsFunc(cmd, args, toComplete)
return append(projectStrings, aliasStrings...), cobra.ShellCompDirectiveDefault
}
func validRemotesFunc(cmd *cobra.Command, args []string, toComplete string) (
[]string, cobra.ShellCompDirective) {
remotes := make([]string, 0, len(conf.Remotes))
for _, remote := range conf.Remotes {
if strings.HasPrefix(remote.Host, toComplete) {
remotes = append(remotes, remote.Host)
}
}
return remotes, cobra.ShellCompDirectiveNoFileComp
}
func validLogLevelsFunc(cmd *cobra.Command, args []string, toComplete string) (
[]string, cobra.ShellCompDirective) {
levels := []string{"info", "warn", "error", "debug"}
matchingLevels := make([]string, 0, len(levels))
for _, level := range levels {
if strings.HasPrefix(level, toComplete) {
matchingLevels = append(matchingLevels, level)
}
}
return matchingLevels, cobra.ShellCompDirectiveNoFileComp
}
func validProjectIdFunc(cmd *cobra.Command, args []string, toComplete string) (
[]string, cobra.ShellCompDirective) {
initProjectCache(cmd, args)
matchingIds := make([]string, 0, len(projectCache.Projects))
for _, p := range projectCache.Projects {
idString := strconv.FormatInt(int64(p.ID), 10)
if strings.HasPrefix(idString, toComplete) {
matchingIds = append(matchingIds, idString)
}
}
return slices.Clip(matchingIds), cobra.ShellCompDirectiveNoFileComp
}

View File

@ -1,143 +0,0 @@
// This file contains init methods that may be used by
// multiple sub-commands. For instance, the cach and projects
// sub-commands both depend on a cache and may both call the initProjectCache
// func from their PersistentPreRun commands
package cmd
import (
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"golang.org/x/sys/unix"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/config"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes"
gitearemote "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/gitea"
githubremote "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/github"
gitlabremote "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/gitlab"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/remote"
)
func initProjectCache(cmd *cobra.Command, _ []string) {
var err error
plog.Debug("Running pre-run for cacheCmd")
conf.Cache.File = conf.ProjectPath + "/.cache.yaml"
gitRemotes := remotes.NewRemotes()
gitRemotes.AddRemotes(*getRemotes(cmd)...)
cacheOpts := &cache.CacheOpts{
ProjectsPath: conf.ProjectPath,
Path: conf.Cache.File,
TTL: conf.Cache.Ttl,
Logger: plog,
Remotes: gitRemotes,
Config: &conf,
}
if projectCache, err = cache.NewProjectCache(cacheOpts); err != nil {
plog.Error("Failed to prepare project cache", plog.Args("error", err))
os.Exit(1)
}
if err := projectCache.Read(); err != nil {
plog.Error("Cache load failed", plog.Args("error", err))
os.Exit(1)
}
plog.Debug("Remotes Loaded", plog.Args("remotes", cacheOpts.Remotes))
}
// Generically loads remotes from info.RemoteInfo in config.Remotes
func getRemotes(cmd *cobra.Command) *remotes.Remotes {
gitRemotes := new(remotes.Remotes)
*gitRemotes = make([]remote.Remote, 0)
for _, r := range conf.Remotes {
// Create a copy, set context
gitRemoteInfo := r
gitRemoteInfo.SetContext(cmd.Context())
var gitRemote remote.Remote
var err error
switch r.Type {
case "gitlab":
gitRemote, err = gitlabremote.NewGitlabRemote(&gitRemoteInfo)
case "gitea":
gitRemote, err = gitearemote.NewGiteaRemote(&gitRemoteInfo)
case "github":
gitRemote, err = githubremote.NewGithubRemote(&gitRemoteInfo)
}
if err != nil {
plog.Error("Failed to prepare remote", plog.Args(
"error", err,
"type", r.Type))
} else {
*gitRemotes = append(*gitRemotes, gitRemote)
}
}
return gitRemotes
}
func postProjectCache(_ *cobra.Command, _ []string) {
projectCache.Write()
}
func initProjectPath(_ *cobra.Command, _ []string) {
plog.Debug("Running persistent pre-run for rootCmd")
var err error
if conf.ProjectPath == "" {
conf.ProjectPath = config.DefaultConfig.ProjectPath
return
}
if conf.ProjectPath, err = resolvePath(conf.ProjectPath); err != nil {
plog.Error("Failed to determine project path", plog.Args("error", err))
os.Exit(1)
}
_, err = os.Stat(conf.ProjectPath)
if err != nil {
plog.Error("Failed to stat project path, trying to create", plog.Args("error", err))
if err = os.MkdirAll(conf.ProjectPath, 0o750); err != nil {
plog.Error("Failed to create project path", plog.Args("error", err))
os.Exit(1)
}
plog.Info("Project path created", plog.Args("path", conf.ProjectPath))
} else {
if err = unix.Access(conf.ProjectPath, unix.W_OK); err != nil {
plog.Error("Unable to write to project path", plog.Args(
"path", conf.ProjectPath,
"error", err))
os.Exit(1)
}
}
}
func resolvePath(path string) (string, error) {
if strings.HasPrefix(path, "~/") {
usr, _ := user.Current()
path = filepath.Join(usr.HomeDir, path[2:])
}
return filepath.Abs(path)
}
func getConfigName(configPath string) string {
// Check existing config
for _, ext := range []string{"yml", "yaml"} {
configFile := fmt.Sprintf("%s/%s.%s", configPath, configName, ext)
legacyConfigFile := fmt.Sprintf("%s/%s.%s", configPath, legacyConfigName, ext)
if _, err := os.Stat(configFile); err == nil {
return configName
} else if _, err := os.Stat(legacyConfigFile); err == nil {
pterm.DefaultLogger.WithWriter(os.Stderr).
Warn(fmt.Sprintf("using legacy config path, suggest using %s/%s.yaml", configPath, configName))
return legacyConfigName
}
}
// Nothing found, do what we want
return configName
}

View File

@ -27,4 +27,4 @@ shortcuts for moving around in projects and opening your code
* [git-project-manager docs](git-project-manager_docs.md) - Generate documentation for git-project-manager * [git-project-manager docs](git-project-manager_docs.md) - Generate documentation for git-project-manager
* [git-project-manager project](git-project-manager_project.md) - Use a Git project * [git-project-manager project](git-project-manager_project.md) - Use a Git project
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -29,4 +29,4 @@ listing, adding, and deleting.
* [git-project-manager alias delete](git-project-manager_alias_delete.md) - Delete a project alias * [git-project-manager alias delete](git-project-manager_alias_delete.md) - Delete a project alias
* [git-project-manager alias list](git-project-manager_alias_list.md) - List Aliases * [git-project-manager alias list](git-project-manager_alias_list.md) - List Aliases
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -31,4 +31,4 @@ git-project-manager alias add [flags]
* [git-project-manager alias](git-project-manager_alias.md) - Manage project aliases * [git-project-manager alias](git-project-manager_alias.md) - Manage project aliases
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -31,4 +31,4 @@ git-project-manager alias delete [fuzzy project or alias] [flags]
* [git-project-manager alias](git-project-manager_alias.md) - Manage project aliases * [git-project-manager alias](git-project-manager_alias.md) - Manage project aliases
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -29,4 +29,4 @@ git-project-manager alias list [flags]
* [git-project-manager alias](git-project-manager_alias.md) - Manage project aliases * [git-project-manager alias](git-project-manager_alias.md) - Manage project aliases
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -32,4 +32,4 @@ API every time a new project is added / searched for
* [git-project-manager cache load](git-project-manager_cache_load.md) - Load Git Project Cache * [git-project-manager cache load](git-project-manager_cache_load.md) - Load Git Project Cache
* [git-project-manager cache unlock](git-project-manager_cache_unlock.md) - unlock Git project cache * [git-project-manager cache unlock](git-project-manager_cache_unlock.md) - unlock Git project cache
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -33,4 +33,4 @@ git-project-manager cache clear [flags]
* [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache * [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -31,4 +31,4 @@ git-project-manager cache dump [flags]
* [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache * [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -33,4 +33,4 @@ git-project-manager cache load [flags]
* [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache * [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -31,4 +31,4 @@ git-project-manager cache unlock [flags]
* [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache * [git-project-manager cache](git-project-manager_cache.md) - Manage Git project cache
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -31,4 +31,4 @@ See each sub-command's help for details on how to use the generated script.
* [git-project-manager completion powershell](git-project-manager_completion_powershell.md) - Generate the autocompletion script for powershell * [git-project-manager completion powershell](git-project-manager_completion_powershell.md) - Generate the autocompletion script for powershell
* [git-project-manager completion zsh](git-project-manager_completion_zsh.md) - Generate the autocompletion script for zsh * [git-project-manager completion zsh](git-project-manager_completion_zsh.md) - Generate the autocompletion script for zsh
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -50,4 +50,4 @@ git-project-manager completion bash
* [git-project-manager completion](git-project-manager_completion.md) - Generate the autocompletion script for the specified shell * [git-project-manager completion](git-project-manager_completion.md) - Generate the autocompletion script for the specified shell
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -41,4 +41,4 @@ git-project-manager completion fish [flags]
* [git-project-manager completion](git-project-manager_completion.md) - Generate the autocompletion script for the specified shell * [git-project-manager completion](git-project-manager_completion.md) - Generate the autocompletion script for the specified shell
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -38,4 +38,4 @@ git-project-manager completion powershell [flags]
* [git-project-manager completion](git-project-manager_completion.md) - Generate the autocompletion script for the specified shell * [git-project-manager completion](git-project-manager_completion.md) - Generate the autocompletion script for the specified shell
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -52,4 +52,4 @@ git-project-manager completion zsh [flags]
* [git-project-manager completion](git-project-manager_completion.md) - Generate the autocompletion script for the specified shell * [git-project-manager completion](git-project-manager_completion.md) - Generate the autocompletion script for the specified shell
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -28,4 +28,4 @@ useful for seeding a new config file
* [git-project-manager config generate](git-project-manager_config_generate.md) - Generate a default configuration * [git-project-manager config generate](git-project-manager_config_generate.md) - Generate a default configuration
* [git-project-manager config show](git-project-manager_config_show.md) - Show Git Project Manager Configuration * [git-project-manager config show](git-project-manager_config_show.md) - Show Git Project Manager Configuration
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -32,4 +32,4 @@ git-project-manager config generate [flags]
* [git-project-manager config](git-project-manager_config.md) - Git Project Manager Configuration * [git-project-manager config](git-project-manager_config.md) - Git Project Manager Configuration
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -26,4 +26,4 @@ git-project-manager config show [flags]
* [git-project-manager config](git-project-manager_config.md) - Git Project Manager Configuration * [git-project-manager config](git-project-manager_config.md) - Git Project Manager Configuration
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -26,4 +26,4 @@ git-project-manager docs [flags]
* [git-project-manager](git-project-manager.md) - Find and use Git projects locally * [git-project-manager](git-project-manager.md) - Find and use Git projects locally
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -31,6 +31,8 @@ the project locally.
* [git-project-manager project list](git-project-manager_project_list.md) - List Git Projects * [git-project-manager project list](git-project-manager_project_list.md) - List Git Projects
* [git-project-manager project open](git-project-manager_project_open.md) - Open project in your IDE * [git-project-manager project open](git-project-manager_project_open.md) - Open project in your IDE
* [git-project-manager project run](git-project-manager_project_run.md) - Run the project (e.g. go run .) * [git-project-manager project run](git-project-manager_project_run.md) - Run the project (e.g. go run .)
* [git-project-manager project run](git-project-manager_project_run.md) - Run the project (e.g. go run .)
* [git-project-manager project show](git-project-manager_project_show.md) - Show detail for a Git project
* [git-project-manager project show](git-project-manager_project_show.md) - Show detail for a Git project * [git-project-manager project show](git-project-manager_project_show.md) - Show detail for a Git project
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -30,4 +30,4 @@ git-project-manager project add [flags]
* [git-project-manager project](git-project-manager_project.md) - Use a Git project * [git-project-manager project](git-project-manager_project.md) - Use a Git project
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -33,4 +33,4 @@ git-project-manager project go [fuzzy alias search] [flags]
* [git-project-manager project](git-project-manager_project.md) - Use a Git project * [git-project-manager project](git-project-manager_project.md) - Use a Git project
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -31,4 +31,4 @@ git-project-manager project list [flags]
* [git-project-manager project](git-project-manager_project.md) - Use a Git project * [git-project-manager project](git-project-manager_project.md) - Use a Git project
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -36,4 +36,4 @@ git-project-manager project open [fuzzy alias search] [flags]
* [git-project-manager project](git-project-manager_project.md) - Use a Git project * [git-project-manager project](git-project-manager_project.md) - Use a Git project
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -30,4 +30,4 @@ git-project-manager project run [flags]
* [git-project-manager project](git-project-manager_project.md) - Use a Git project * [git-project-manager project](git-project-manager_project.md) - Use a Git project
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

View File

@ -31,4 +31,4 @@ git-project-manager project show [fuzzy alias search] [flags]
* [git-project-manager project](git-project-manager_project.md) - Use a Git project * [git-project-manager project](git-project-manager_project.md) - Use a Git project
###### Auto generated by spf13/cobra on 27-Dec-2024 ###### Auto generated by spf13/cobra on 31-Dec-2024

60
go.mod
View File

@ -1,23 +1,26 @@
module gitea.libretechconsulting.com/rmcguire/git-project-manager module gitea.libretechconsulting.com/rmcguire/git-project-manager
go 1.23.4 go 1.24.2
require ( require (
github.com/go-git/go-git/v5 v5.12.0 github.com/go-git/go-git/v5 v5.16.0
github.com/ktr0731/go-fuzzyfinder v0.8.0 github.com/ktr0731/go-fuzzyfinder v0.9.0
github.com/lithammer/fuzzysearch v1.1.8 github.com/lithammer/fuzzysearch v1.1.8
github.com/pterm/pterm v0.12.80 github.com/pterm/pterm v0.12.80
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.19.0 github.com/spf13/viper v1.20.1
github.com/whilp/git-urls v1.0.0 github.com/whilp/git-urls v1.0.0
github.com/xanzy/go-gitlab v0.115.0 github.com/xanzy/go-gitlab v0.115.0
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
golang.org/x/sys v0.28.0 golang.org/x/mod v0.24.0
golang.org/x/sys v0.32.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
github.com/42wim/httpsig v1.2.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
) )
@ -25,21 +28,21 @@ require (
atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/cursor v0.2.0 // indirect
atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/keyboard v0.2.9 // indirect
atomicgo.dev/schedule v0.1.0 // indirect atomicgo.dev/schedule v0.1.0 // indirect
code.gitea.io/sdk/gitea v0.19.0 // direct code.gitea.io/sdk/gitea v0.21.0 // direct
dario.cat/mergo v1.0.1 // indirect dario.cat/mergo v1.0.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/ProtonMail/go-crypto v1.2.0 // indirect
github.com/cloudflare/circl v1.5.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/containerd/console v1.0.4 // indirect github.com/containerd/console v1.0.4 // indirect
github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gdamore/encoding v1.0.1 // indirect github.com/gdamore/encoding v1.0.1 // indirect
github.com/gdamore/tcell/v2 v2.7.4 // indirect github.com/gdamore/tcell/v2 v2.8.1 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.0 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-github/v58 v58.0.0 // direct github.com/google/go-github/v58 v58.0.0 // direct
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
@ -47,38 +50,33 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/ktr0731/go-ansisgr v0.1.0 // indirect github.com/ktr0731/go-ansisgr v0.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/nsf/termbox-go v1.1.1 // indirect github.com/nsf/termbox-go v1.1.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/locafero v0.9.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect github.com/spf13/afero v1.14.0 // indirect
github.com/spf13/cast v1.7.1 // indirect github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.31.0 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.33.0 // indirect golang.org/x/net v0.39.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/term v0.27.0 // indirect golang.org/x/term v0.31.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.8.0 // indirect golang.org/x/time v0.11.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
) )

173
go.sum
View File

@ -6,10 +6,12 @@ atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
code.gitea.io/sdk/gitea v0.19.0 h1:8I6s1s4RHgzxiPHhOQdgim1RWIRcr0LVMbHBjBFXq4Y= code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
code.gitea.io/sdk/gitea v0.19.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI= code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/42wim/httpsig v1.2.2 h1:ofAYoHUNs/MJOLqQ8hIxeyz2QxOz8qdSVvp3PX/oPgA=
github.com/42wim/httpsig v1.2.2/go.mod h1:P/UYo7ytNBFwc+dg35IubuAUIs8zj5zzFIgUCEl55WY=
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
@ -22,63 +24,61 @@ github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y= github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ=
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v58 v58.0.0 h1:Una7GGERlF/37XfkPwpzYJe0Vp4dt2k1kCjlxwjIvzw= github.com/google/go-github/v58 v58.0.0 h1:Una7GGERlF/37XfkPwpzYJe0Vp4dt2k1kCjlxwjIvzw=
github.com/google/go-github/v58 v58.0.0/go.mod h1:k4hxDKEfoWpSqFlc8LTpGd9fu2KrV1YAa6Hi6FmDNY4= github.com/google/go-github/v58 v58.0.0/go.mod h1:k4hxDKEfoWpSqFlc8LTpGd9fu2KrV1YAa6Hi6FmDNY4=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@ -97,8 +97,6 @@ github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISH
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@ -119,39 +117,32 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ktr0731/go-ansisgr v0.1.0 h1:fbuupput8739hQbEmZn1cEKjqQFwtCCZNznnF6ANo5w= github.com/ktr0731/go-ansisgr v0.1.0 h1:fbuupput8739hQbEmZn1cEKjqQFwtCCZNznnF6ANo5w=
github.com/ktr0731/go-ansisgr v0.1.0/go.mod h1:G9lxwgBwH0iey0Dw5YQd7n6PmQTwTuTM/X5Sgm/UrzE= github.com/ktr0731/go-ansisgr v0.1.0/go.mod h1:G9lxwgBwH0iey0Dw5YQd7n6PmQTwTuTM/X5Sgm/UrzE=
github.com/ktr0731/go-fuzzyfinder v0.8.0 h1:+yobwo9lqZZ7jd1URPdCgZXTE2U1mpIVTkQoo4roi6w= github.com/ktr0731/go-fuzzyfinder v0.9.0 h1:JV8S118RABzRl3Lh/RsPhXReJWc2q0rbuipzXQH7L4c=
github.com/ktr0731/go-fuzzyfinder v0.8.0/go.mod h1:Bjpz5im+tppKE9Ii6UK1h+6RaX/lUvJ0ruO4LIYRkqo= github.com/ktr0731/go-fuzzyfinder v0.9.0/go.mod h1:uybx+5PZFCgMCSDHJDQ9M3nNKx/vccPmGffsXPn2ad8=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
@ -165,39 +156,37 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
@ -217,25 +206,41 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -251,40 +256,52 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -14,6 +14,8 @@ import (
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
) )
const cacheVersion = "v0.1.0"
type Cache struct { type Cache struct {
Projects []*projects.Project Projects []*projects.Project
Aliases []*ProjectAlias Aliases []*ProjectAlias
@ -27,6 +29,7 @@ type Cache struct {
file string file string
log *pterm.Logger log *pterm.Logger
path string path string
CacheVersion string
} }
type CacheOpts struct { type CacheOpts struct {
@ -67,11 +70,10 @@ func (c *Cache) LockCache() {
c.checkLock() c.checkLock()
file, err := os.OpenFile(c.file+".lock", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o640) file, err := os.OpenFile(c.file+".lock", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o640)
defer file.Close()
if err != nil { if err != nil {
c.log.Fatal("Failed to lock cache", c.log.Args("error", err)) c.log.Fatal("Failed to lock cache", c.log.Args("error", err))
} }
file.Close()
} }
func (c *Cache) checkLock() { func (c *Cache) checkLock() {
@ -119,6 +121,19 @@ func (c *Cache) Read() error {
d := yaml.NewDecoder(file) d := yaml.NewDecoder(file)
d.Decode(c) d.Decode(c)
// Perform migrations
migrated, err := c.doMigrations()
if err != nil {
c.log.Error("Failed to run cache migrations",
c.log.Args(
"migrated", migrated,
"error", err,
))
} else if migrated > 0 {
c.log.Info("Migrations run successfully", c.log.Args(
"migrated", migrated))
}
c.readFromFile = true c.readFromFile = true
return nil return nil
} }

View File

@ -3,8 +3,9 @@ package cache
import ( import (
"errors" "errors"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
) )
func (c *Cache) deleteAlias(alias *ProjectAlias) { func (c *Cache) deleteAlias(alias *ProjectAlias) {
@ -21,34 +22,35 @@ func (c *Cache) DeleteAlias(alias *ProjectAlias) {
c.deleteAlias(alias) c.deleteAlias(alias)
} }
func (c *Cache) addAlias(alias string, projectID int, remote string) error { func (c *Cache) addAlias(alias string, project *projects.Project) error {
if c.GetAliasByName(alias) != nil { if c.GetAliasByName(alias) != nil {
return errors.New("Failed to add alias, already exists") return errors.New("failed to add alias, already exists")
} }
c.Aliases = append(c.Aliases, c.Aliases = append(c.Aliases,
&ProjectAlias{ &ProjectAlias{
Alias: alias, Alias: alias,
ProjectID: projectID, ProjectID: project.ID,
Remote: remote, ID: project.GetID(),
Remote: project.Remote,
}) })
return nil return nil
} }
func (c *Cache) AddAlias(alias string, projectID int, remote string) error { func (c *Cache) AddAlias(alias string, project *projects.Project) error {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
return c.addAlias(alias, projectID, remote) return c.addAlias(alias, project)
} }
func (c *Cache) GetProjectsWithAliases() []*projects.Project { func (c *Cache) GetProjectsWithAliases() []*projects.Project {
projectList := make([]*projects.Project, 0) projectList := make([]*projects.Project, 0)
projectsFound := make([]int, 0) projectsFound := make([]string, 0)
for _, a := range c.Aliases { for _, a := range c.Aliases {
if !slices.Contains(projectsFound, a.ProjectID) { if !slices.Contains(projectsFound, a.ID) {
projectList = append(projectList, c.GetProjectByAlias(a)) projectList = append(projectList, c.GetProjectByAlias(a))
projectsFound = append(projectsFound, a.ProjectID) projectsFound = append(projectsFound, a.ID)
} }
} }
return projectList return projectList
@ -67,12 +69,13 @@ func (c *Cache) setAliasRemotes() {
} }
func (c *Cache) setAliasRemote(alias *ProjectAlias) { func (c *Cache) setAliasRemote(alias *ProjectAlias) {
project := c.GetProjectByID(alias.ProjectID) project := c.GetProjectByID(alias.ID)
if project != nil { if project != nil {
alias.Remote = project.Remote alias.Remote = project.Remote
c.log.Debug("Fixed missing alias remote", c.log.Args( c.log.Debug("Fixed missing alias remote", c.log.Args(
"alias", alias.Alias, "alias", alias.Alias,
"projectID", alias.ProjectID, "projectID", alias.ProjectID,
"ID", alias.ID,
"remote", alias.Remote, "remote", alias.Remote,
)) ))
} }

78
internal/cache/cache_migrations.go vendored Normal file
View File

@ -0,0 +1,78 @@
package cache
import (
"errors"
"fmt"
"golang.org/x/mod/semver"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
)
// Migrations funcs should return errors along with
// number of records updated
type migrationFunc func(c *Cache) (int, error)
// Registry of migrations by version
var migrations = map[string]map[string]migrationFunc{
"v0.1.0": {
"Make Aliases Unique": v010_aliases,
},
}
// Performs any required updates based on version
// of cache read from disk.
// Does not check to ensure migrations were successful,
// only checks if a version has been achieved
func (c *Cache) DoMigrations() (int, error) {
c.lock.Lock()
defer c.lock.Unlock()
return c.doMigrations()
}
func (c *Cache) doMigrations() (int, error) {
var errs error
var migrated int
for version, migrationFuncs := range migrations {
var funcMigrated int
if semver.Compare(c.CacheVersion, version) < 0 {
for name, migration := range migrationFuncs {
numMigrated, err := migration(c)
if err != nil {
errs = errors.Join(
errs,
fmt.Errorf("%s - %s: %w", version, name, err),
)
}
funcMigrated += numMigrated
}
// We've reached a cache version, update the CacheVersion
// and write to disk
if errs == nil && funcMigrated > 0 {
c.CacheVersion = version
c.write()
}
}
migrated += funcMigrated
}
return migrated, errs
}
func v010_aliases(c *Cache) (int, error) {
var aliasesMigrated int
var errs error
for i, a := range c.Aliases {
if a.ID == "" {
if a.Remote == "" {
errs = errors.Join(errs,
fmt.Errorf("alias %s [id:%d] has no remote", a.Alias, a.ProjectID))
continue
}
c.Aliases[i].ID = projects.MakeID(a.Remote, a.ProjectID)
aliasesMigrated++
}
}
return aliasesMigrated, errs
}

View File

@ -4,8 +4,9 @@ import (
"strings" "strings"
"github.com/pterm/pterm" "github.com/pterm/pterm"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
) )
func (c *Cache) ProjectString(p *projects.Project) string { func (c *Cache) ProjectString(p *projects.Project) string {
@ -60,9 +61,9 @@ func (c *Cache) GetProjectByRemoteAndId(remote string, id int) *projects.Project
return nil return nil
} }
func (c *Cache) GetProjectByID(id int) *projects.Project { func (c *Cache) GetProjectByID(id string) *projects.Project {
for _, p := range c.Projects { for _, p := range c.Projects {
if p.ID == id { if p.GetID() == id {
return p return p
} }
} }

View File

@ -7,13 +7,15 @@ import (
"text/tabwriter" "text/tabwriter"
"github.com/pterm/pterm" "github.com/pterm/pterm"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
) )
type ProjectAlias struct { type ProjectAlias struct {
Alias string Alias string
ProjectID int ProjectID int
ID string
Remote string Remote string
} }
@ -96,7 +98,7 @@ func (c *Cache) GetProjectByAlias(alias *ProjectAlias) *projects.Project {
return nil return nil
} }
for _, p := range c.Projects { for _, p := range c.Projects {
if p.ID == alias.ProjectID && p.Remote == alias.Remote { if p.GetID() == alias.ID {
return p return p
} }
} }

View File

@ -32,7 +32,7 @@ func (c *Cache) GetProjectFromCwd() (*projects.Project, error) {
if err != nil { if err != nil {
return project, err return project, err
} else if !strings.HasPrefix(cwd, c.path) { } else if !strings.HasPrefix(cwd, c.path) {
return project, errors.New("Not in any project path") return project, errors.New("not in any project path")
} }
// Strip projects dir from path // Strip projects dir from path

View File

@ -5,17 +5,16 @@ import (
"time" "time"
git "github.com/go-git/go-git/v5" git "github.com/go-git/go-git/v5"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
) )
const gitCloneTimeoutSecs = 60
// Will either read in the current repo, preparing a report // Will either read in the current repo, preparing a report
// on its current state, or will clone the project if it has not // on its current state, or will clone the project if it has not
// already been cloned in its path // already been cloned in its path
func (c *Cache) OpenProject(ctx context.Context, project *projects.Project) *git.Repository { func (c *Cache) OpenProject(ctx context.Context, project *projects.Project) *git.Repository {
path := c.GetProjectPath(project) path := c.GetProjectPath(project)
cloneCtx, cncl := context.WithDeadline(ctx, time.Now().Add(gitCloneTimeoutSecs*time.Second)) cloneCtx, cncl := context.WithDeadline(ctx, time.Now().Add(c.config.GetCloneTimeout()))
defer cncl() defer cncl()
var repo *git.Repository var repo *git.Repository
@ -31,7 +30,7 @@ func (c *Cache) OpenProject(ctx context.Context, project *projects.Project) *git
// Check to make sure we can connect before we time out // Check to make sure we can connect before we time out
// shouldn't be necessary, but go-git does not properly // shouldn't be necessary, but go-git does not properly
// honor its context // honor its context
if err := project.CheckHost(projects.GitProtoSSH); err != nil { if err = project.CheckHost(projects.GitProtoSSH); err != nil {
c.log.Fatal("Git remote unreachable, giving up", c.log.Args("error", err)) c.log.Fatal("Git remote unreachable, giving up", c.log.Args("error", err))
} }

View File

@ -1,6 +1,7 @@
package config package config
import ( import (
"sync"
"time" "time"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/info" "gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/info"
@ -9,14 +10,17 @@ import (
type Config struct { type Config struct {
// Named keys above maintained for backwards compatibility // Named keys above maintained for backwards compatibility
// Ideally only Gitlabs is used // Ideally only Gitlabs is used
Remotes []info.RemoteInfo Remotes []info.RemoteInfo `yaml:"remotes" json:"remotes,omitempty"`
LogLevel string `yaml:"logLevel" json:"logLevel" enum:"info,warn,debug,error"` LogLevel string `yaml:"logLevel" json:"logLevel,omitempty" enum:"info,warn,debug,error"`
ProjectPath string `yaml:"projectPath" json:"projectPath"` ProjectPath string `yaml:"projectPath" json:"projectPath,omitempty"`
Cache cacheConfig `yaml:"cache" json:"cache"` Cache cacheConfig `yaml:"cache" json:"cache"`
Dump struct { CloneTimeout string `yaml:"cloneTimeout" json:"cloneTimeout,omitempty" default:"120s"`
Full bool `yaml:"full" json:"full"` cloneTimeout time.Duration
Dump struct {
Full bool `yaml:"full" json:"full,omitempty"`
} `yaml:"dump" json:"dump"` } `yaml:"dump" json:"dump"`
Editor editorConfig `yaml:"editor" json:"editor"` Editor editorConfig `yaml:"editor" json:"editor"`
lock *sync.RWMutex
} }
type editorConfig struct { type editorConfig struct {
@ -39,12 +43,13 @@ type cacheConfig struct {
} `yaml:"unlock" json:"unlock"` } `yaml:"unlock" json:"unlock"`
Clear struct { Clear struct {
ClearAliases bool `yaml:"clearAliases,omitempty" json:"clearAliases,omitempty"` ClearAliases bool `yaml:"clearAliases,omitempty" json:"clearAliases,omitempty"`
} `yaml:"clear,omitempty" json:"clear,omitempty"` } `yaml:"clear" json:"clear"`
} }
var DefaultConfig = Config{ var DefaultConfig = Config{
LogLevel: "warn", LogLevel: "warn",
ProjectPath: "~/work/projects", ProjectPath: "~/work/projects",
CloneTimeout: "120s",
Remotes: []info.RemoteInfo{{ Remotes: []info.RemoteInfo{{
Host: "https://gitlab.com", Host: "https://gitlab.com",
Token: "yourtokenhere", Token: "yourtokenhere",
@ -59,3 +64,29 @@ var DefaultConfig = Config{
}, },
}, },
} }
func NewConfig() *Config {
return &Config{
lock: &sync.RWMutex{},
}
}
// Perform updates to config
func (c *Config) Prepare() (err error) {
c.lock.Lock()
defer c.lock.Unlock()
if c.CloneTimeout == "" {
c.CloneTimeout = DefaultConfig.CloneTimeout
}
c.cloneTimeout, err = time.ParseDuration(c.CloneTimeout)
return err
}
func (c *Config) GetCloneTimeout() time.Duration {
c.lock.RLock()
defer c.lock.RUnlock()
return c.cloneTimeout
}

View File

@ -1,6 +1,7 @@
package projects package projects
import ( import (
"crypto/sha1"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@ -8,6 +9,8 @@ import (
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
) )
// Git project metadata
// Do not use Project.ID directly (remotes may conflict), use Project.GetID()
type Project struct { type Project struct {
ID int ID int
Description string Description string
@ -35,8 +38,7 @@ type ProjectLanguage struct {
} }
func NewProjectLanguages() *ProjectLanguages { func NewProjectLanguages() *ProjectLanguages {
var pLangs ProjectLanguages var pLangs ProjectLanguages = make([]*ProjectLanguage, 0)
pLangs = make([]*ProjectLanguage, 0)
return &pLangs return &pLangs
} }
@ -44,6 +46,32 @@ func (pl *ProjectLanguages) AddLanguage(lang *ProjectLanguage) {
*pl = append(*pl, lang) *pl = append(*pl, lang)
} }
// Gets a unique ID using a short-sha of the http repo
// along with the numerical ID of the project.
// Uses SSH URL and then Remote if previous is empty
func (p *Project) GetID() string {
return fmt.Sprintf("%s||%d", p.GetRemoteSha(), p.ID)
}
func MakeID(remote string, projectID int) string {
return fmt.Sprintf("%s||%d", GetRemoteSha(remote), projectID)
}
func (p *Project) GetRemoteSha() string {
remote := p.Remote
if remote == "" && p.HTTPURLToRepo != "" {
remote = p.HTTPURLToRepo
} else if remote == "" && p.WebURL != "" {
remote = p.WebURL
}
return GetRemoteSha(remote)
}
func GetRemoteSha(remote string) string {
return fmt.Sprintf("%x", sha1.Sum([]byte(remote)))[:12]
}
func (p *Project) String() string { func (p *Project) String() string {
var projectString string var projectString string
if p != nil { if p != nil {

View File

@ -20,9 +20,7 @@ const (
GitProtoHTTP GitProtoHTTP
) )
var ( var ErrUnknownHost error = errors.New("no addresses found for host")
ErrUnknownHost error = errors.New("No addresses found for host")
)
func (p *Project) CheckHost(proto GitProto) error { func (p *Project) CheckHost(proto GitProto) error {
switch proto { switch proto {
@ -31,7 +29,7 @@ func (p *Project) CheckHost(proto GitProto) error {
case GitProtoSSH: case GitProtoSSH:
return p.checkSSHRemote() return p.checkSSHRemote()
} }
return errors.New("Unknown git protocol") return errors.New("unknown git protocol")
} }
func (p *Project) checkHTTPRemote() error { func (p *Project) checkHTTPRemote() error {