diff --git a/README.md b/README.md index 97919ce..3ac8edf 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ General help is also available by running `bb --help` or `bb help`. By default `bb` works in the current git repository. You can specify a Bitbucket repository with the `--repository` flag. +See the [Completion](#completion) section for more information about completion. Many commands and flags are dynamically auto-completed. + ### Output `bb` outputs a table by default and get be set per profile. You can also use the `--output` flag to change the output format manually. The following formats are supported: @@ -38,6 +40,12 @@ Or bb workspace list --output json ``` +You can also set the output format with the environment variable `BB_OUTPUT_FORMAT`: + +```bash +export BB_OUTPUT_FORMAT=json +``` + ### Profiles `bb` uses profiles to store your Bitbucket credentials. You can create a profile with the `bb profile create` command: @@ -51,6 +59,8 @@ bb profile create \ You can also pass the `--default` flag to make this profile the default one, or pass a `--output` flag to change the profile output format. +You can also pass the `--default-workspace` and/or `--default-project` flags to set the default workspace and/or project for this profile. + Profiles support the following authentications: - [OAuth 2.0](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/) with the `--client-id` and `--client-secret` flags @@ -69,6 +79,12 @@ You can get the details of a profile with the `bb profile get` or `bb profile sh bb profile get myprofile ``` +You can ge the details of the current profile: + +```bash +bb profile get --current +``` + You can update a profile with the `bb profile update` command: ```bash @@ -89,6 +105,29 @@ You can set the default profile with the `bb profile use` command: bb profile use myprofile ``` +You can also set the profile with the environment variable `BB_PROFILE`: + +```bash +export BB_PROFILE=myprofile +``` + +Profiles are stored in the configuration file. By default, the configuration file is located: + +- on Linux: `$XDG_CONFIG_HOME/bitbucket/config-cli.json`, or `~/.config/bitbucket/config-cli.json`, then `~/.bitbucket-cli` +- on macOS: `$HOME/Library/Application Support/bitbucket/config-cli.json`, then `~/.bitbucket-cli` +- on Windows: `%AppData%\bitbucket\config-cli.json`, then `$HOME/.bitbucket-cli` +- on Plan 9: `$home/lib/bitbucket/config-cli.json`, then `~/.bitbucket-cli` + +You can also override the location of the configuration file with the environment variable `BB_CONFIG` or the `--config` flag: + +```bash +export BB_CONFIG=~/.bb/config.json +``` + +```bash +bb --config ~/.bb/config.json workspace list +``` + ### Workspaces You can list workspaces with the `bb workspace list` command: @@ -123,7 +162,7 @@ bb workspace get myworkspace --member mymember ### Projects -You can list projects with the `bb project list` command, the `--workspace` flag is required for all project commands: +You can list projects with the `bb project list` command. If the `--workspace` flag is not provided, the default workspace of the profile is used (if the profile does not have a default workspace, the command will fail): ```bash bb project list --workspace myworkspace @@ -142,27 +181,25 @@ You can create a project with the `bb project create` command: ```bash bb project create \ --name myproject \ - --key MYPROJECT \ - --workspace myworkspace + --key MYPROJECT ``` You can update a project with the `bb project update` command: ```bash bb project update myproject \ - --name myproject \ - --workspace myworkspace + --name myproject ``` You can delete a project with the `bb project delete` command: ```bash -bb project delete myproject --workspace myworkspace +bb project delete myproject ``` #### Project Default Reviewers -You can list the default reviewers of a project with the `bb project reviewer list` command: +You can list the default reviewers of a project with the `bb project reviewer list` command. In addition to the `--workspace`, if the `--project` flag is not provided, the default project of the workspace is used (if the workspace does not have a default project, the command will fail): ```bash bb project reviewer list --workspace myworkspace --project myproject @@ -171,19 +208,15 @@ bb project reviewer list --workspace myworkspace --project myproject You can add a default reviewer to a project with the `bb project reviewer add` command: ```bash -bb project reviewer add \ - --workspace myworkspace \ - --project myproject \ - userUUID +bb project reviewer add userUUID ``` +The `{}` around the `userUUID` are optional. + You can remove a default reviewer from a project with the `bb project reviewer remove` command: ```bash -bb project reviewer remove \ - --workspace myworkspace \ - --project myproject \ - userUUID +bb project reviewer remove userUUID ``` You can get the details of a default reviewer with the `bb project reviewer get` or `bb project reviewer show` command: @@ -195,6 +228,88 @@ bb project reviewer get \ userUUID ``` +### Repositories + +You can list repositories with the `bb repo list` command: + +```bash +bb repo list --workspace myworkspace +``` + +If you do not provide a workspace, the command will attempt to list all repositories you have access to, which can take a very long time. + +You can also get the details of a repository with the `bb repo get` or `bb repo show` command. If the `--workspace` flag is not provided, the default workspace of the profile is used (if the profile does not have a default workspace, the command will fail): + +```bash +bb repo get --workspace myworkspace myrepository +``` + +You can clone a repository with the `bb repo clone` command: + +```bash +bb repo clone myworkspace/myrepository +``` + +or, with the `--workspace` flag: + +```bash +bb repo clone --workspace myworkspace myrepository +``` + +Or, using the profile's default workspace: + +```bash +bb repo clone myrepository +``` + +By default, the repository is cloned in a folder with the same name as the repository. You can specify a different folder with the `--destination` flag: + +```bash +bb repo clone --workspace myworkspace --destination myfolder myrepository +``` + +You can create a repository with the `bb repo create` command: + +```bash +bb repo create myrepository_slug \ + --name myrepository \ + --project myproject \ + --workspace myworkspace +``` + +If the `--project` flag is not provided, the repository will be created in the default project of the profile. + +You can update a repository with the `bb repo update` command: + +```bash +bb repo update --workspace myworkspace myrepository \ + --private \ + --fork-policy no_public_forks +``` + +You can delete a repository with the `bb repo delete` command: + +```bash +bb repo delete --workspace myworkspace myrepository +``` + +You can fork a repository with the `bb repo fork` command: + +```bash +bb repo fork myrepository \ + --workspace myworkspace \ + --project myproject \ + --name myfork +``` + +You can list the forks of a repository with the `bb repo get --forks` command: + +```bash +bb repo get myrepository \ + --workspace myworkspace \ + --forks +``` + ### Pull Requests You can list pull requests with the `bb pullrequest list` command: @@ -440,6 +555,6 @@ On macOS, you can add the completion to the brew functions: bb completion zsh > "$(brew --prefix)/share/zsh/site-functions/_bb" ``` -### TODO +## TODO We will add more commands in the future. If you have any suggestions, please open an issue. diff --git a/cmd/artifact/delete.go b/cmd/artifact/delete.go index 5052055..26d2d27 100644 --- a/cmd/artifact/delete.go +++ b/cmd/artifact/delete.go @@ -11,9 +11,9 @@ import ( ) var deleteCmd = &cobra.Command{ - Use: "delete", + Use: "delete [flags] ", Aliases: []string{"remove", "rm"}, - Short: "delete an artifact by its filename", + Short: "delete an artifact by its .", ValidArgsFunction: deleteValidArgs, Args: cobra.ExactArgs(1), RunE: deleteProcess, @@ -47,7 +47,7 @@ func deleteProcess(cmd *cobra.Command, args []string) error { return errors.ArgumentMissing.With("profile") } - log.Infof("Deleting artifact %s from repository %s with profile %s", args[0], listOptions.Repository, profile.Current) + log.Infof("Deleting artifact %s from repository %s with profile %s", args[0], deleteOptions.Repository, profile.Current) err := profile.Current.Delete( log.ToContext(cmd.Context()), cmd, diff --git a/cmd/artifact/download.go b/cmd/artifact/download.go index 0fc0348..541b2af 100644 --- a/cmd/artifact/download.go +++ b/cmd/artifact/download.go @@ -11,9 +11,9 @@ import ( ) var downloadCmd = &cobra.Command{ - Use: "download", + Use: "download [flags] ", Aliases: []string{"get", "fetch"}, - Short: "download an artifact", + Short: "download an artifact by its .", ValidArgsFunction: downloadValidArgs, Args: cobra.ExactArgs(1), RunE: getProcess, diff --git a/cmd/artifact/upload.go b/cmd/artifact/upload.go index 0654080..40d9ade 100644 --- a/cmd/artifact/upload.go +++ b/cmd/artifact/upload.go @@ -11,7 +11,7 @@ import ( ) var uploadCmd = &cobra.Command{ - Use: "upload", + Use: "upload [flags] ", Aliases: []string{"add", "create"}, Short: "upload an artifact", Args: cobra.ExactArgs(1), diff --git a/cmd/branch/branch.go b/cmd/branch/branch.go index 993750d..ecc0d1e 100644 --- a/cmd/branch/branch.go +++ b/cmd/branch/branch.go @@ -18,6 +18,11 @@ type Branch struct { DefaultMergeStrategy string `json:"default_merge_strategy,omitempty" mapstructure:"default_merge_strategy"` } +type BranchReference struct { + Type string `json:"type" mapstructure:"type"` + Name string `json:"name" mapstructure:"name"` +} + // Command represents this folder's command var Command = &cobra.Command{ Use: "branch", @@ -30,6 +35,14 @@ var Command = &cobra.Command{ }, } +// NewReference creates a new BranchReference +func NewReference(name string) *BranchReference { + return &BranchReference{ + Type: "branch", + Name: name, + } +} + // GetHeader gets the header for a table // // implements common.Tableable diff --git a/cmd/common/link.go b/cmd/common/link.go index f8f81f0..671b4e1 100644 --- a/cmd/common/link.go +++ b/cmd/common/link.go @@ -9,20 +9,32 @@ import ( ) type Link struct { - HREF url.URL `json:"href" mapstructure:"href"` + Name string `json:"name,omitempty" mapstructure:"name"` + HREF url.URL `json:"-"` + GitRef string `json:"-"` } // MarshalJSON implements the json.Marshaler interface. func (link Link) MarshalJSON() (data []byte, err error) { type surrogate Link - data, err = json.Marshal(struct { - surrogate - HREF core.URL `json:"href"` - }{ - surrogate: surrogate(link), - HREF: core.URL(link.HREF), - }) + if len(link.GitRef) > 0 { + data, err = json.Marshal(struct { + surrogate + GitRef string `json:"href"` + }{ + surrogate: surrogate(link), + GitRef: link.GitRef, + }) + } else { + data, err = json.Marshal(struct { + surrogate + HREF core.URL `json:"href"` + }{ + surrogate: surrogate(link), + HREF: core.URL(link.HREF), + }) + } return data, errors.JSONMarshalError.Wrap(err) } @@ -30,17 +42,40 @@ func (link Link) MarshalJSON() (data []byte, err error) { func (link *Link) UnmarshalJSON(data []byte) (err error) { type surrogate Link - var inner struct { - surrogate - HREF core.URL `json:"href"` + var header struct { + Name string `json:"name"` } - - if err = json.Unmarshal(data, &inner); errors.Is(err, errors.JSONUnmarshalError) { + if err = json.Unmarshal(data, &header); errors.Is(err, errors.JSONUnmarshalError) { return err } else if err != nil { return errors.JSONUnmarshalError.Wrap(err) } - *link = Link(inner.surrogate) - link.HREF = inner.HREF.AsURL() + switch header.Name { + case "ssh": + var inner struct { + surrogate + GitRef string `json:"href"` + } + if err = json.Unmarshal(data, &inner); errors.Is(err, errors.JSONUnmarshalError) { + return err + } else if err != nil { + return errors.JSONUnmarshalError.Wrap(err) + } + *link = Link(inner.surrogate) + link.GitRef = inner.GitRef + default: + var inner struct { + surrogate + HREF core.URL `json:"href"` + } + + if err = json.Unmarshal(data, &inner); errors.Is(err, errors.JSONUnmarshalError) { + return err + } else if err != nil { + return errors.JSONUnmarshalError.Wrap(err) + } + *link = Link(inner.surrogate) + link.HREF = inner.HREF.AsURL() + } return } diff --git a/cmd/common/links.go b/cmd/common/links.go index 1fc4a07..b4e60c8 100644 --- a/cmd/common/links.go +++ b/cmd/common/links.go @@ -1,18 +1,27 @@ package common type Links struct { - Self *Link `json:"self,omitempty" mapstructure:"self"` - HTML *Link `json:"html,omitempty" mapstructure:"html"` - Avatar *Link `json:"avatar,omitempty" mapstructure:"avatar"` - Commits *Link `json:"commits,omitempty" mapstructure:"commits"` - Approve *Link `json:"approve,omitempty" mapstructure:"approve"` - RequestChanges *Link `json:"request-changes,omitempty" mapstructure:"request-changes"` - Diff *Link `json:"diff,omitempty" mapstructure:"diff"` - DiffStat *Link `json:"diffstat,omitempty" mapstructure:"diffstat"` - Patch *Link `json:"patch,omitempty" mapstructure:"patch"` - Comments *Link `json:"comments,omitempty" mapstructure:"comments"` - Activity *Link `json:"activity,omitempty" mapstructure:"activity"` - Merge *Link `json:"merge,omitempty" mapstructure:"merge"` - Decline *Link `json:"decline,omitempty" mapstructure:"decline"` - Statuses *Link `json:"statuses,omitempty" mapstructure:"statuses"` + Self *Link `json:"self,omitempty" mapstructure:"self"` + HTML *Link `json:"html,omitempty" mapstructure:"html"` + Avatar *Link `json:"avatar,omitempty" mapstructure:"avatar"` + Branches *Link `json:"branches,omitempty" mapstructure:"branches"` + Forks *Link `json:"forks,omitempty" mapstructure:"forks"` + Commits *Link `json:"commits,omitempty" mapstructure:"commits"` + PullRequests *Link `json:"pullrequests,omitempty" mapstructure:"pullrequests"` + Approve *Link `json:"approve,omitempty" mapstructure:"approve"` + RequestChanges *Link `json:"request-changes,omitempty" mapstructure:"request-changes"` + Diff *Link `json:"diff,omitempty" mapstructure:"diff"` + DiffStat *Link `json:"diffstat,omitempty" mapstructure:"diffstat"` + Patch *Link `json:"patch,omitempty" mapstructure:"patch"` + Comments *Link `json:"comments,omitempty" mapstructure:"comments"` + Activity *Link `json:"activity,omitempty" mapstructure:"activity"` + Merge *Link `json:"merge,omitempty" mapstructure:"merge"` + Decline *Link `json:"decline,omitempty" mapstructure:"decline"` + Statuses *Link `json:"statuses,omitempty" mapstructure:"statuses"` + Tags *Link `json:"tags,omitempty" mapstructure:"tags"` + Watchers *Link `json:"watchers,omitempty" mapstructure:"watchers"` + Downloads *Link `json:"downloads,omitempty" mapstructure:"downloads"` + Source *Link `json:"source,omitempty" mapstructure:"source"` + Clone []Link `json:"clone,omitempty" mapstructure:"clone"` + Hooks *Link `json:"hooks,omitempty" mapstructure:"hooks"` } diff --git a/cmd/common/remote_value_flag.go b/cmd/common/remote_value_flag.go index d19cf73..7cbf9ed 100644 --- a/cmd/common/remote_value_flag.go +++ b/cmd/common/remote_value_flag.go @@ -7,7 +7,7 @@ import ( ) type RemoteValueFlag struct { - AllowedFunc func(context.Context, *cobra.Command) []string + AllowedFunc func(context.Context, *cobra.Command, []string) []string Value string } @@ -31,7 +31,7 @@ func (flag *RemoteValueFlag) Set(value string) error { func (flag *RemoteValueFlag) CompletionFunc() func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if flag.AllowedFunc != nil { - return flag.AllowedFunc(cmd.Context(), cmd), cobra.ShellCompDirectiveNoFileComp + return flag.AllowedFunc(cmd.Context(), cmd, args), cobra.ShellCompDirectiveNoFileComp } return []string{}, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/component/component.go b/cmd/component/component.go index f93d5da..3e6e944 100644 --- a/cmd/component/component.go +++ b/cmd/component/component.go @@ -63,8 +63,8 @@ func GetComponentIDs(context context.Context, cmd *cobra.Command, currentProfile return []string{} } ids = make([]string, 0, len(components)) - for _, issue := range components { - ids = append(ids, fmt.Sprintf("%d", issue.ID)) + for _, component := range components { + ids = append(ids, fmt.Sprintf("%d", component.ID)) } return } diff --git a/cmd/component/get.go b/cmd/component/get.go index 2427e64..af9f31e 100644 --- a/cmd/component/get.go +++ b/cmd/component/get.go @@ -11,9 +11,9 @@ import ( ) var getCmd = &cobra.Command{ - Use: "get", + Use: "get [flags] ", Aliases: []string{"show", "info", "display"}, - Short: "get a workspace", + Short: "get a component by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: getValidArgs, RunE: getProcess, diff --git a/cmd/issue/attachment/attachment.go b/cmd/issue/attachment/attachment.go index df7250a..04672c6 100644 --- a/cmd/issue/attachment/attachment.go +++ b/cmd/issue/attachment/attachment.go @@ -61,7 +61,7 @@ func (attachment Attachment) String() string { } // GetIssueIDs gets the IDs of the issues -func GetIssueIDs(context context.Context, cmd *cobra.Command) (ids []string) { +func GetIssueIDs(context context.Context, cmd *cobra.Command, args []string) (ids []string) { log := logger.Must(logger.FromContext(context)).Child("issue", "getids") type Issue struct { diff --git a/cmd/issue/attachment/delete.go b/cmd/issue/attachment/delete.go index 8ac1586..1a1264c 100644 --- a/cmd/issue/attachment/delete.go +++ b/cmd/issue/attachment/delete.go @@ -12,9 +12,9 @@ import ( ) var deleteCmd = &cobra.Command{ - Use: "delete", + Use: "delete [flags] ", Aliases: []string{"remove", "rm"}, - Short: "delete an issue attaachment", + Short: "delete an issue attachment by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: deleteValidArgs, RunE: deleteProcess, diff --git a/cmd/issue/attachment/download.go b/cmd/issue/attachment/download.go index 5f79d60..119bf74 100644 --- a/cmd/issue/attachment/download.go +++ b/cmd/issue/attachment/download.go @@ -12,9 +12,9 @@ import ( ) var downloadCmd = &cobra.Command{ - Use: "download", + Use: "download [flags] ", Aliases: []string{"get", "fetch"}, - Short: "download an issue attachment", + Short: "download an issue attachment by its .", ValidArgsFunction: downloadValidArgs, Args: cobra.ExactArgs(1), RunE: downloadProcess, diff --git a/cmd/issue/attachment/upload.go b/cmd/issue/attachment/upload.go index b6e8d9a..59a7637 100644 --- a/cmd/issue/attachment/upload.go +++ b/cmd/issue/attachment/upload.go @@ -12,9 +12,9 @@ import ( ) var uploadCmd = &cobra.Command{ - Use: "upload", + Use: "upload [flags] ", Aliases: []string{"add", "create"}, - Short: "upload an artifact", + Short: "upload an artifact.", Args: cobra.ExactArgs(1), RunE: uploadProcess, } diff --git a/cmd/issue/comment/comment.go b/cmd/issue/comment/comment.go index f162b1b..30455fa 100644 --- a/cmd/issue/comment/comment.go +++ b/cmd/issue/comment/comment.go @@ -114,7 +114,7 @@ func (comment Comment) MarshalJSON() (data []byte, err error) { } // GetIssueIDs gets the IDs of the issues -func GetIssueIDs(context context.Context, cmd *cobra.Command) (ids []string) { +func GetIssueIDs(context context.Context, cmd *cobra.Command, args []string) (ids []string) { log := logger.Must(logger.FromContext(context)).Child("issue", "getids") type Issue struct { diff --git a/cmd/issue/comment/delete.go b/cmd/issue/comment/delete.go index 698db7b..de6fe5c 100644 --- a/cmd/issue/comment/delete.go +++ b/cmd/issue/comment/delete.go @@ -12,9 +12,9 @@ import ( ) var deleteCmd = &cobra.Command{ - Use: "delete", + Use: "delete [flags] .", Args: cobra.ExactArgs(1), ValidArgsFunction: deleteValidArgs, RunE: deleteProcess, diff --git a/cmd/issue/comment/get.go b/cmd/issue/comment/get.go index 7225321..b2b93f4 100644 --- a/cmd/issue/comment/get.go +++ b/cmd/issue/comment/get.go @@ -12,9 +12,9 @@ import ( ) var getCmd = &cobra.Command{ - Use: "get", + Use: "get [flags] ", Aliases: []string{"show", "info", "display"}, - Short: "get an issue comment", + Short: "get an issue comment by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: getValidArgs, RunE: getProcess, diff --git a/cmd/issue/comment/update.go b/cmd/issue/comment/update.go index a6c2213..d86225a 100644 --- a/cmd/issue/comment/update.go +++ b/cmd/issue/comment/update.go @@ -16,9 +16,9 @@ type CommentUpdator struct { } var updateCmd = &cobra.Command{ - Use: "update", + Use: "update [flags] ", Aliases: []string{"edit"}, - Short: "update an issue comment", + Short: "update an issue comment by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: updateValidArgs, RunE: updateProcess, diff --git a/cmd/issue/delete.go b/cmd/issue/delete.go index 3073e6b..ec05ae0 100644 --- a/cmd/issue/delete.go +++ b/cmd/issue/delete.go @@ -11,9 +11,9 @@ import ( ) var deleteCmd = &cobra.Command{ - Use: "delete", + Use: "delete [flags] ", Aliases: []string{"remove", "rm"}, - Short: "delete an issue by its id", + Short: "delete an issue by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: deleteValidArgs, RunE: deleteProcess, diff --git a/cmd/issue/get.go b/cmd/issue/get.go index 94d1690..eb458e8 100644 --- a/cmd/issue/get.go +++ b/cmd/issue/get.go @@ -11,9 +11,9 @@ import ( ) var getCmd = &cobra.Command{ - Use: "get", + Use: "get [flags] ", Aliases: []string{"show", "info", "display"}, - Short: "get an issue", + Short: "get an issue by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: getValidArgs, RunE: getProcess, diff --git a/cmd/issue/list.go b/cmd/issue/list.go index 138bc96..ad190fb 100644 --- a/cmd/issue/list.go +++ b/cmd/issue/list.go @@ -1,6 +1,9 @@ package issue import ( + "fmt" + + "bitbucket.org/gildas_cherruel/bb/cmd/common" "bitbucket.org/gildas_cherruel/bb/cmd/profile" "github.com/gildas/go-errors" "github.com/gildas/go-logger" @@ -16,12 +19,15 @@ var listCmd = &cobra.Command{ var listOptions struct { Repository string + State common.EnumFlag } func init() { Command.AddCommand(listCmd) + listOptions.State = common.EnumFlag{Allowed: []string{"all", "closed", "duplicate", "invalid", "on hold", "new", "open", "resolved", "submitted", "wontfix"}, Value: "all"} listCmd.Flags().StringVar(&listOptions.Repository, "repository", "", "Repository to list issues from. Defaults to the current repository") + listCmd.Flags().Var(&listOptions.State, "state", "State of the issues to list") } func listProcess(cmd *cobra.Command, args []string) (err error) { @@ -31,8 +37,13 @@ func listProcess(cmd *cobra.Command, args []string) (err error) { return errors.ArgumentMissing.With("profile") } + filter := "" + if listOptions.State.Value != "all" { + filter = fmt.Sprintf(`?q=state="%s"`, listOptions.State.Value) + } + log.Infof("Listing all issues from repository %s with profile %s", listOptions.Repository, profile.Current) - issues, err := profile.GetAll[Issue](cmd.Context(), cmd, profile.Current, "issues") + issues, err := profile.GetAll[Issue](cmd.Context(), cmd, profile.Current, "issues"+filter) if err != nil { return err } diff --git a/cmd/issue/unvote.go b/cmd/issue/unvote.go index 4520ab1..375c525 100644 --- a/cmd/issue/unvote.go +++ b/cmd/issue/unvote.go @@ -11,8 +11,8 @@ import ( ) var unvoteCmd = &cobra.Command{ - Use: "unvote", - Short: "remove vote for an issue", + Use: "unvote [flags] ", + Short: "remove vote for an issue by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: unvoteValidArgs, RunE: unvoteProcess, diff --git a/cmd/issue/unwatch.go b/cmd/issue/unwatch.go index c08dd5a..ff19ab8 100644 --- a/cmd/issue/unwatch.go +++ b/cmd/issue/unwatch.go @@ -11,8 +11,8 @@ import ( ) var unwatchCmd = &cobra.Command{ - Use: "unwatch", - Short: "stop watching an issue", + Use: "unwatch [flags] ", + Short: "stop watching an issue by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: unwatchValidArgs, RunE: unwatchProcess, diff --git a/cmd/issue/vote.go b/cmd/issue/vote.go index 98cb90a..5554274 100644 --- a/cmd/issue/vote.go +++ b/cmd/issue/vote.go @@ -11,8 +11,8 @@ import ( ) var voteCmd = &cobra.Command{ - Use: "vote", - Short: "vote for an issue", + Use: "vote [flags] ", + Short: "vote for an issue by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: voteValidArgs, RunE: voteProcess, diff --git a/cmd/issue/watch.go b/cmd/issue/watch.go index de5a616..366bfb5 100644 --- a/cmd/issue/watch.go +++ b/cmd/issue/watch.go @@ -11,8 +11,8 @@ import ( ) var watchCmd = &cobra.Command{ - Use: "watch", - Short: "watch an issue", + Use: "watch [flags] ", + Short: "watch an issue by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: watchValidArgs, RunE: watchProcess, diff --git a/cmd/profile/create.go b/cmd/profile/create.go index 53fdaf9..5ee2fb9 100644 --- a/cmd/profile/create.go +++ b/cmd/profile/create.go @@ -21,12 +21,16 @@ var createCmd = &cobra.Command{ var createOptions struct { Profile - OutputFormat common.EnumFlag + DefaultWorkspace common.RemoteValueFlag + DefaultProject common.RemoteValueFlag + OutputFormat common.EnumFlag } func init() { Command.AddCommand(createCmd) + createOptions.DefaultWorkspace = common.RemoteValueFlag{AllowedFunc: getWorkspaceSlugs} + createOptions.DefaultProject = common.RemoteValueFlag{AllowedFunc: getProjectKeys} createOptions.OutputFormat = common.EnumFlag{Allowed: []string{"json", "yaml", "table"}, Value: ""} createCmd.Flags().StringVarP(&createOptions.Name, "name", "n", "", "Name of the profile") createCmd.Flags().StringVar(&createOptions.Description, "description", "", "Description of the profile") @@ -36,6 +40,8 @@ func init() { createCmd.Flags().StringVar(&createOptions.ClientID, "client-id", "", "Client ID of the profile") createCmd.Flags().StringVar(&createOptions.ClientSecret, "client-secret", "", "Client Secret of the profile") createCmd.Flags().StringVar(&createOptions.AccessToken, "access-token", "", "Access Token of the profile") + createCmd.Flags().Var(&createOptions.DefaultWorkspace, "default-workspace", "Default workspace of the profile") + createCmd.Flags().Var(&createOptions.DefaultProject, "default-project", "Default project of the profile") createCmd.Flags().Var(&createOptions.OutputFormat, "output", "Output format (json, yaml, table).") _ = createCmd.MarkFlagRequired("name") createCmd.MarkFlagsRequiredTogether("user", "password") @@ -47,10 +53,16 @@ func init() { func createProcess(cmd *cobra.Command, args []string) error { log := logger.Must(logger.FromContext(cmd.Context())).Child(cmd.Parent().Name(), "create") - log.Infof("Creating profile %s", createOptions.Name) + if len(createOptions.DefaultWorkspace.String()) > 0 { + createOptions.Profile.DefaultWorkspace = createOptions.DefaultWorkspace.String() + } + if len(createOptions.DefaultProject.String()) > 0 { + createOptions.Profile.DefaultProject = createOptions.DefaultProject.String() + } if len(createOptions.OutputFormat.String()) > 0 { createOptions.Profile.OutputFormat = createOptions.OutputFormat.String() } + log.Infof("Creating profile %s", createOptions.Name) if err := createOptions.Validate(); err != nil { return err } diff --git a/cmd/profile/delete.go b/cmd/profile/delete.go index 93f4cd8..521938c 100644 --- a/cmd/profile/delete.go +++ b/cmd/profile/delete.go @@ -9,9 +9,9 @@ import ( ) var deleteCmd = &cobra.Command{ - Use: "delete", + Use: "delete [flags] ", Aliases: []string{"remove", "rm"}, - Short: "delete a profile", + Short: "delete a profile by its .", Args: cobra.MinimumNArgs(1), ValidArgsFunction: ValidProfileNames, RunE: deleteProcess, diff --git a/cmd/profile/get.go b/cmd/profile/get.go index e308e53..7fd6879 100644 --- a/cmd/profile/get.go +++ b/cmd/profile/get.go @@ -7,21 +7,35 @@ import ( ) var getCmd = &cobra.Command{ - Use: "get", + Use: "get [flags] ", Aliases: []string{"show", "info", "display"}, - Short: "get a profile", - Args: cobra.ExactArgs(1), + Short: "get a profile by its .", ValidArgsFunction: ValidProfileNames, RunE: getProcess, } +var getOptions struct { + Current bool +} + func init() { Command.AddCommand(getCmd) + + getCmd.Flags().BoolVar(&getOptions.Current, "current", false, "Get the current profile") } func getProcess(cmd *cobra.Command, args []string) error { log := logger.Must(logger.FromContext(cmd.Context())).Child(cmd.Parent().Name(), "get") + if getOptions.Current { + log.Infof("Displaying current profile") + return Current.Print(cmd.Context(), Current) + } + + if len(args) == 0 { + return errors.ArgumentMissing.With("profile") + } + log.Infof("Displaying profile %s (Valid names: %v)", args[0], Profiles.Names()) profile, found := Profiles.Find(args[0]) if !found { diff --git a/cmd/profile/profile.go b/cmd/profile/profile.go index 49ab2ec..681b1e8 100644 --- a/cmd/profile/profile.go +++ b/cmd/profile/profile.go @@ -21,18 +21,20 @@ import ( // Profile describes the configuration needed to connect to BitBucket type Profile struct { - Name string `json:"name" mapstructure:"name"` - Description string `json:"description,omitempty" mapstructure:"description,omitempty" yaml:",omitempty"` - Default bool `json:"default" mapstructure:"default" yaml:",omitempty"` - OutputFormat string `json:"outputFormat,omitempty" mapstructure:"outputFormat,omitempty" yaml:",omitempty"` - User string `json:"user,omitempty" mapstructure:"user" yaml:",omitempty"` - Password string `json:"password,omitempty" mapstructure:"password" yaml:",omitempty"` - ClientID string `json:"clientID,omitempty" mapstructure:"clientID" yaml:",omitempty"` - ClientSecret string `json:"clientSecret,omitempty" mapstructure:"clientSecret" yaml:",omitempty"` - AccessToken string `json:"accessToken,omitempty" mapstructure:"accessToken" yaml:",omitempty"` - RefreshToken string `json:"-" mapstructure:"refreshToken" yaml:"-"` - TokenExpires time.Time `json:"-" mapstructure:"tokenExpires" yaml:"-"` - TokenScopes []string `json:"-" mapstructure:"tokenScopes" yaml:"-"` + Name string `json:"name" mapstructure:"name"` + Description string `json:"description,omitempty" mapstructure:"description,omitempty" yaml:",omitempty"` + Default bool `json:"default" mapstructure:"default" yaml:",omitempty"` + DefaultWorkspace string `json:"defaultWorkspace,omitempty" mapstructure:"defaultWorkspace" yaml:",omitempty"` + DefaultProject string `json:"defaultProject,omitempty" mapstructure:"defaultProject" yaml:",omitempty"` + OutputFormat string `json:"outputFormat,omitempty" mapstructure:"outputFormat,omitempty" yaml:",omitempty"` + User string `json:"user,omitempty" mapstructure:"user" yaml:",omitempty"` + Password string `json:"password,omitempty" mapstructure:"password" yaml:",omitempty"` + ClientID string `json:"clientID,omitempty" mapstructure:"clientID" yaml:",omitempty"` + ClientSecret string `json:"clientSecret,omitempty" mapstructure:"clientSecret" yaml:",omitempty"` + AccessToken string `json:"accessToken,omitempty" mapstructure:"accessToken" yaml:",omitempty"` + RefreshToken string `json:"-" mapstructure:"refreshToken" yaml:"-"` + TokenExpires time.Time `json:"-" mapstructure:"tokenExpires" yaml:"-"` + TokenScopes []string `json:"-" mapstructure:"tokenScopes" yaml:"-"` } // Current is the current profile @@ -122,6 +124,12 @@ func (profile *Profile) Update(other Profile) error { profile.TokenExpires = time.Time{} profile.TokenScopes = []string{} } + if len(other.DefaultWorkspace) > 0 { + profile.DefaultWorkspace = other.DefaultWorkspace + } + if len(other.DefaultProject) > 0 { + profile.DefaultProject = other.DefaultProject + } return profile.Validate() } @@ -375,3 +383,83 @@ func (profile *Profile) getTokenData() (data []byte) { data, _ = json.Marshal(token) return } + +// getProfileFromCmd gets the profile from the command line +func getProfileFromCmd(context context.Context, cmd *cobra.Command, args []string) (profile *Profile, err error) { + log := logger.Must(logger.FromContext(context)).Child("profile", "getfromcmd") + + if len(args) > 0 { + log.Debugf("Getting profile %s", args[0]) + var found bool + + profile, found = Profiles.Find(args[0]) + if !found { + log.Infof("Profile %s not found", args[0]) + return nil, errors.NotFound.With("profile", args[0]) + } + } else { + profile = &Profile{} + } + if clientID := cmd.Flag("client-id").Value.String(); len(clientID) > 0 { + profile.ClientID = clientID + } + if clientSecret := cmd.Flag("client-secret").Value.String(); len(clientSecret) > 0 { + profile.ClientSecret = clientSecret + } + if accessToken := cmd.Flag("access-token").Value.String(); len(accessToken) > 0 { + profile.AccessToken = accessToken + } + return +} + +// getWorkspaceSlugs gets the slugs of all workspaces +func getWorkspaceSlugs(context context.Context, cmd *cobra.Command, args []string) (slugs []string) { + log := logger.Must(logger.FromContext(context)).Child("workspace", "slugs") + type Workspace struct { + Slug string `json:"slug"` + } + + profile, err := getProfileFromCmd(context, cmd, args) + if err != nil { + return []string{} + } + + log.Debugf("Getting all workspaces") + workspaces, err := GetAll[Workspace](context, cmd, profile, "/workspaces") + if err != nil { + log.Errorf("Failed to get workspaces for profile %s", profile, err) + return []string{} + } + return core.Map(workspaces, func(workspace Workspace) string { + return workspace.Slug + }) +} + +// getProjectKeys gets the keys of all projects +func getProjectKeys(context context.Context, cmd *cobra.Command, args []string) (keys []string) { + log := logger.Must(logger.FromContext(context)).Child("project", "keys") + type Project struct { + Key string `json:"key"` + } + + profile, err := getProfileFromCmd(context, cmd, args) + if err != nil { + return []string{} + } + + workspace := cmd.Flag("default-workspace").Value.String() + if len(workspace) == 0 { + log.Warnf("No workspace given") + return + } + + log.Debugf("Getting all projects in workspace %s", workspace) + projects, err := GetAll[Project](context, cmd, profile, fmt.Sprintf("/workspaces/%s/projects", workspace)) + if err != nil { + log.Errorf("Failed to get projects", err) + return + } + return core.Map(projects, func(project Project) string { + return project.Key + }) +} diff --git a/cmd/profile/profile_client.go b/cmd/profile/profile_client.go index 87fd3c2..54f2090 100644 --- a/cmd/profile/profile_client.go +++ b/cmd/profile/profile_client.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "bitbucket.org/gildas_cherruel/bb/cmd/repository" + "bitbucket.org/gildas_cherruel/bb/cmd/remote" "github.com/gildas/go-core" "github.com/gildas/go-errors" "github.com/gildas/go-logger" @@ -110,7 +110,7 @@ func (profile *Profile) Download(context context.Context, cmd *cobra.Command, ur return errors.RuntimeError.Wrap(err) } - log.Debugf("Downloading artifact to %s", writer.Name()) + log.Debugf("Downloading data to %s", writer.Name()) options := &request.Options{ Method: http.MethodGet, Timeout: 15 * time.Minute, @@ -220,12 +220,12 @@ func (profile *Profile) send(context context.Context, cmd *cobra.Command, option if strings.HasPrefix(uripath, "/") { uripath = fmt.Sprintf("https://api.bitbucket.org/2.0%s", uripath) } else if !strings.HasPrefix(uripath, "http") { - repository, err := repository.GetRepository(context, cmd) + repositoryName, err := profile.getRepositoryFullname(context, cmd) if err != nil { return nil, err } - log.Infof("Using repository %s", repository) - uripath = fmt.Sprintf("https://api.bitbucket.org/2.0/repositories/%s/%s", repository, uripath) + log.Infof("Using repository %s", repositoryName) + uripath = fmt.Sprintf("https://api.bitbucket.org/2.0/repositories/%s/%s", repositoryName, uripath) } options.URL, err = url.Parse(uripath) @@ -252,3 +252,18 @@ func (profile *Profile) send(context context.Context, cmd *cobra.Command, option } return } + +func (profile Profile) getRepositoryFullname(context context.Context, cmd *cobra.Command) (string, error) { + log := logger.Must(logger.FromContext(context)).Child("profile", "getrepositoryname") + + fullName := cmd.Flag("repository").Value.String() + if len(fullName) == 0 { + log.Debugf("No repository name given, trying to get it from the current git repository") + remote, err := remote.GetFromGitConfig(context, "origin") + if err != nil { + return "", errors.Join(errors.NotFound.With("current repository"), err) + } + fullName = remote.RepositoryName() + } + return fullName, nil +} diff --git a/cmd/profile/update.go b/cmd/profile/update.go index ccfd829..2df2f02 100644 --- a/cmd/profile/update.go +++ b/cmd/profile/update.go @@ -12,9 +12,9 @@ import ( ) var updateCmd = &cobra.Command{ - Use: "update", + Use: "update [flags] ", Aliases: []string{"edit"}, - Short: "update a profile", + Short: "update a profile by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: ValidProfileNames, RunE: updateProcess, @@ -22,12 +22,16 @@ var updateCmd = &cobra.Command{ var updateOptions struct { Profile - OutputFormat common.EnumFlag + DefaultWorkspace common.RemoteValueFlag + DefaultProject common.RemoteValueFlag + OutputFormat common.EnumFlag } func init() { Command.AddCommand(updateCmd) + updateOptions.DefaultWorkspace = common.RemoteValueFlag{AllowedFunc: getWorkspaceSlugs} + updateOptions.DefaultProject = common.RemoteValueFlag{AllowedFunc: getProjectKeys} updateOptions.OutputFormat = common.EnumFlag{Allowed: []string{"json", "yaml", "table"}, Value: ""} updateCmd.Flags().StringVarP(&updateOptions.Name, "name", "n", "", "Name of the profile") updateCmd.Flags().StringVar(&updateOptions.Description, "description", "", "Description of the profile") @@ -37,20 +41,30 @@ func init() { updateCmd.Flags().StringVar(&updateOptions.ClientID, "client-id", "", "Client ID of the profile") updateCmd.Flags().StringVar(&updateOptions.ClientSecret, "client-secret", "", "Client Secret of the profile") updateCmd.Flags().StringVar(&updateOptions.AccessToken, "access-token", "", "Access Token of the profile") + updateCmd.Flags().Var(&updateOptions.DefaultWorkspace, "default-workspace", "Default workspace of the profile") + updateCmd.Flags().Var(&updateOptions.DefaultProject, "default-project", "Default project of the profile") updateCmd.Flags().Var(&updateOptions.OutputFormat, "output", "Output format (json, yaml, table).") updateCmd.MarkFlagsRequiredTogether("user", "password") updateCmd.MarkFlagsRequiredTogether("client-id", "client-secret") updateCmd.MarkFlagsMutuallyExclusive("user", "client-id", "access-token") + _ = updateCmd.RegisterFlagCompletionFunc("default-workspace", updateOptions.DefaultWorkspace.CompletionFunc()) + _ = updateCmd.RegisterFlagCompletionFunc("default-project", updateOptions.DefaultProject.CompletionFunc()) _ = updateCmd.RegisterFlagCompletionFunc("output", updateOptions.OutputFormat.CompletionFunc()) } func updateProcess(cmd *cobra.Command, args []string) error { log := logger.Must(logger.FromContext(cmd.Context())).Child(cmd.Parent().Name(), "update") + if len(updateOptions.DefaultWorkspace.String()) > 0 { + updateOptions.Profile.DefaultWorkspace = updateOptions.DefaultWorkspace.String() + } + if len(updateOptions.DefaultProject.String()) > 0 { + updateOptions.Profile.DefaultProject = updateOptions.DefaultProject.String() + } if len(updateOptions.OutputFormat.String()) > 0 { updateOptions.Profile.OutputFormat = updateOptions.OutputFormat.String() } - log.Infof("Updating profile %s (Valid Names: %v)", args[0], Profiles.Names()) + log.Infof("Checking if profile %s exists (Valid Names: %v)", args[0], Profiles.Names()) profile, found := Profiles.Find(args[0]) if !found { return errors.NotFound.With("profile", args[0]) diff --git a/cmd/profile/use.go b/cmd/profile/use.go index eda3fb2..626e237 100644 --- a/cmd/profile/use.go +++ b/cmd/profile/use.go @@ -8,9 +8,9 @@ import ( ) var useCmd = &cobra.Command{ - Use: "use", + Use: "use [flags] ", Aliases: []string{"default"}, - Short: "set the default profile", + Short: "set the default profile by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: ValidProfileNames, RunE: useProcess, diff --git a/cmd/project/create.go b/cmd/project/create.go index 07fd6f9..95eff24 100644 --- a/cmd/project/create.go +++ b/cmd/project/create.go @@ -52,7 +52,6 @@ func init() { createCmd.Flags().StringVar(&createOptions.AvatarURL, "avatar-url", "", "Avatar of the project") createCmd.Flags().StringVar(&createOptions.AvatarPath, "avatar-file", "", "Avatar of the project") createCmd.Flags().BoolVar(&createOptions.IsPrivate, "is-private", false, "Is the project private") - _ = createCmd.MarkFlagRequired("workspace") _ = createCmd.MarkFlagRequired("name") _ = createCmd.MarkFlagRequired("key") _ = createCmd.MarkFlagFilename("avatar-file") @@ -66,6 +65,12 @@ func createProcess(cmd *cobra.Command, args []string) (err error) { if profile.Current == nil { return errors.ArgumentMissing.With("profile") } + if len(createOptions.Workspace.Value) == 0 { + createOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(createOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } payload := ProjectCreator{ Name: createOptions.Name, diff --git a/cmd/project/delete.go b/cmd/project/delete.go index 6261cbf..a99ef9b 100644 --- a/cmd/project/delete.go +++ b/cmd/project/delete.go @@ -13,9 +13,9 @@ import ( ) var deleteCmd = &cobra.Command{ - Use: "delete", + Use: "delete [flags] ", Aliases: []string{"remove", "rm"}, - Short: "delete a project by its key", + Short: "delete a project by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: deleteValidArgs, RunE: deleteProcess, @@ -30,7 +30,6 @@ func init() { deleteOptions.Workspace = common.RemoteValueFlag{AllowedFunc: workspace.GetWorkspaceSlugs} deleteCmd.Flags().Var(&deleteOptions.Workspace, "workspace", "Workspace to delete projects from") - _ = deleteCmd.MarkFlagRequired("workspace") } func deleteValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -41,7 +40,7 @@ func deleteValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]st if profile.Current == nil { return []string{}, cobra.ShellCompDirectiveNoFileComp } - return GetProjectKeys(cmd.Context(), cmd, profile.Current, deleteOptions.Workspace.String()), cobra.ShellCompDirectiveNoFileComp + return GetProjectKeys(cmd.Context(), cmd, args), cobra.ShellCompDirectiveNoFileComp } func deleteProcess(cmd *cobra.Command, args []string) error { @@ -50,6 +49,12 @@ func deleteProcess(cmd *cobra.Command, args []string) error { if profile.Current == nil { return errors.ArgumentMissing.With("profile") } + if len(deleteOptions.Workspace.Value) == 0 { + deleteOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(deleteOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } log.Infof("Deleting project %s", args[0]) err := profile.Current.Delete( diff --git a/cmd/project/get.go b/cmd/project/get.go index 3a9a22f..759e5c3 100644 --- a/cmd/project/get.go +++ b/cmd/project/get.go @@ -13,9 +13,9 @@ import ( ) var getCmd = &cobra.Command{ - Use: "get", + Use: "get [flags] ", Aliases: []string{"show", "info", "display"}, - Short: "get a project", + Short: "get a project by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: getValidArgs, RunE: getProcess, @@ -30,7 +30,6 @@ func init() { getOptions.Workspace = common.RemoteValueFlag{AllowedFunc: workspace.GetWorkspaceSlugs} getCmd.Flags().Var(&getOptions.Workspace, "workspace", "Workspace to get projects from") - _ = getCmd.MarkFlagRequired("workspace") _ = getCmd.RegisterFlagCompletionFunc("workspace", getOptions.Workspace.CompletionFunc()) } @@ -42,7 +41,7 @@ func getValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]strin if profile.Current == nil { return []string{}, cobra.ShellCompDirectiveNoFileComp } - return GetProjectKeys(cmd.Context(), cmd, profile.Current, deleteOptions.Workspace.String()), cobra.ShellCompDirectiveNoFileComp + return GetProjectKeys(cmd.Context(), cmd, args), cobra.ShellCompDirectiveNoFileComp } func getProcess(cmd *cobra.Command, args []string) error { @@ -51,6 +50,12 @@ func getProcess(cmd *cobra.Command, args []string) error { if profile.Current == nil { return errors.ArgumentMissing.With("profile") } + if len(getOptions.Workspace.Value) == 0 { + getOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(getOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } log.Infof("Displaying project %s", args[0]) var project Project diff --git a/cmd/project/list.go b/cmd/project/list.go index 474487a..609de6f 100644 --- a/cmd/project/list.go +++ b/cmd/project/list.go @@ -27,7 +27,6 @@ func init() { listOptions.Workspace = common.RemoteValueFlag{AllowedFunc: workspace.GetWorkspaceSlugs} listCmd.Flags().Var(&listOptions.Workspace, "workspace", "Workspace to list projects from") - _ = listCmd.MarkFlagRequired("workspace") _ = listCmd.RegisterFlagCompletionFunc("workspace", listOptions.Workspace.CompletionFunc()) } @@ -37,6 +36,12 @@ func listProcess(cmd *cobra.Command, args []string) (err error) { if profile.Current == nil { return errors.ArgumentMissing.With("profile") } + if len(listOptions.Workspace.Value) == 0 { + listOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(listOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } log.Infof("Listing all projects from workspace %s with profile %s", listOptions.Workspace, profile.Current) projects, err := profile.GetAll[Project]( diff --git a/cmd/project/project.go b/cmd/project/project.go index 016edff..e359795 100644 --- a/cmd/project/project.go +++ b/cmd/project/project.go @@ -32,6 +32,10 @@ type Project struct { UpdatedOn time.Time `json:"updated_on" mapstructure:"updated_on"` } +type ProjectReference struct { + Key string `json:"key" mapstructure:"key"` +} + // Command represents this folder's command var Command = &cobra.Command{ Use: "project", @@ -48,6 +52,13 @@ func init() { Command.AddCommand(reviewer.Command) } +// NewReference creates a new ProjectReference +func NewReference(key string) *ProjectReference { + return &ProjectReference{ + Key: key, + } +} + // GetHeader gets the header for a table // // implements common.Tableable @@ -79,24 +90,54 @@ func (project Project) String() string { // MarshalJSON implements the json.Marshaler interface. func (project Project) MarshalJSON() (data []byte, err error) { type surrogate Project + var owner *user.User + var wspace *workspace.Workspace + var createdOn string + var updatedOn string + + if !project.Owner.ID.IsNil() { + owner = &project.Owner + } + if !project.Workspace.ID.IsNil() { + wspace = &project.Workspace + } + if !project.CreatedOn.IsZero() { + createdOn = project.CreatedOn.Format("2006-01-02T15:04:05.999999999-07:00") + } + if !project.UpdatedOn.IsZero() { + updatedOn = project.UpdatedOn.Format("2006-01-02T15:04:05.999999999-07:00") + } data, err = json.Marshal(struct { surrogate - CreatedOn string `json:"created_on"` - UpdatedOn string `json:"updated_on"` + Owner *user.User `json:"owner,omitempty"` + Workspace *workspace.Workspace `json:"workspace,omitempty"` + CreatedOn string `json:"created_on,omitempty"` + UpdatedOn string `json:"updated_on,omitempty"` }{ surrogate: surrogate(project), - CreatedOn: project.CreatedOn.Format("2006-01-02T15:04:05.999999999-07:00"), - UpdatedOn: project.UpdatedOn.Format("2006-01-02T15:04:05.999999999-07:00"), + Owner: owner, + Workspace: wspace, + CreatedOn: createdOn, + UpdatedOn: updatedOn, }) return data, errors.JSONMarshalError.Wrap(err) } -// GetProjectKeys gets the keys of the projects in the given workspace -func GetProjectKeys(context context.Context, cmd *cobra.Command, currentProfile *profile.Profile, workspace string) (keys []string) { +// GetProjectKeys gets the keys of the projects in the workspace given in the command +func GetProjectKeys(context context.Context, cmd *cobra.Command, args []string) (keys []string) { log := logger.Must(logger.FromContext(context)).Child("project", "keys") - projects, err := profile.GetAll[Project](context, cmd, currentProfile, fmt.Sprintf("/workspaces/%s/projects", workspace)) + workspace := cmd.Flag("workspace").Value.String() + if len(workspace) == 0 { + workspace = profile.Current.DefaultWorkspace + if len(workspace) == 0 { + log.Warnf("No workspace given") + return + } + } + + projects, err := profile.GetAll[Project](context, cmd, profile.Current, fmt.Sprintf("/workspaces/%s/projects", workspace)) if err != nil { log.Errorf("Failed to get projects", err) return @@ -105,3 +146,27 @@ func GetProjectKeys(context context.Context, cmd *cobra.Command, currentProfile return project.Key }) } + +// GetProjectNames gets the names of the projects in the workspace given in the command +func GetProjectNames(context context.Context, cmd *cobra.Command, args []string) (names []string) { + log := logger.Must(logger.FromContext(context)).Child("project", "names") + + workspace := cmd.Flag("workspace").Value.String() + if len(workspace) == 0 { + workspace = profile.Current.DefaultWorkspace + if len(workspace) == 0 { + log.Warnf("No workspace given") + return + } + } + + log.Infof("Getting all projects from workspace %s", workspace) + projects, err := profile.GetAll[Project](context, cmd, profile.Current, fmt.Sprintf("/workspaces/%s/projects", workspace)) + if err != nil { + log.Errorf("Failed to get projects", err) + return + } + return core.Map(projects, func(project Project) string { + return project.Name + }) +} diff --git a/cmd/project/reviewer/add.go b/cmd/project/reviewer/add.go index 9f5478a..f529f2b 100644 --- a/cmd/project/reviewer/add.go +++ b/cmd/project/reviewer/add.go @@ -33,8 +33,6 @@ func init() { addOptions.Project = common.RemoteValueFlag{AllowedFunc: GetProjectKeys} addCmd.Flags().Var(&addOptions.Workspace, "workspace", "Workspace to add reviewers to") addCmd.Flags().Var(&addOptions.Project, "project", "Project Key to add reviewers to") - _ = addCmd.MarkFlagRequired("workspace") - _ = addCmd.MarkFlagRequired("project") _ = addCmd.RegisterFlagCompletionFunc("workspace", addOptions.Workspace.CompletionFunc()) _ = getCmd.RegisterFlagCompletionFunc("project", addOptions.Project.CompletionFunc()) } @@ -45,6 +43,18 @@ func addProcess(cmd *cobra.Command, args []string) error { if profile.Current == nil { return errors.ArgumentMissing.With("profile") } + if len(addOptions.Workspace.Value) == 0 { + addOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(addOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } + if len(addOptions.Project.Value) == 0 { + addOptions.Project.Value = profile.Current.DefaultProject + if len(addOptions.Project.Value) == 0 { + return errors.ArgumentMissing.With("project") + } + } log.Infof("Adding reviewer %s", args[0]) var user user.User diff --git a/cmd/project/reviewer/delete.go b/cmd/project/reviewer/delete.go index 621b94f..b46c73c 100644 --- a/cmd/project/reviewer/delete.go +++ b/cmd/project/reviewer/delete.go @@ -33,8 +33,6 @@ func init() { deleteOptions.Project = common.RemoteValueFlag{AllowedFunc: GetProjectKeys} deleteCmd.Flags().Var(&deleteOptions.Workspace, "workspace", "Workspace to delete reviewers from") deleteCmd.Flags().Var(&deleteOptions.Project, "project", "Project Key to delete reviewers from") - _ = deleteCmd.MarkFlagRequired("workspace") - _ = deleteCmd.MarkFlagRequired("project") _ = deleteCmd.RegisterFlagCompletionFunc("workspace", deleteOptions.Workspace.CompletionFunc()) _ = getCmd.RegisterFlagCompletionFunc("project", deleteOptions.Project.CompletionFunc()) } @@ -47,7 +45,14 @@ func deleteValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]st if profile.Current == nil { return []string{}, cobra.ShellCompDirectiveNoFileComp } - return GetReviewerUserIDs(cmd.Context(), cmd, profile.Current, deleteOptions.Workspace.Value, deleteOptions.Project.Value), cobra.ShellCompDirectiveNoFileComp + workspace := deleteOptions.Workspace.Value + if len(workspace) == 0 { + workspace = profile.Current.DefaultWorkspace + if len(workspace) == 0 { + return []string{}, cobra.ShellCompDirectiveNoFileComp + } + } + return GetReviewerUserIDs(cmd.Context(), cmd, profile.Current, workspace, deleteOptions.Project.Value), cobra.ShellCompDirectiveNoFileComp } func deleteProcess(cmd *cobra.Command, args []string) error { @@ -56,6 +61,18 @@ func deleteProcess(cmd *cobra.Command, args []string) error { if profile.Current == nil { return errors.ArgumentMissing.With("profile") } + if len(deleteOptions.Workspace.Value) == 0 { + deleteOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(deleteOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } + if len(deleteOptions.Project.Value) == 0 { + deleteOptions.Project.Value = profile.Current.DefaultProject + if len(deleteOptions.Project.Value) == 0 { + return errors.ArgumentMissing.With("project") + } + } log.Infof("deleteing reviewer %s", args[0]) diff --git a/cmd/project/reviewer/get.go b/cmd/project/reviewer/get.go index 2d6e1c7..bdf0097 100644 --- a/cmd/project/reviewer/get.go +++ b/cmd/project/reviewer/get.go @@ -34,8 +34,6 @@ func init() { getOptions.Project = common.RemoteValueFlag{AllowedFunc: GetProjectKeys} getCmd.Flags().Var(&getOptions.Workspace, "workspace", "Workspace to get reviewers from") getCmd.Flags().Var(&getOptions.Project, "project", "Project Key to get reviewers from") - _ = getCmd.MarkFlagRequired("workspace") - _ = getCmd.MarkFlagRequired("project") _ = getCmd.RegisterFlagCompletionFunc("workspace", getOptions.Workspace.CompletionFunc()) _ = getCmd.RegisterFlagCompletionFunc("project", getOptions.Project.CompletionFunc()) } @@ -48,7 +46,14 @@ func getValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]strin if profile.Current == nil { return []string{}, cobra.ShellCompDirectiveNoFileComp } - return GetReviewerUserIDs(cmd.Context(), cmd, profile.Current, getOptions.Workspace.Value, getOptions.Project.Value), cobra.ShellCompDirectiveNoFileComp + workspace := getOptions.Workspace.Value + if len(workspace) == 0 { + workspace = profile.Current.DefaultWorkspace + if len(workspace) == 0 { + return []string{}, cobra.ShellCompDirectiveNoFileComp + } + } + return GetReviewerUserIDs(cmd.Context(), cmd, profile.Current, workspace, getOptions.Project.Value), cobra.ShellCompDirectiveNoFileComp } func getProcess(cmd *cobra.Command, args []string) error { @@ -57,6 +62,18 @@ func getProcess(cmd *cobra.Command, args []string) error { if profile.Current == nil { return errors.ArgumentMissing.With("profile") } + if len(getOptions.Workspace.Value) == 0 { + getOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(getOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } + if len(getOptions.Project.Value) == 0 { + getOptions.Project.Value = profile.Current.DefaultProject + if len(getOptions.Project.Value) == 0 { + return errors.ArgumentMissing.With("project") + } + } log.Infof("Displaying reviewer %s", args[0]) var user user.User diff --git a/cmd/project/reviewer/list.go b/cmd/project/reviewer/list.go index 21775fd..ed0dafd 100644 --- a/cmd/project/reviewer/list.go +++ b/cmd/project/reviewer/list.go @@ -30,8 +30,6 @@ func init() { listOptions.Project = common.RemoteValueFlag{AllowedFunc: GetProjectKeys} listCmd.Flags().Var(&listOptions.Workspace, "workspace", "Workspace to list reviewers from") listCmd.Flags().Var(&listOptions.Project, "project", "Project Key to list reviewers from") - _ = listCmd.MarkFlagRequired("workspace") - _ = listCmd.MarkFlagRequired("project") _ = listCmd.RegisterFlagCompletionFunc("workspace", listOptions.Workspace.CompletionFunc()) _ = getCmd.RegisterFlagCompletionFunc("project", listOptions.Project.CompletionFunc()) } @@ -42,6 +40,18 @@ func listProcess(cmd *cobra.Command, args []string) (err error) { if profile.Current == nil { return errors.ArgumentMissing.With("profile") } + if len(listOptions.Workspace.Value) == 0 { + listOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(listOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } + if len(listOptions.Project.Value) == 0 { + listOptions.Project.Value = profile.Current.DefaultProject + if len(listOptions.Project.Value) == 0 { + return errors.ArgumentMissing.With("project") + } + } log.Infof("Listing all reviewers") reviewers, err := profile.GetAll[Reviewer]( diff --git a/cmd/project/reviewer/reviewer.go b/cmd/project/reviewer/reviewer.go index f1679ca..aedbd3f 100644 --- a/cmd/project/reviewer/reviewer.go +++ b/cmd/project/reviewer/reviewer.go @@ -52,13 +52,16 @@ func (reviewer *Reviewer) Validate() error { } // GetProjectKeys gets the keys of the projects in the workspace given in the command -func GetProjectKeys(context context.Context, cmd *cobra.Command) (keys []string) { +func GetProjectKeys(context context.Context, cmd *cobra.Command, args []string) (keys []string) { log := logger.Must(logger.FromContext(context)).Child("project", "keys") workspace := cmd.Flag("workspace").Value.String() if len(workspace) == 0 { - log.Warnf("No workspace given") - return + workspace = profile.Current.DefaultWorkspace + if len(workspace) == 0 { + log.Warnf("No workspace given") + return + } } type Project struct { diff --git a/cmd/project/update.go b/cmd/project/update.go index 3448680..711d008 100644 --- a/cmd/project/update.go +++ b/cmd/project/update.go @@ -24,9 +24,9 @@ type ProjectUpdator struct { } var updateCmd = &cobra.Command{ - Use: "update", + Use: "update [flags] ", Aliases: []string{"edit"}, - Short: "update a project", + Short: "update a project by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: updateValidArgs, RunE: updateProcess, @@ -53,7 +53,6 @@ func init() { updateCmd.Flags().StringVar(&updateOptions.AvatarURL, "avatar-url", "", "Avatar of the project") updateCmd.Flags().StringVar(&updateOptions.AvatarPath, "avatar-file", "", "Avatar of the project") updateCmd.Flags().BoolVar(&updateOptions.IsPrivate, "is-private", false, "Is the project private") - _ = updateCmd.MarkFlagRequired("workspace") updateCmd.MarkFlagsMutuallyExclusive("avatar-url", "avatar-file") _ = updateCmd.RegisterFlagCompletionFunc("workspace", updateOptions.Workspace.CompletionFunc()) } @@ -66,7 +65,7 @@ func updateValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]st if profile.Current == nil { return []string{}, cobra.ShellCompDirectiveNoFileComp } - return GetProjectKeys(cmd.Context(), cmd, profile.Current, deleteOptions.Workspace.String()), cobra.ShellCompDirectiveNoFileComp + return GetProjectKeys(cmd.Context(), cmd, args), cobra.ShellCompDirectiveNoFileComp } func updateProcess(cmd *cobra.Command, args []string) error { @@ -75,6 +74,12 @@ func updateProcess(cmd *cobra.Command, args []string) error { if profile.Current == nil { return errors.ArgumentMissing.With("profile") } + if len(updateOptions.Workspace.Value) == 0 { + updateOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(updateOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } payload := ProjectUpdator{ Name: updateOptions.Name, diff --git a/cmd/pullrequest/approve.go b/cmd/pullrequest/approve.go index 280ffe6..8c3bc05 100644 --- a/cmd/pullrequest/approve.go +++ b/cmd/pullrequest/approve.go @@ -12,8 +12,8 @@ import ( ) var approveCmd = &cobra.Command{ - Use: "approve", - Short: "approve a pullrequest", + Use: "approve [flags] ", + Short: "approve a pullrequest by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: approveValidArgs, RunE: approveProcess, @@ -38,7 +38,7 @@ func approveValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]s return []string{}, cobra.ShellCompDirectiveNoFileComp } - return GetPullRequests(cmd.Context(), cmd, approveOptions.Repository, "OPEN"), cobra.ShellCompDirectiveNoFileComp + return GetPullRequestIDs(cmd.Context(), cmd, approveOptions.Repository, "OPEN"), cobra.ShellCompDirectiveNoFileComp } func approveProcess(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/pullrequest/decline.go b/cmd/pullrequest/decline.go index b566566..c9bdd19 100644 --- a/cmd/pullrequest/decline.go +++ b/cmd/pullrequest/decline.go @@ -12,8 +12,8 @@ import ( ) var declineCmd = &cobra.Command{ - Use: "decline", - Short: "decline a pullrequest", + Use: "decline [flags] ", + Short: "decline a pullrequest by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: declineValidArgs, RunE: declineProcess, @@ -38,7 +38,7 @@ func declineValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]s return []string{}, cobra.ShellCompDirectiveNoFileComp } - return GetPullRequests(cmd.Context(), cmd, declineOptions.Repository, "OPEN"), cobra.ShellCompDirectiveNoFileComp + return GetPullRequestIDs(cmd.Context(), cmd, declineOptions.Repository, "OPEN"), cobra.ShellCompDirectiveNoFileComp } func declineProcess(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/pullrequest/get.go b/cmd/pullrequest/get.go index f62f670..a914fc9 100644 --- a/cmd/pullrequest/get.go +++ b/cmd/pullrequest/get.go @@ -11,9 +11,9 @@ import ( ) var getCmd = &cobra.Command{ - Use: "get", + Use: "get [flags] ", Aliases: []string{"show", "info", "display"}, - Short: "get a profile", + Short: "get a profile by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: getValidArgs, RunE: getProcess, @@ -38,7 +38,7 @@ func getValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]strin return []string{}, cobra.ShellCompDirectiveNoFileComp } - return GetPullRequests(cmd.Context(), cmd, getOptions.Repository, "ALL"), cobra.ShellCompDirectiveNoFileComp + return GetPullRequestIDs(cmd.Context(), cmd, getOptions.Repository, "ALL"), cobra.ShellCompDirectiveNoFileComp } func getProcess(cmd *cobra.Command, args []string) error { diff --git a/cmd/pullrequest/merge.go b/cmd/pullrequest/merge.go index c5fa7b8..aafa6cb 100644 --- a/cmd/pullrequest/merge.go +++ b/cmd/pullrequest/merge.go @@ -12,8 +12,8 @@ import ( ) var mergeCmd = &cobra.Command{ - Use: "merge", - Short: "merge a pullrequest", + Use: "merge [flags] ", + Short: "merge a pullrequest by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: mergeValidArgs, RunE: mergeProcess, @@ -46,7 +46,7 @@ func mergeValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]str return []string{}, cobra.ShellCompDirectiveNoFileComp } - return GetPullRequests(cmd.Context(), cmd, mergeOptions.Repository, "OPEN"), cobra.ShellCompDirectiveNoFileComp + return GetPullRequestIDs(cmd.Context(), cmd, mergeOptions.Repository, "OPEN"), cobra.ShellCompDirectiveNoFileComp } func mergeProcess(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/pullrequest/pullrequest.go b/cmd/pullrequest/pullrequest.go index 84cc391..0d19ae5 100644 --- a/cmd/pullrequest/pullrequest.go +++ b/cmd/pullrequest/pullrequest.go @@ -101,8 +101,8 @@ func (pullrequest PullRequest) MarshalJSON() (data []byte, err error) { return data, errors.JSONMarshalError.Wrap(err) } -// GetPullRequests gets the pullrequests for completion -func GetPullRequests(context context.Context, cmd *cobra.Command, repository string, state string) []string { +// GetPullRequestIDs gets the pullrequest Ids for completion +func GetPullRequestIDs(context context.Context, cmd *cobra.Command, repository string, state string) []string { log := logger.Must(logger.FromContext(context)).Child(nil, "getpullrequests") log.Infof("Getting open pullrequests for repository %s", approveOptions.Repository) diff --git a/cmd/pullrequest/unapprove.go b/cmd/pullrequest/unapprove.go index 18c4e9f..aa21ec4 100644 --- a/cmd/pullrequest/unapprove.go +++ b/cmd/pullrequest/unapprove.go @@ -11,8 +11,8 @@ import ( ) var unapproveCmd = &cobra.Command{ - Use: "unapprove", - Short: "unapprove a pullrequest", + Use: "unapprove [flags] ", + Short: "unapprove a pullrequest by its .", Args: cobra.ExactArgs(1), ValidArgsFunction: unapproveValidArgs, RunE: unapproveProcess, @@ -37,7 +37,7 @@ func unapproveValidArgs(cmd *cobra.Command, args []string, toComplete string) ([ return []string{}, cobra.ShellCompDirectiveNoFileComp } - return GetPullRequests(cmd.Context(), cmd, unapproveOptions.Repository, "OPEN"), cobra.ShellCompDirectiveNoFileComp + return GetPullRequestIDs(cmd.Context(), cmd, unapproveOptions.Repository, "OPEN"), cobra.ShellCompDirectiveNoFileComp } func unapproveProcess(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/remote/remote.go b/cmd/remote/remote.go index fd6eccc..81dba16 100644 --- a/cmd/remote/remote.go +++ b/cmd/remote/remote.go @@ -1,10 +1,14 @@ package remote import ( + "context" "io" "os" + "path/filepath" "strings" + "github.com/gildas/go-errors" + "github.com/gildas/go-logger" "gopkg.in/ini.v1" ) @@ -13,20 +17,37 @@ type Remote struct { Fetch string } -func OpenGitConfig() (io.ReadCloser, error) { - return os.Open(".git/config") +func OpenGitConfig(context context.Context) (io.ReadCloser, error) { + log := logger.Must(logger.FromContext(context)).Child("remote", "opengitconfig") + folder := "." + + for { + filename := filepath.Join(folder, ".git/config") + log.Debugf("opening %s", filename) + file, err := os.Open(filename) + if err == nil { + return file, nil + } + if !errors.Is(err, os.ErrNotExist) { + return nil, errors.RuntimeError.Wrap(err) + } + if folder == "/" { + return nil, errors.New("not a git repository") + } + folder += "/.." + } } -func GetFromGitConfig(name string) (remote *Remote, err error) { - file, err := OpenGitConfig() +func GetFromGitConfig(context context.Context, name string) (remote *Remote, err error) { + file, err := OpenGitConfig(context) if err != nil { return nil, err } defer file.Close() - return Get(file, name) + return Get(context, file, name) } -func Get(reader io.Reader, name string) (remote *Remote, err error) { +func Get(context context.Context, reader io.Reader, name string) (remote *Remote, err error) { payload, err := io.ReadAll(reader) if err != nil { return nil, err diff --git a/cmd/remote/remote_test.go b/cmd/remote/remote_test.go index f6b6d9b..5403ac6 100644 --- a/cmd/remote/remote_test.go +++ b/cmd/remote/remote_test.go @@ -1,6 +1,7 @@ package remote_test import ( + "context" "strings" "testing" @@ -8,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCanGetRepositoryWithGitAt(t *testing.T) { +func TestCanGetRemoteWithGitAt(t *testing.T) { payload := ` [core] repositoryformatversion = 0 @@ -28,18 +29,18 @@ func TestCanGetRepositoryWithGitAt(t *testing.T) { remote = origin merge = refs/heads/dev ` - r, err := remote.Get(strings.NewReader(payload), "origin") + r, err := remote.Get(context.Background(), strings.NewReader(payload), "origin") assert.NoError(t, err) assert.NotNil(t, r) assert.Equal(t, "gildas_cherruel/bb", r.RepositoryName()) - r, err = remote.Get(strings.NewReader(payload), "alternate") + r, err = remote.Get(context.Background(), strings.NewReader(payload), "alternate") assert.NoError(t, err) assert.NotNil(t, r) assert.Equal(t, "gildas_cherruel/bb", r.RepositoryName()) } -func TestCanGetRepositoryWithHTTPS(t *testing.T) { +func TestCanGetRemoteWithHTTPS(t *testing.T) { payload := ` [core] repositoryformatversion = 0 @@ -59,12 +60,12 @@ func TestCanGetRepositoryWithHTTPS(t *testing.T) { remote = origin merge = refs/heads/dev ` - r, err := remote.Get(strings.NewReader(payload), "origin") + r, err := remote.Get(context.Background(), strings.NewReader(payload), "origin") assert.NoError(t, err) assert.NotNil(t, r) assert.Equal(t, "gildas_cherruel/bb", r.RepositoryName()) - r, err = remote.Get(strings.NewReader(payload), "alternate") + r, err = remote.Get(context.Background(), strings.NewReader(payload), "alternate") assert.NoError(t, err) assert.NotNil(t, r) assert.Equal(t, "gildas_cherruel/bb", r.RepositoryName()) diff --git a/cmd/repository/clone.go b/cmd/repository/clone.go new file mode 100644 index 0000000..25109a1 --- /dev/null +++ b/cmd/repository/clone.go @@ -0,0 +1,85 @@ +package repository + +import ( + "fmt" + "os" + "strings" + + "bitbucket.org/gildas_cherruel/bb/cmd/common" + "bitbucket.org/gildas_cherruel/bb/cmd/profile" + "bitbucket.org/gildas_cherruel/bb/cmd/workspace" + "github.com/gildas/go-errors" + "github.com/gildas/go-logger" + "github.com/go-git/go-git/v5" + "github.com/spf13/cobra" +) + +var cloneCmd = &cobra.Command{ + Use: "clone [flags] ", + Short: "clone a repository by its .", + Args: cobra.ExactArgs(1), + ValidArgsFunction: cloneValidArgs, + RunE: cloneProcess, +} + +var cloneOptions struct { + Workspace common.RemoteValueFlag + Destination string + Bare bool +} + +func init() { + Command.AddCommand(cloneCmd) + + cloneOptions.Workspace = common.RemoteValueFlag{AllowedFunc: workspace.GetWorkspaceSlugs} + cloneCmd.Flags().Var(&cloneOptions.Workspace, "workspace", "Workspace to clone repositories from. If omitted, it will be extracted from the repository name") + cloneCmd.Flags().StringVar(&cloneOptions.Destination, "destination", ".", "Destination folder. Default is the repository name") + cloneCmd.Flags().BoolVar(&cloneOptions.Bare, "bare", false, "Clone as a bare repository") + _ = cloneCmd.MarkFlagDirname("destination") + _ = cloneCmd.RegisterFlagCompletionFunc("workspace", cloneOptions.Workspace.CompletionFunc()) +} + +func cloneValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + if profile.Current == nil { + return []string{}, cobra.ShellCompDirectiveNoFileComp + } + return GetRepositorySlugs(cmd.Context(), cmd, profile.Current, cloneOptions.Workspace.String()), cobra.ShellCompDirectiveNoFileComp +} + +func cloneProcess(cmd *cobra.Command, args []string) error { + log := logger.Must(logger.FromContext(cmd.Context())).Child(cmd.Parent().Name(), "clone") + + if profile.Current == nil { + return errors.ArgumentMissing.With("profile") + } + if len(cloneOptions.Workspace.Value) == 0 { + cloneOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(cloneOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } + + if len(cloneOptions.Workspace.Value) == 0 { + components := strings.Split(args[0], "/") + if len(components) != 2 { + return errors.ArgumentInvalid.With("repository", args[0]) + } + cloneOptions.Workspace.Value = components[0] + args[0] = components[1] + } + + if len(cloneOptions.Destination) == 0 { + cloneOptions.Destination = strings.TrimSuffix(args[0], ".git") + } + + log.Infof("Cloning repository %s/%s", cloneOptions.Workspace.String(), args[0]) + _, err := git.PlainCloneContext(cmd.Context(), cloneOptions.Destination, cloneOptions.Bare, &git.CloneOptions{ + URL: fmt.Sprintf("https://bitbucket.org/%s/%s.git", cloneOptions.Workspace.String(), args[0]), + Progress: os.Stdout, + }) + return err +} diff --git a/cmd/repository/create.go b/cmd/repository/create.go new file mode 100644 index 0000000..e7f6e46 --- /dev/null +++ b/cmd/repository/create.go @@ -0,0 +1,107 @@ +package repository + +import ( + "fmt" + "os" + + "bitbucket.org/gildas_cherruel/bb/cmd/common" + "bitbucket.org/gildas_cherruel/bb/cmd/profile" + "bitbucket.org/gildas_cherruel/bb/cmd/project" + "bitbucket.org/gildas_cherruel/bb/cmd/workspace" + "github.com/gildas/go-errors" + "github.com/gildas/go-logger" + "github.com/spf13/cobra" +) + +type RepositoryCreator struct { + Name string `json:"name" mapstructure:"name"` + Description string `json:"description,omitempty" mapstructure:"description"` + Project *project.ProjectReference `json:"project,omitempty" mapstructure:"project"` + IsPrivate bool `json:"is_private" mapstructure:"is_private"` + Language string `json:"language,omitempty" mapstructure:"language"` + MainBranch *branch `json:"mainbranch,omitempty" mapstructure:"mainbranch"` + ForkPolicy string `json:"fork_policy,omitempty" mapstructure:"fork_policy"` +} + +var createCmd = &cobra.Command{ + Use: "create [flags] ", + Short: "create a repository in a project and a workspace. The repository must be unique in the workspace.", + Args: cobra.ExactArgs(1), + RunE: createProcess, +} + +var createOptions struct { + Workspace common.RemoteValueFlag + Project common.RemoteValueFlag + Name string + Description string + Public bool + Private bool + Language string + MainBranch string + ForkPolicy common.EnumFlag +} + +func init() { + Command.AddCommand(createCmd) + + createOptions.Workspace = common.RemoteValueFlag{AllowedFunc: workspace.GetWorkspaceSlugs} + createOptions.Project = common.RemoteValueFlag{AllowedFunc: project.GetProjectKeys} + createOptions.ForkPolicy = common.EnumFlag{Allowed: []string{"allow_forks", "no_public_forks", "no_forks"}, Value: "no_public_forks"} + createCmd.Flags().Var(&createOptions.Workspace, "workspace", "Workspace to create repositories from") + createCmd.Flags().Var(&createOptions.Project, "project", "Project to create repositories from") + createCmd.Flags().StringVar(&createOptions.Name, "name", "", "Name of the repository") + createCmd.Flags().StringVar(&createOptions.Description, "description", "", "Description of the repository") + createCmd.Flags().BoolVar(&createOptions.Private, "private", false, "make the repository private") + createCmd.Flags().BoolVar(&createOptions.Public, "public", false, "make the repository public") + createCmd.Flags().StringVar(&createOptions.Language, "language", "", "Language of the repository") + createCmd.Flags().StringVar(&createOptions.MainBranch, "main-branch", "", "Main branch of the repository") + createCmd.Flags().Var(&createOptions.ForkPolicy, "fork-policy", "Fork policy of the repository. Default: no_public_forks") + createCmd.MarkFlagsMutuallyExclusive("private", "public") + _ = createCmd.RegisterFlagCompletionFunc("workspace", createOptions.Workspace.CompletionFunc()) + _ = createCmd.RegisterFlagCompletionFunc("project", createOptions.Project.CompletionFunc()) +} + +func createProcess(cmd *cobra.Command, args []string) (err error) { + log := logger.Must(logger.FromContext(cmd.Context())).Child(cmd.Parent().Name(), "create") + + if profile.Current == nil { + return errors.ArgumentMissing.With("profile") + } + if len(createOptions.Workspace.Value) == 0 { + createOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(createOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } + + payload := RepositoryCreator{ + Name: createOptions.Name, + Description: createOptions.Description, + IsPrivate: createOptions.Private, + Language: createOptions.Language, + ForkPolicy: createOptions.ForkPolicy.Value, + } + if len(createOptions.MainBranch) > 0 { + payload.MainBranch = &branch{Type: "branch", Name: createOptions.MainBranch} + } + if len(createOptions.Project.Value) > 0 { + payload.Project = project.NewReference(createOptions.Project.Value) + } + + log.Record("payload", payload).Infof("Creating repository %s/%s in project %s", createOptions.Workspace.String(), createOptions.Name, createOptions.Project.String()) + var repository Repository + + err = profile.Current.Post( + log.ToContext(cmd.Context()), + cmd, + fmt.Sprintf("/repositories/%s/%s", createOptions.Workspace, args[0]), + payload, + &repository, + ) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create repository %s/%s: %s\n", createOptions.Workspace, args[0], err) + os.Exit(1) + } + return profile.Current.Print(cmd.Context(), repository) +} diff --git a/cmd/repository/delete.go b/cmd/repository/delete.go new file mode 100644 index 0000000..a886a58 --- /dev/null +++ b/cmd/repository/delete.go @@ -0,0 +1,73 @@ +package repository + +import ( + "fmt" + + "bitbucket.org/gildas_cherruel/bb/cmd/common" + "bitbucket.org/gildas_cherruel/bb/cmd/profile" + "bitbucket.org/gildas_cherruel/bb/cmd/workspace" + "github.com/gildas/go-errors" + "github.com/gildas/go-logger" + "github.com/spf13/cobra" +) + +var deleteCmd = &cobra.Command{ + Use: "delete [flags] ", + Aliases: []string{"remove", "rm"}, + Short: "delete a repository by its or .", + Args: cobra.ExactArgs(1), + ValidArgsFunction: deleteValidArgs, + RunE: deleteProcess, +} + +var deleteOptions struct { + Workspace common.RemoteValueFlag +} + +func init() { + Command.AddCommand(deleteCmd) + + deleteOptions.Workspace = common.RemoteValueFlag{AllowedFunc: workspace.GetWorkspaceSlugs} + deleteCmd.Flags().Var(&deleteOptions.Workspace, "workspace", "Workspace to delete repositories from") + _ = deleteCmd.RegisterFlagCompletionFunc("workspace", deleteOptions.Workspace.CompletionFunc()) +} + +func deleteValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + if profile.Current == nil { + return []string{}, cobra.ShellCompDirectiveNoFileComp + } + return GetRepositorySlugs(cmd.Context(), cmd, profile.Current, deleteOptions.Workspace.String()), cobra.ShellCompDirectiveNoFileComp +} + +func deleteProcess(cmd *cobra.Command, args []string) error { + log := logger.Must(logger.FromContext(cmd.Context())).Child(cmd.Parent().Name(), "delete") + + if profile.Current == nil { + return errors.ArgumentMissing.With("profile") + } + if len(deleteOptions.Workspace.Value) == 0 { + deleteOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(deleteOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } + + log.Infof("Deleting repository %s", args[0]) + + err := profile.Current.Delete( + log.ToContext(cmd.Context()), + cmd, + fmt.Sprintf("/repositories/%s/%s", deleteOptions.Workspace, args[0]), + nil, + ) + if err != nil { + return err + } + + log.Infof("Repository %s deleted", args[0]) + return nil +} diff --git a/cmd/repository/fork.go b/cmd/repository/fork.go new file mode 100644 index 0000000..0bd9009 --- /dev/null +++ b/cmd/repository/fork.go @@ -0,0 +1,114 @@ +package repository + +import ( + "fmt" + + "bitbucket.org/gildas_cherruel/bb/cmd/common" + "bitbucket.org/gildas_cherruel/bb/cmd/profile" + "bitbucket.org/gildas_cherruel/bb/cmd/project" + "bitbucket.org/gildas_cherruel/bb/cmd/workspace" + "github.com/gildas/go-errors" + "github.com/gildas/go-logger" + "github.com/spf13/cobra" +) + +type RepositoryForkCreator struct { + Name string `json:"name" mapstructure:"name"` + Description string `json:"description,omitempty" mapstructure:"description"` + Project *project.ProjectReference `json:"project,omitempty" mapstructure:"project"` + IsPrivate bool `json:"is_private" mapstructure:"is_private"` + Language string `json:"language,omitempty" mapstructure:"language"` + MainBranch *branch `json:"mainbranch,omitempty" mapstructure:"mainbranch"` + ForkPolicy string `json:"fork_policy,omitempty" mapstructure:"fork_policy"` +} + +var forkCmd = &cobra.Command{ + Use: "fork [flags] ", + Short: "fork a repository by its or .", + Args: cobra.ExactArgs(1), + ValidArgsFunction: forkValidArgs, + RunE: forkProcess, +} + +var forkOptions struct { + Workspace common.RemoteValueFlag + Project common.RemoteValueFlag + Name string + Description string + Public bool + Private bool + Language string + MainBranch string + ForkPolicy common.EnumFlag +} + +func init() { + Command.AddCommand(forkCmd) + + forkOptions.Workspace = common.RemoteValueFlag{AllowedFunc: workspace.GetWorkspaceSlugs} + forkOptions.Project = common.RemoteValueFlag{AllowedFunc: project.GetProjectKeys} + forkOptions.ForkPolicy = common.EnumFlag{Allowed: []string{"allow_forks", "no_public_forks", "no_forks"}, Value: "no_public_forks"} + forkCmd.Flags().Var(&forkOptions.Workspace, "workspace", "Workspace to fork repositories from") + forkCmd.Flags().Var(&forkOptions.Project, "project", "Project to fork repositories from") + forkCmd.Flags().StringVar(&forkOptions.Name, "name", "", "Name of the repository") + forkCmd.Flags().StringVar(&forkOptions.Description, "description", "", "Description of the repository") + forkCmd.Flags().BoolVar(&forkOptions.Private, "private", false, "make the repository private") + forkCmd.Flags().BoolVar(&forkOptions.Public, "public", false, "make the repository public") + forkCmd.Flags().StringVar(&forkOptions.Language, "language", "", "Language of the repository") + forkCmd.Flags().StringVar(&forkOptions.MainBranch, "main-branch", "", "Main branch of the repository") + forkCmd.Flags().Var(&forkOptions.ForkPolicy, "fork-policy", "Fork policy of the repository. Default: no_public_forks") + forkCmd.MarkFlagsMutuallyExclusive("private", "public") + _ = forkCmd.RegisterFlagCompletionFunc("workspace", forkOptions.Workspace.CompletionFunc()) + _ = forkCmd.RegisterFlagCompletionFunc("project", forkOptions.Project.CompletionFunc()) +} + +func forkValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + return GetRepositorySlugs(cmd.Context(), cmd, profile.Current, forkOptions.Workspace.String()), cobra.ShellCompDirectiveNoFileComp +} + +func forkProcess(cmd *cobra.Command, args []string) error { + log := logger.Must(logger.FromContext(cmd.Context())).Child(cmd.Parent().Name(), "fork") + + if profile.Current == nil { + return errors.ArgumentMissing.With("profile") + } + if len(forkOptions.Workspace.Value) == 0 { + forkOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(forkOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } + + payload := RepositoryForkCreator{ + Name: forkOptions.Name, + Description: forkOptions.Description, + Language: forkOptions.Language, + IsPrivate: forkOptions.Private, + ForkPolicy: forkOptions.ForkPolicy.Value, + } + if len(forkOptions.MainBranch) > 0 { + payload.MainBranch = &branch{Type: "branch", Name: forkOptions.MainBranch} + } + if len(forkOptions.Project.Value) > 0 { + payload.Project = project.NewReference(forkOptions.Project.Value) + } + + log.Infof("Forking repository %s", args[0]) + var forked Repository + + err := profile.Current.Post( + log.ToContext(cmd.Context()), + cmd, + fmt.Sprintf("/repositories/%s/%s/forks", forkOptions.Workspace, args[0]), + payload, + &forked, + ) + if err != nil { + return err + } + return profile.Current.Print(cmd.Context(), forked) +} diff --git a/cmd/repository/get.go b/cmd/repository/get.go new file mode 100644 index 0000000..9682072 --- /dev/null +++ b/cmd/repository/get.go @@ -0,0 +1,94 @@ +package repository + +import ( + "fmt" + "os" + + "bitbucket.org/gildas_cherruel/bb/cmd/common" + "bitbucket.org/gildas_cherruel/bb/cmd/profile" + "bitbucket.org/gildas_cherruel/bb/cmd/workspace" + "github.com/gildas/go-errors" + "github.com/gildas/go-logger" + "github.com/spf13/cobra" +) + +var getCmd = &cobra.Command{ + Use: "get [flags] or . With the --forks flag, it will display the forks of the repository.", + Args: cobra.ExactArgs(1), + ValidArgsFunction: getValidArgs, + RunE: getProcess, +} + +var getOptions struct { + Workspace common.RemoteValueFlag + ShowForks bool +} + +func init() { + Command.AddCommand(getCmd) + + getOptions.Workspace = common.RemoteValueFlag{AllowedFunc: workspace.GetWorkspaceSlugs} + getCmd.Flags().Var(&getOptions.Workspace, "workspace", "Workspace to get repositories from") + getCmd.Flags().BoolVar(&getOptions.ShowForks, "forks", false, "Show the forks of the repository") + _ = getCmd.RegisterFlagCompletionFunc("workspace", getOptions.Workspace.CompletionFunc()) +} + +func getValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + if profile.Current == nil { + return []string{}, cobra.ShellCompDirectiveNoFileComp + } + return GetRepositorySlugs(cmd.Context(), cmd, profile.Current, getOptions.Workspace.String()), cobra.ShellCompDirectiveNoFileComp +} + +func getProcess(cmd *cobra.Command, args []string) error { + log := logger.Must(logger.FromContext(cmd.Context())).Child(cmd.Parent().Name(), "get") + + if profile.Current == nil { + return errors.ArgumentMissing.With("profile") + } + if len(getOptions.Workspace.Value) == 0 { + getOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(getOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } + + if getOptions.ShowForks { + log.Infof("Displaying forks of repository %s", args[0]) + forks, err := profile.GetAll[Repository]( + cmd.Context(), + cmd, + profile.Current, + fmt.Sprintf("/repositories/%s/%s/forks", getOptions.Workspace, args[0]), + ) + if err != nil { + return err + } + if len(forks) == 0 { + log.Infof("No fork found") + return nil + } + return profile.Current.Print(cmd.Context(), Repositories(forks)) + } + + log.Infof("Displaying repository %s", args[0]) + var repository Repository + + err := profile.Current.Get( + log.ToContext(cmd.Context()), + cmd, + fmt.Sprintf("/repositories/%s/%s", getOptions.Workspace, args[0]), + &repository, + ) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get repository %s: %s\n", args[0], err) + os.Exit(1) + } + return profile.Current.Print(cmd.Context(), repository) +} diff --git a/cmd/repository/list.go b/cmd/repository/list.go new file mode 100644 index 0000000..a448be4 --- /dev/null +++ b/cmd/repository/list.go @@ -0,0 +1,66 @@ +package repository + +import ( + "bitbucket.org/gildas_cherruel/bb/cmd/common" + "bitbucket.org/gildas_cherruel/bb/cmd/profile" + "bitbucket.org/gildas_cherruel/bb/cmd/workspace" + "github.com/gildas/go-errors" + "github.com/gildas/go-logger" + "github.com/spf13/cobra" +) + +var listCmd = &cobra.Command{ + Use: "list", + Short: "list all public repositories", + Args: cobra.NoArgs, + RunE: listProcess, +} + +var listOptions struct { + Role common.EnumFlag + Workspace common.RemoteValueFlag +} + +func init() { + Command.AddCommand(listCmd) + + listOptions.Role = common.EnumFlag{Allowed: []string{"owner", "admin", "contributor", "member", "all"}, Value: "owner"} + listOptions.Workspace = common.RemoteValueFlag{AllowedFunc: workspace.GetWorkspaceSlugs} + listCmd.Flags().Var(&listOptions.Role, "role", "Role of the user in the repository") + listCmd.Flags().Var(&listOptions.Workspace, "workspace", "Workspace to list repositories from") + _ = listCmd.RegisterFlagCompletionFunc("workspace", listOptions.Workspace.CompletionFunc()) +} + +func listProcess(cmd *cobra.Command, args []string) (err error) { + log := logger.Must(logger.FromContext(cmd.Context())).Child(cmd.Parent().Name(), "list") + + if profile.Current == nil { + return errors.ArgumentMissing.With("profile") + } + + filter := "" + if listOptions.Role.Value != "all" { + filter = "?role=" + listOptions.Role.Value + } + + workspace := "" + if len(listOptions.Workspace.Value) > 0 { + workspace = "/" + listOptions.Workspace.Value + } + + log.Infof("Listing all repositories, workspace %s, role %s", listOptions.Workspace, listOptions.Role) + repositories, err := profile.GetAll[Repository]( + cmd.Context(), + cmd, + profile.Current, + "/repositories"+workspace+filter, + ) + if err != nil { + return err + } + if len(repositories) == 0 { + log.Infof("No repository found") + return nil + } + return profile.Current.Print(cmd.Context(), Repositories(repositories)) +} diff --git a/cmd/repository/repositories.go b/cmd/repository/repositories.go new file mode 100644 index 0000000..69aba94 --- /dev/null +++ b/cmd/repository/repositories.go @@ -0,0 +1,27 @@ +package repository + +type Repositories []Repository + +// GetHeader gets the header for a table +// +// implements common.Tableables +func (repositories Repositories) GetHeader() []string { + return Repository{}.GetHeader(false) +} + +// GetRowAt gets the row for a table +// +// implements common.Tableables +func (repositories Repositories) GetRowAt(index int, headers []string) []string { + if index < 0 || index >= len(repositories) { + return []string{} + } + return repositories[index].GetRow(headers) +} + +// Size gets the number of elements +// +// implements common.Tableables +func (repositories Repositories) Size() int { + return len(repositories) +} diff --git a/cmd/repository/repository.go b/cmd/repository/repository.go index a7a3825..4cc4fc7 100644 --- a/cmd/repository/repository.go +++ b/cmd/repository/repository.go @@ -2,38 +2,86 @@ package repository import ( "context" - "strings" + "encoding/json" + "fmt" + "time" "bitbucket.org/gildas_cherruel/bb/cmd/common" - "bitbucket.org/gildas_cherruel/bb/cmd/remote" + "bitbucket.org/gildas_cherruel/bb/cmd/profile" + "bitbucket.org/gildas_cherruel/bb/cmd/project" + "bitbucket.org/gildas_cherruel/bb/cmd/user" + "bitbucket.org/gildas_cherruel/bb/cmd/workspace" + "github.com/gildas/go-core" "github.com/gildas/go-errors" + "github.com/gildas/go-logger" "github.com/spf13/cobra" ) type Repository struct { - Type string `json:"type" mapstructure:"type"` - ID common.UUID `json:"uuid" mapstructure:"uuid"` - Name string `json:"name" mapstructure:"name"` - FullName string `json:"full_name" mapstructure:"full_name"` - Links common.Links `json:"links" mapstructure:"links"` -} - -func GetRepository(context context.Context, cmd *cobra.Command) (*Repository, error) { - fullName := cmd.Flag("repository").Value.String() - if len(fullName) == 0 { - remote, err := remote.GetFromGitConfig("origin") - if err != nil { - return nil, errors.Join(errors.NotFound.With("current repository"), err) + Type string `json:"type" mapstructure:"type"` + ID common.UUID `json:"uuid" mapstructure:"uuid"` + Name string `json:"name" mapstructure:"name"` + FullName string `json:"full_name" mapstructure:"full_name"` + Slug string `json:"slug" mapstructure:"slug"` + Owner user.Account `json:"owner" mapstructure:"owner"` + Workspace workspace.Workspace `json:"workspace" mapstructure:"workspace"` + Project project.Project `json:"project" mapstructure:"project"` + HasIssues bool `json:"has_issues" mapstructure:"has_issues"` + HasWiki bool `json:"has_wiki" mapstructure:"has_wiki"` + IsPrivate bool `json:"is_private" mapstructure:"is_private"` + ForkPolicy string `json:"fork_policy" mapstructure:"fork_policy"` + Size int64 `json:"size" mapstructure:"size"` + Language string `json:"language,omitempty" mapstructure:"language"` + MainBranch string `json:"-" mapstructure:"-"` + DefaultMergeStrategy string `json:"-" mapstructure:"-"` + BranchingModel string `json:"-" mapstructure:"-"` + Parent *Repository `json:"parent" mapstructure:"parent"` + Links common.Links `json:"links" mapstructure:"links"` + CreatedOn time.Time `json:"created_on" mapstructure:"created_on"` + UpdatedOn time.Time `json:"updated_on" mapstructure:"updated_on"` +} + +/* +type repositorySettings struct { + DefaultMergeStrategy bool `json:"default_merge_strategy" mapstructure:"default_merge_strategy"` + BranchingModel bool `json:"branching_model" mapstructure:"branching_model"` +} +*/ + +type branch struct { + Type string `json:"type" mapstructure:"type"` + Name string `json:"name" mapstructure:"name"` +} + +// Command represents this folder's command +var Command = &cobra.Command{ + Use: "repo", + Aliases: []string{"repository"}, + Short: "Manage repositories", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Workspace requires a subcommand:") + for _, command := range cmd.Commands() { + fmt.Println(command.Name()) } - fullName = remote.RepositoryName() - } - components := strings.Split(fullName, "/") - if len(components) == 2 { - return &Repository{Name: components[1], FullName: fullName}, nil - } else if len(components) == 1 { - return &Repository{Name: components[0], FullName: fullName}, nil + }, +} + +// GetHeader gets the header for a table +// +// implements common.Tableable +func (repository Repository) GetHeader(short bool) []string { + return []string{"ID", "Name", "Full Name"} +} + +// GetRow gets the row for a table +// +// implements common.Tableable +func (repository Repository) GetRow(headers []string) []string { + return []string{ + repository.ID.String(), + repository.Name, + repository.FullName, } - return &Repository{Name: fullName, FullName: fullName}, nil } // String returns the string representation of the repository @@ -42,3 +90,85 @@ func GetRepository(context context.Context, cmd *cobra.Command) (*Repository, er func (repository Repository) String() string { return repository.FullName } + +// GetRepositorySlugs gets the slugs of all repositories +func GetRepositorySlugs(context context.Context, cmd *cobra.Command, currentProfile *profile.Profile, workspace string) (slugs []string) { + log := logger.Must(logger.FromContext(context)).Child("repository", "slugs") + + repositories, err := profile.GetAll[Repository](context, cmd, currentProfile, fmt.Sprintf("/repositories/%s", workspace)) + if err != nil { + log.Errorf("Failed to get repositories", err) + return + } + return core.Map(repositories, func(repository Repository) string { + return repository.Slug + }) +} + +// MarshalJSON implements the json.Marshaler interface. +// +// Implements json.Marshaler +func (repository Repository) MarshalJSON() (data []byte, err error) { + type surrogate Repository + var owner *user.Account + var wspace *workspace.Workspace + var proj *project.Project + var br *branch + var createdOn string + var updatedOn string + + if !repository.Owner.ID.IsNil() { + owner = &repository.Owner + } + if !repository.Workspace.ID.IsNil() { + wspace = &repository.Workspace + } + if !repository.Project.ID.IsNil() { + proj = &repository.Project + } + if len(repository.MainBranch) > 0 { + br = &branch{Type: "branch", Name: repository.MainBranch} + } + if !repository.CreatedOn.IsZero() { + createdOn = repository.CreatedOn.Format("2006-01-02T15:04:05.999999999-07:00") + } + if !repository.UpdatedOn.IsZero() { + updatedOn = repository.UpdatedOn.Format("2006-01-02T15:04:05.999999999-07:00") + } + + data, err = json.Marshal(struct { + surrogate + Owner *user.Account `json:"owner,omitempty"` + Workspace *workspace.Workspace `json:"workspace,omitempty"` + Project *project.Project `json:"project,omitempty"` + MainBranch *branch `json:"mainbranch,omitempty"` + CreatedOn string `json:"created_on,omitempty"` + UpdatedOn string `json:"updated_on,omitempty"` + }{ + surrogate: surrogate(repository), + Owner: owner, + Workspace: wspace, + Project: proj, + MainBranch: br, + CreatedOn: createdOn, + UpdatedOn: updatedOn, + }) + return data, errors.JSONMarshalError.Wrap(err) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// +// Implements json.Unmarshaler +func (repository *Repository) UnmarshalJSON(data []byte) (err error) { + type surrogate Repository + var inner struct { + surrogate + MainBranch branch `json:"mainbranch"` + } + if err = json.Unmarshal(data, &inner); err != nil { + return errors.JSONUnmarshalError.Wrap(err) + } + *repository = Repository(inner.surrogate) + repository.MainBranch = inner.MainBranch.Name + return nil +} diff --git a/cmd/repository/update.go b/cmd/repository/update.go new file mode 100644 index 0000000..0297a40 --- /dev/null +++ b/cmd/repository/update.go @@ -0,0 +1,115 @@ +package repository + +import ( + "fmt" + "os" + + "bitbucket.org/gildas_cherruel/bb/cmd/common" + "bitbucket.org/gildas_cherruel/bb/cmd/profile" + "bitbucket.org/gildas_cherruel/bb/cmd/project" + "bitbucket.org/gildas_cherruel/bb/cmd/workspace" + "github.com/gildas/go-errors" + "github.com/gildas/go-logger" + "github.com/spf13/cobra" +) + +type RepositoryUpdator struct { + Name string `json:"name,omitempty" mapstructure:"name"` + Description string `json:"description,omitempty" mapstructure:"description"` + Project *project.ProjectReference `json:"project,omitempty" mapstructure:"project"` + IsPrivate *bool `json:"is_private,omitempty" mapstructure:"is_private"` + Language string `json:"language,omitempty" mapstructure:"language"` + MainBranch *branch `json:"mainbranch,omitempty" mapstructure:"mainbranch"` + ForkPolicy string `json:"fork_policy,omitempty" mapstructure:"fork_policy"` +} + +var updateCmd = &cobra.Command{ + Use: "update [flags] ", + Short: "update a repository in a project and a workspace. The project must be unique in the workspace.", + Args: cobra.ExactArgs(1), + RunE: updateProcess, +} + +var updateOptions struct { + Workspace common.RemoteValueFlag + Project common.RemoteValueFlag + Name string + Description string + Public bool + Private bool + Language string + MainBranch string + ForkPolicy common.EnumFlag +} + +func init() { + Command.AddCommand(updateCmd) + + updateOptions.Workspace = common.RemoteValueFlag{AllowedFunc: workspace.GetWorkspaceSlugs} + updateOptions.Project = common.RemoteValueFlag{AllowedFunc: project.GetProjectKeys} + updateOptions.ForkPolicy = common.EnumFlag{Allowed: []string{"allow_forks", "no_public_forks", "no_forks"}, Value: "no_public_forks"} + updateCmd.Flags().Var(&updateOptions.Workspace, "workspace", "Workspace to update repositories from") + updateCmd.Flags().Var(&updateOptions.Project, "project", "Project to update repositories from") + updateCmd.Flags().StringVar(&updateOptions.Name, "name", "", "Name of the repository") + updateCmd.Flags().StringVar(&updateOptions.Description, "description", "", "Description of the repository") + updateCmd.Flags().BoolVar(&updateOptions.Private, "private", false, "make the repository private") + updateCmd.Flags().BoolVar(&updateOptions.Public, "public", false, "make the repository public") + updateCmd.Flags().StringVar(&updateOptions.Language, "language", "", "Language of the repository") + updateCmd.Flags().StringVar(&updateOptions.MainBranch, "main-branch", "", "Main branch of the repository") + updateCmd.Flags().Var(&updateOptions.ForkPolicy, "fork-policy", "Fork policy of the repository. Default: no_public_forks") + updateCmd.MarkFlagsMutuallyExclusive("private", "public") + _ = updateCmd.RegisterFlagCompletionFunc("workspace", updateOptions.Workspace.CompletionFunc()) + _ = updateCmd.RegisterFlagCompletionFunc("project", updateOptions.Project.CompletionFunc()) +} + +func updateProcess(cmd *cobra.Command, args []string) (err error) { + log := logger.Must(logger.FromContext(cmd.Context())).Child(cmd.Parent().Name(), "update") + + if profile.Current == nil { + return errors.ArgumentMissing.With("profile") + } + if len(updateOptions.Workspace.Value) == 0 { + updateOptions.Workspace.Value = profile.Current.DefaultWorkspace + if len(updateOptions.Workspace.Value) == 0 { + return errors.ArgumentMissing.With("workspace") + } + } + + var private *bool + + if updateOptions.Private { + private = &updateOptions.Private // => true + } else if updateOptions.Public { + private = &updateOptions.Private // => false + } + + payload := RepositoryUpdator{ + Name: updateOptions.Name, + Description: updateOptions.Description, + IsPrivate: private, + Language: updateOptions.Language, + ForkPolicy: updateOptions.ForkPolicy.Value, + } + if len(updateOptions.MainBranch) > 0 { + payload.MainBranch = &branch{Type: "branch", Name: updateOptions.MainBranch} + } + if len(updateOptions.Project.Value) > 0 { + payload.Project = project.NewReference(updateOptions.Project.Value) + } + + log.Record("payload", payload).Infof("Updating repository %s/%s in project %s", updateOptions.Workspace.String(), updateOptions.Name, updateOptions.Project.String()) + var repository Repository + + err = profile.Current.Put( + log.ToContext(cmd.Context()), + cmd, + fmt.Sprintf("/repositories/%s/%s", updateOptions.Workspace, args[0]), + payload, + &repository, + ) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to update repository %s/%s: %s\n", updateOptions.Workspace, args[0], err) + os.Exit(1) + } + return profile.Current.Print(cmd.Context(), repository) +} diff --git a/cmd/root.go b/cmd/root.go index 04eedc4..f46b875 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,8 +17,10 @@ import ( "bitbucket.org/gildas_cherruel/bb/cmd/profile" "bitbucket.org/gildas_cherruel/bb/cmd/project" "bitbucket.org/gildas_cherruel/bb/cmd/pullrequest" + "bitbucket.org/gildas_cherruel/bb/cmd/repository" "bitbucket.org/gildas_cherruel/bb/cmd/user" "bitbucket.org/gildas_cherruel/bb/cmd/workspace" + "github.com/gildas/go-core" "github.com/gildas/go-logger" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -55,9 +57,9 @@ func init() { cobra.CheckErr(err) // Global flags - CmdOptions.OutputFormat = common.EnumFlag{Allowed: []string{"csv", "json", "yaml", "table", "tsv"}, Value: ""} - RootCmd.PersistentFlags().StringVar(&CmdOptions.ConfigFile, "config", "", "config file (default is .env, "+filepath.Join(configDir, "bitbucket", "config-cli.yml")) - RootCmd.PersistentFlags().StringVarP(&CmdOptions.ProfileName, "profile", "p", "", "Profile to use. Overrides the default profile") + CmdOptions.OutputFormat = common.EnumFlag{Allowed: []string{"csv", "json", "yaml", "table", "tsv"}, Value: core.GetEnvAsString("BB_OUTPUT_FORMAT", "")} + RootCmd.PersistentFlags().StringVar(&CmdOptions.ConfigFile, "config", core.GetEnvAsString("BB_CONFIG", ""), "config file (default is .env, "+filepath.Join(configDir, "bitbucket", "config-cli.yml")) + RootCmd.PersistentFlags().StringVarP(&CmdOptions.ProfileName, "profile", "p", core.GetEnvAsString("BB_PROFILE", ""), "Profile to use. Overrides the default profile") RootCmd.PersistentFlags().StringVarP(&CmdOptions.LogDestination, "log", "l", "", "Log destination (stdout, stderr, file, none), overrides LOG_DESTINATION environment variable") RootCmd.PersistentFlags().BoolVar(&CmdOptions.Debug, "debug", false, "logs are written at DEBUG level, overrides DEBUG environment variable") RootCmd.PersistentFlags().BoolVarP(&CmdOptions.Verbose, "verbose", "v", false, "Verbose mode, overrides VERBOSE environment variable") @@ -75,6 +77,7 @@ func init() { RootCmd.AddCommand(component.Command) RootCmd.AddCommand(issue.Command) RootCmd.AddCommand(pullrequest.Command) + RootCmd.AddCommand(repository.Command) RootCmd.AddCommand(user.Command) RootCmd.AddCommand(workspace.Command) diff --git a/cmd/user/account.go b/cmd/user/account.go index 9dc28c5..2126482 100644 --- a/cmd/user/account.go +++ b/cmd/user/account.go @@ -13,15 +13,15 @@ import ( ) type Account struct { - Type string `json:"type" mapstructure:"type"` - ID common.UUID `json:"uuid" mapstructure:"uuid"` - Username string `json:"username" mapstructure:"username"` - Name string `json:"display_name" mapstructure:"display_name"` - AccountID string `json:"account_id" mapstructure:"account_id"` - AccountStatus string `json:"account_status" mapstructure:"account_status"` - Kind string `json:"kind" mapstructure:"kind"` - Links common.Links `json:"links" mapstructure:"links"` - CreatedOn time.Time `json:"created_on" mapstructure:"created_on"` + Type string `json:"type" mapstructure:"type"` + ID common.UUID `json:"uuid" mapstructure:"uuid"` + Username string `json:"username,omitempty" mapstructure:"username"` + Name string `json:"display_name" mapstructure:"display_name"` + AccountID string `json:"account_id" mapstructure:"account_id"` + AccountStatus string `json:"account_status,omitempty" mapstructure:"account_status"` + Kind string `json:"kind,omitempty" mapstructure:"kind"` + Links common.Links `json:"links" mapstructure:"links"` + CreatedOn time.Time `json:"created_on" mapstructure:"created_on"` } // Command represents this folder's command @@ -91,13 +91,17 @@ func GetAccount(context context.Context, cmd *cobra.Command, currentProfile *pro // MarshalJSON implements the json.Marshaler interface. func (account Account) MarshalJSON() (data []byte, err error) { type surrogate Account + var createdOn string + if !account.CreatedOn.IsZero() { + createdOn = account.CreatedOn.Format("2006-01-02T15:04:05.999999999-07:00") + } data, err = json.Marshal(struct { surrogate - CreatedOn string `json:"created_on"` + CreatedOn string `json:"created_on,omitempty"` }{ surrogate: surrogate(account), - CreatedOn: account.CreatedOn.Format("2006-01-02T15:04:05.999999999-07:00"), + CreatedOn: createdOn, }) return data, errors.JSONMarshalError.Wrap(err) } diff --git a/cmd/workspace/get.go b/cmd/workspace/get.go index 376f3c6..d511205 100644 --- a/cmd/workspace/get.go +++ b/cmd/workspace/get.go @@ -12,9 +12,9 @@ import ( ) var getCmd = &cobra.Command{ - Use: "get", + Use: "get [flags] ", Aliases: []string{"show", "info", "display"}, - Short: "get a workspace", + Short: "get a workspace by its . With the --members flag, it will display the members of the workspace. With the --member flag, it will display workspaces for the given user.", Args: cobra.ExactArgs(1), RunE: getProcess, } diff --git a/cmd/workspace/workspace.go b/cmd/workspace/workspace.go index 37a1db2..279df32 100644 --- a/cmd/workspace/workspace.go +++ b/cmd/workspace/workspace.go @@ -50,7 +50,7 @@ func (workspace Workspace) GetRow(headers []string) []string { } // GetWorkspaceSlugs gets the slugs of all workspaces -func GetWorkspaceSlugs(context context.Context, cmd *cobra.Command) (slugs []string) { +func GetWorkspaceSlugs(context context.Context, cmd *cobra.Command, args []string) (slugs []string) { log := logger.Must(logger.FromContext(context)).Child("workspace", "slugs") log.Debugf("Getting all workspaces") diff --git a/go.mod b/go.mod index cdfc7f1..3776481 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,11 @@ module bitbucket.org/gildas_cherruel/bb go 1.21.3 require ( - github.com/gildas/go-core v0.5.5 + github.com/gildas/go-core v0.5.6 github.com/gildas/go-errors v0.3.6 - github.com/gildas/go-logger v1.6.9 - github.com/gildas/go-request v0.7.18 + github.com/gildas/go-logger v1.6.10 + github.com/gildas/go-request v0.7.19 + github.com/go-git/go-git/v5 v5.11.0 github.com/google/uuid v1.5.0 github.com/joho/godotenv v1.5.1 github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23 @@ -23,10 +24,18 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/logging v1.9.0 // indirect cloud.google.com/go/longrunning v0.5.4 // indirect + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect + github.com/cloudflare/circl v1.3.6 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.4.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -35,19 +44,25 @@ require ( github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sergi/go-diff v1.3.1 // indirect + github.com/skeema/knownhosts v1.2.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect @@ -56,18 +71,21 @@ require ( go.opentelemetry.io/otel/trace v1.21.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.17.0 // indirect - golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect + golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect + golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.16.1 // indirect google.golang.org/api v0.154.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/grpc v1.60.1 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 78465ed..abd64ed 100644 --- a/go.sum +++ b/go.sum @@ -11,17 +11,38 @@ cloud.google.com/go/logging v1.9.0 h1:iEIOXFO9EmSiTjDmfpbRjOxECO7R8C7b8IXUGOj7xZ cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg= cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE= +github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +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/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/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg= +github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/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/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -34,17 +55,27 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gildas/go-core v0.5.5 h1:07gpLpsauJMc7yDU0RDRGSdP0vubVyK+jjz2xTNN5Y0= -github.com/gildas/go-core v0.5.5/go.mod h1:nCXOr3NH2LWZY8dgijaBJT3J8oZAbGaHRYtiWy0gnqM= +github.com/gildas/go-core v0.5.6 h1:gQz1bUHtSW196+fbF4XMi8sucfDdJdhSjhWmg5QojoA= +github.com/gildas/go-core v0.5.6/go.mod h1:1u70yeQBWKCT7CQNvDSGn2LfZmGna9REzq/4jzAPf0E= github.com/gildas/go-errors v0.3.6 h1:/loKTkq/t+eoIcULhKAwd0WBRPHgZxhkd3l+m/uw15c= github.com/gildas/go-errors v0.3.6/go.mod h1:jqH4hy2BzpU3mdjkUYJhkZvEkn56cWRjWVgz/HNqglQ= -github.com/gildas/go-logger v1.6.9 h1:GdNY9SrGkO4zLFdKjAABzwirxubayrmKAOLIaFWbldw= -github.com/gildas/go-logger v1.6.9/go.mod h1:R/tHixu5HBhFtlZoE6XAhsUqY1iYys8P2+Bq3XUGw8w= -github.com/gildas/go-request v0.7.18 h1:ttxtbvPTxIgjHprt2lTM53numkAa/Nai72s2uRXrsuM= -github.com/gildas/go-request v0.7.18/go.mod h1:YiiHsecBJAN9Yxe7FlicP4tfhOEWsPqoet8yBPzXVdk= +github.com/gildas/go-logger v1.6.10 h1:VDLcwsh2FR7i6kO0HgKBTwKrpdrJqCdo8JmkSK57z4U= +github.com/gildas/go-logger v1.6.10/go.mod h1:o326U1fT1tOufXzUrlkLuKpybWdBqyjHQ++GLJsdGc4= +github.com/gildas/go-request v0.7.19 h1:feoSKFEGv+QCGJan7D9qbguT6iA8H1X6RxzkSuK2MbQ= +github.com/gildas/go-request v0.7.19/go.mod h1:WOCGZooanhl6atrUr38Ce7BxkTu8VX/VCWfr7ZKpc3g= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +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/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +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/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.0 h1:wx+BduGRXjIL6VPeeb7DRX+ii7sR/ch8DlRifHR589o= -github.com/go-logr/logr v1.4.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -87,12 +118,19 @@ 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/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/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23 h1:M8exrBzuhWcU6aoHJlHWPe4qFjVKzkMGRal78f5jRRU= github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23/go.mod h1:kBSna6b0/RzsOcOZf515vAXwSsXYusl2U7SA0XP09yI= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -101,8 +139,14 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ github.com/mattn/go-runewidth v0.0.15/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/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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= @@ -110,13 +154,18 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= 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.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= 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/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -132,6 +181,8 @@ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -139,6 +190,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= @@ -159,15 +212,21 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE= -golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= +golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -176,7 +235,11 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -186,24 +249,42 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 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.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.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.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.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -215,6 +296,9 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 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= google.golang.org/api v0.154.0 h1:X7QkVKZBskztmpPKWQXgjJRPA2dJYrL6r+sYPRLj050= @@ -250,13 +334,18 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 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/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/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/testdata/repository.json b/testdata/repository.json new file mode 100644 index 0000000..47e9feb --- /dev/null +++ b/testdata/repository.json @@ -0,0 +1,130 @@ +{ + "type": "repository", + "full_name": "gildas_cherruel/bb", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/gildas_cherruel/bb" + }, + "html": { + "href": "https://bitbucket.org/gildas_cherruel/bb" + }, + "avatar": { + "href": "https://bytebucket.org/ravatar/%7Bb8313f5b-2134-488d-9aca-830fb95206c5%7D?ts=go" + }, + "pullrequests": { + "href": "https://api.bitbucket.org/2.0/repositories/gildas_cherruel/bb/pullrequests" + }, + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/gildas_cherruel/bb/commits" + }, + "forks": { + "href": "https://api.bitbucket.org/2.0/repositories/gildas_cherruel/bb/forks" + }, + "watchers": { + "href": "https://api.bitbucket.org/2.0/repositories/gildas_cherruel/bb/watchers" + }, + "branches": { + "href": "https://api.bitbucket.org/2.0/repositories/gildas_cherruel/bb/refs/branches" + }, + "tags": { + "href": "https://api.bitbucket.org/2.0/repositories/gildas_cherruel/bb/refs/tags" + }, + "downloads": { + "href": "https://api.bitbucket.org/2.0/repositories/gildas_cherruel/bb/downloads" + }, + "source": { + "href": "https://api.bitbucket.org/2.0/repositories/gildas_cherruel/bb/src" + }, + "clone": [ + { + "name": "https", + "href": "https://gildas_cherruel@bitbucket.org/gildas_cherruel/bb.git" + }, + { + "name": "ssh", + "href": "git@bitbucket.org:gildas_cherruel/bb.git" + } + ], + "issues": { + "href": "https://api.bitbucket.org/2.0/repositories/gildas_cherruel/bb/issues" + }, + "hooks": { + "href": "https://api.bitbucket.org/2.0/repositories/gildas_cherruel/bb/hooks" + } + }, + "name": "bb", + "slug": "bb", + "description": "", + "scm": "git", + "website": null, + "owner": { + "display_name": "Gildas Cherruel", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7B4115785a-a1b9-4887-9af2-ca401bc7f168%7D" + }, + "avatar": { + "href": "https://secure.gravatar.com/avatar/30dbb120d3b75aaa0cec44e826c85cd7?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FGC-0.png" + }, + "html": { + "href": "https://bitbucket.org/%7B4115785a-a1b9-4887-9af2-ca401bc7f168%7D/" + } + }, + "type": "user", + "uuid": "{4115785a-a1b9-4887-9af2-ca401bc7f168}", + "account_id": "557058:ec46c4b2-f245-496e-8c11-1404869e3346", + "nickname": "gildas_cherruel" + }, + "workspace": { + "type": "workspace", + "uuid": "{4115785a-a1b9-4887-9af2-ca401bc7f168}", + "name": "Gildas Cherruel", + "slug": "gildas_cherruel", + "links": { + "avatar": { + "href": "https://bitbucket.org/workspaces/gildas_cherruel/avatar/?ts=1543531676" + }, + "html": { + "href": "https://bitbucket.org/gildas_cherruel/" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/gildas_cherruel" + } + } + }, + "is_private": false, + "project": { + "type": "project", + "key": "OS", + "uuid": "{e5abc9b3-0ac5-4ae9-add6-a817b1fea161}", + "name": "Open Source", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/gildas_cherruel/projects/OS" + }, + "html": { + "href": "https://bitbucket.org/gildas_cherruel/workspace/projects/OS" + }, + "avatar": { + "href": "https://bitbucket.org/account/user/gildas_cherruel/projects/OS/avatar/32?ts=1702034483" + } + } + }, + "fork_policy": "allow_forks", + "created_on": "2023-11-02T09:27:57.558951+00:00", + "updated_on": "2023-12-21T19:02:25.884059+00:00", + "size": 1874302, + "language": "go", + "uuid": "{b8313f5b-2134-488d-9aca-830fb95206c5}", + "mainbranch": { + "name": "master", + "type": "branch" + }, + "override_settings": { + "default_merge_strategy": false, + "branching_model": false + }, + "parent": null, + "has_issues": true, + "has_wiki": false +} diff --git a/version.go b/version.go index 4eb07d7..4417cef 100644 --- a/version.go +++ b/version.go @@ -12,7 +12,7 @@ var branch string var stamp string // VERSION is the version of this application -var VERSION = "0.6.0" +var VERSION = "0.7.0" // APP is the name of the application const APP = "bb"