From 1a1a186a22d6032cff5d7836c5a6ff3fec6d8eae Mon Sep 17 00:00:00 2001 From: Matti R Date: Mon, 4 Oct 2021 16:25:17 -0400 Subject: [PATCH 1/6] Add user list command --- cmd/admin.go | 24 ++++++++++++++++++ cmd/admin/users/list.go | 54 +++++++++++++++++++++++++++++++++++++++++ cmd/admin_users.go | 41 +++++++++++++++++++++++++++++++ cmd/categories.go | 1 + main.go | 2 ++ modules/print/user.go | 3 +++ 6 files changed, 125 insertions(+) create mode 100644 cmd/admin.go create mode 100644 cmd/admin/users/list.go create mode 100644 cmd/admin_users.go diff --git a/cmd/admin.go b/cmd/admin.go new file mode 100644 index 0000000..c8640df --- /dev/null +++ b/cmd/admin.go @@ -0,0 +1,24 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/urfave/cli/v2" +) + +// CmdAdmin represents the admin sub-commands +var CmdAdmin = cli.Command{ + Name: "admin", + Aliases: []string{"a"}, + Category: catAdmin, + Action: func(cmd *cli.Context) error { + // TODO: this is just a stub for all admin actions + // there is no default admin action + return nil + }, + Subcommands: []*cli.Command{ + &cmdAdminUsers, + }, +} diff --git a/cmd/admin/users/list.go b/cmd/admin/users/list.go new file mode 100644 index 0000000..c29020c --- /dev/null +++ b/cmd/admin/users/list.go @@ -0,0 +1,54 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package users + +import ( + "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/print" + + "code.gitea.io/sdk/gitea" + "github.com/urfave/cli/v2" +) + +var userFieldsFlag = flags.FieldsFlag(print.UserFields, []string{ + "id", "login", "full_name", "email", "activated", +}) + +// CmdUserList represents a sub command of users to list users +var CmdUserList = cli.Command{ + Name: "list", + Aliases: []string{"ls"}, + Usage: "List Users", + Description: "List users", + Action: RunUserList, + Flags: append([]cli.Flag{ + userFieldsFlag, + &flags.PaginationPageFlag, + &flags.PaginationLimitFlag, + }, flags.AllDefaultFlags...), +} + +// RunUserList list user organizations +func RunUserList(cmd *cli.Context) error { + ctx := context.InitCommand(cmd) + client := ctx.Login.Client() + + users, _, err := client.AdminListUsers(gitea.AdminListUsersOptions{ + ListOptions: ctx.GetListOptions(), + }) + if err != nil { + return err + } + + fields, err := userFieldsFlag.GetValues(cmd) + if err != nil { + return err + } + + print.UserList(users, ctx.Output, fields) + + return nil +} diff --git a/cmd/admin_users.go b/cmd/admin_users.go new file mode 100644 index 0000000..056c996 --- /dev/null +++ b/cmd/admin_users.go @@ -0,0 +1,41 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "code.gitea.io/tea/cmd/admin/users" + "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/print" + + "github.com/urfave/cli/v2" +) + +var cmdAdminUsers = cli.Command{ + Name: "user", + Aliases: []string{"u"}, + Category: catAdmin, + Action: func(ctx *cli.Context) error { + if ctx.Args().Len() == 1 { + return runAdminUserDetail(ctx, ctx.Args().First()) + } + return users.RunUserList(ctx) + }, + Subcommands: []*cli.Command{ + &users.CmdUserList, + }, + Flags: users.CmdUserList.Flags, +} + +func runAdminUserDetail(cmd *cli.Context, u string) error { + ctx := context.InitCommand(cmd) + client := ctx.Login.Client() + user, _, err := client.GetUserInfo(u) + if err != nil { + return err + } + + print.UserDetails(user) + return nil +} diff --git a/cmd/categories.go b/cmd/categories.go index 4b2088b..c368b5a 100644 --- a/cmd/categories.go +++ b/cmd/categories.go @@ -8,4 +8,5 @@ var ( catSetup = "SETUP" catEntities = "ENTITIES" catHelpers = "HELPERS" + catAdmin = "ADMIN" ) diff --git a/main.go b/main.go index 52735cf..130e893 100644 --- a/main.go +++ b/main.go @@ -49,6 +49,8 @@ func main() { &cmd.CmdOpen, &cmd.CmdNotifications, + + &cmd.CmdAdmin, } app.EnableBashCompletion = true err := app.Run(os.Args) diff --git a/modules/print/user.go b/modules/print/user.go index 301685a..21ef43b 100644 --- a/modules/print/user.go +++ b/modules/print/user.go @@ -77,6 +77,7 @@ var UserFields = []string{ "website", "description", "visibility", + "activated", } type printableUser struct{ *gitea.User } @@ -113,6 +114,8 @@ func (x printableUser) FormatField(field string, machineReadable bool) string { return formatBoolean(x.Restricted, !machineReadable) case "prohibit_login": return formatBoolean(x.ProhibitLogin, !machineReadable) + case "activated": + return formatBoolean(x.IsActive, !machineReadable) case "location": return x.Location case "website": -- 2.40.1 From 340f8b721f81d14e1743a67b10d33d602bf48bc1 Mon Sep 17 00:00:00 2001 From: Matti R Date: Mon, 4 Oct 2021 16:26:59 -0400 Subject: [PATCH 2/6] correct documentation --- cmd/admin/users/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/admin/users/list.go b/cmd/admin/users/list.go index c29020c..31cf8a9 100644 --- a/cmd/admin/users/list.go +++ b/cmd/admin/users/list.go @@ -31,7 +31,7 @@ var CmdUserList = cli.Command{ }, flags.AllDefaultFlags...), } -// RunUserList list user organizations +// RunUserList list users func RunUserList(cmd *cli.Context) error { ctx := context.InitCommand(cmd) client := ctx.Login.Client() -- 2.40.1 From 3aaac9b0d0f6af3b6876b5aee5f21f070436cb39 Mon Sep 17 00:00:00 2001 From: Norwin Date: Thu, 17 Mar 2022 10:09:41 +0800 Subject: [PATCH 3/6] Enhance Admin User Listing (#427) (#471) This is a patch for #427: - merge `master` - address my own review - add some more printable fields Co-authored-by: kellya Co-authored-by: Ikko Ashimine Co-authored-by: arkamar Co-authored-by: Lunny Xiao Co-authored-by: techknowlogick Co-authored-by: Norwin Reviewed-on: https://gitea.com/gitea/tea/pulls/471 Co-authored-by: Norwin Co-committed-by: Norwin --- .drone.yml | 22 ++- .gitea/issue_template/bug.md | 30 ++++ CONTRIBUTING.md | 10 +- Makefile | 19 +-- README.md | 45 +++-- build.go | 3 +- cmd/admin.go | 41 ++++- cmd/admin/users/list.go | 10 +- cmd/admin_users.go | 41 ----- cmd/categories.go | 1 - cmd/clone.go | 89 ++++++++++ cmd/comment.go | 14 +- cmd/flags/csvflag.go | 8 +- cmd/flags/{flags.go => generic.go} | 98 ----------- cmd/flags/issue_pr.go | 161 ++++++++++++++++++ cmd/issues.go | 2 +- cmd/issues/list.go | 51 +++++- cmd/milestones/create.go | 2 +- cmd/pulls.go | 2 +- cmd/pulls/create.go | 6 +- cmd/pulls/list.go | 2 +- cmd/releases/create.go | 6 +- cmd/repos.go | 2 + cmd/repos/create_from_template.go | 121 +++++++++++++ cmd/repos/fork.go | 57 +++++++ go.mod | 2 - go.sum | 4 +- main.go | 12 +- modules/config/config.go | 14 +- modules/config/login.go | 19 +++ modules/context/context.go | 5 + modules/git/url.go | 19 ++- modules/git/url_test.go | 56 ++++++ modules/interact/comments.go | 1 + modules/interact/issue_create.go | 7 +- modules/interact/milestone_create.go | 6 +- modules/interact/prompts.go | 23 ++- modules/interact/pull_review.go | 8 +- modules/print/comment.go | 4 +- modules/print/formatters.go | 8 + modules/print/issue.go | 2 +- modules/print/pull.go | 2 +- modules/print/table.go | 2 +- modules/print/user.go | 6 + modules/task/pull_create.go | 2 +- modules/task/repo_clone.go | 93 ++++++++++ modules/utils/parse.go | 2 +- .../charmbracelet/glamour/README.md | 4 +- .../charmbracelet/glamour/ansi/elements.go | 4 - .../charmbracelet/glamour/ansi/image.go | 2 +- .../charmbracelet/glamour/ansi/link.go | 2 +- .../charmbracelet/glamour/ansi/listitem.go | 3 +- .../charmbracelet/glamour/ansi/paragraph.go | 2 +- .../charmbracelet/glamour/ansi/renderer.go | 13 +- .../charmbracelet/glamour/glamour.go | 8 - vendor/modules.txt | 2 +- 56 files changed, 907 insertions(+), 273 deletions(-) create mode 100644 .gitea/issue_template/bug.md delete mode 100644 cmd/admin_users.go create mode 100644 cmd/clone.go rename cmd/flags/{flags.go => generic.go} (57%) create mode 100644 cmd/flags/issue_pr.go create mode 100644 cmd/repos/create_from_template.go create mode 100644 cmd/repos/fork.go create mode 100644 modules/git/url_test.go create mode 100644 modules/task/repo_clone.go diff --git a/.drone.yml b/.drone.yml index 4d08751..d7c79f2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -9,7 +9,7 @@ platform: steps: - name: build pull: always - image: golang:1.16 + image: golang:1.17 environment: GOPROXY: https://goproxy.cn commands: @@ -27,7 +27,7 @@ steps: - pull_request - name: unit-test - image: golang:1.16 + image: golang:1.17 commands: - make unit-test-coverage settings: @@ -40,7 +40,7 @@ steps: - pull_request - name: release-test - image: golang:1.16 + image: golang:1.17 commands: - make test settings: @@ -54,7 +54,7 @@ steps: - name: tag-test pull: always - image: golang:1.16 + image: golang:1.17 commands: - make test settings: @@ -64,7 +64,7 @@ steps: - tag - name: static - image: golang:1.16 + image: golang:1.17 environment: GOPROXY: https://goproxy.cn commands: @@ -96,7 +96,7 @@ steps: - name: tag-release pull: always - image: plugins/s3:1 + image: woodpeckerci/plugin-s3:latest settings: acl: public-read bucket: gitea-artifacts @@ -116,12 +116,11 @@ steps: - name: release-branch-release pull: always - image: plugins/s3:1 + image: woodpeckerci/plugin-s3:latest settings: acl: public-read bucket: gitea-artifacts - endpoint: https://storage.gitea.io - path_style: true + endpoint: https://ams3.digitaloceanspaces.com source: "dist/release/*" strip_prefix: dist/release/ target: "/tea/${DRONE_BRANCH##release/v}" @@ -138,12 +137,11 @@ steps: - name: release pull: always - image: plugins/s3:1 + image: woodpeckerci/plugin-s3:latest settings: acl: public-read bucket: gitea-artifacts - endpoint: https://storage.gitea.io - path_style: true + endpoint: https://ams3.digitaloceanspaces.com source: "dist/release/*" strip_prefix: dist/release/ target: /tea/master diff --git a/.gitea/issue_template/bug.md b/.gitea/issue_template/bug.md new file mode 100644 index 0000000..4dacdd6 --- /dev/null +++ b/.gitea/issue_template/bug.md @@ -0,0 +1,30 @@ +--- +name: "Bug Report" +about: "Use this template when reporting a bug, so you don't forget important information we'd ask for later." +title: "Bug: " +labels: +- kind/bug +--- + +### describe your environment +- tea version used (`tea -v`): + - [ ] I also reproduced the issue [with the latest master build](https://dl.gitea.io/tea/master) +- Gitea version used: + - [ ] the issue only occurred after updating gitea recently +- operating system: +- I make use of... + - [ ] non-standard default branch names (no `main`,`master`, or `trunk`) + - [ ] .ssh/config or .gitconfig host aliases in my git remotes + - [ ] ssh_agent or similar + - [ ] non-standard ports for gitea and/or ssh + - [ ] something else that's likely to interact badly with tea: ... + + +Please provide the output of `git remote -v` (if the issue is related to tea not finding resources on Gitea): +``` + +``` + +### describe the issue (observed vs expected behaviour) + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d32bfba..498d474 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,8 +57,8 @@ high-level discussions. ## Testing redux -Before sending code out for review, run all the test by execting: `make test` -Since TEA is an cli tool it should be obvious to test your feature localy first. +Before sending code out for review, run all the test by executing: `make test` +Since TEA is an cli tool it should be obvious to test your feature locally first. ## Vendoring @@ -77,7 +77,7 @@ You can find more information on how to get started with it on the [dep project ## Code review Changes to TEA must be reviewed before they are accepted—no matter who -makes the change, even if they are an owner or a maintainer. We use Giteas's +makes the change, even if they are an owner or a maintainer. We use Gitea's pull request & review workflow to do that. Gitea ensure every PR is reviewed by at least 2 maintainers. Please try to make your pull request easy to review for us. And, please read @@ -118,8 +118,8 @@ Some of the key points: - Always make sure that the help texts are properly set, and as concise as possible. ### Internal Module Structure -- `cmd`: only contains comand/flag options for `urfave/cli` - - subcomands are in a subpackage named after its parent comand +- `cmd`: only contains command/flag options for `urfave/cli` + - subcommands are in a subpackage named after its parent command - `modules/task`: contain func for doing something with gitea (e.g. create token by user/passwd) - `modules/print`: contain all functions that print to stdout diff --git a/Makefile b/Makefile index 5a1c971..3b2c956 100644 --- a/Makefile +++ b/Makefile @@ -26,24 +26,20 @@ TEA_VERSION_TAG ?= $(shell sed 's/+/_/' <<< $(TEA_VERSION)) TAGS ?= LDFLAGS := -X "main.Version=$(TEA_VERSION)" -X "main.Tags=$(TAGS)" -s -w -ifeq ($(STATIC),true) - # NOTE: clean up this mess, when https://github.com/golang/go/issues/26492 is resolved - # static_build is a defacto standard tag used in go packages - TAGS := osusergo,netgo,static_build,$(TAGS) - LDFLAGS := $(LDFLAGS) -linkmode=external -extldflags "-static-pie" -X "main.Tags=$(TAGS)" - export CGO_ENABLED=1 # needed for linkmode=external -endif - # override to allow passing additional goflags via make CLI override GOFLAGS := $(GOFLAGS) -mod=vendor -tags '$(TAGS)' -ldflags '$(LDFLAGS)' PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/) SOURCES ?= $(shell find . -name "*.go" -type f) +# OS specific vars. ifeq ($(OS), Windows_NT) EXECUTABLE := tea.exe else EXECUTABLE := tea + ifneq ($(shell uname -s), OpenBSD) + override BUILDMODE := -buildmode=pie + endif endif .PHONY: all @@ -124,16 +120,13 @@ check: test .PHONY: install install: $(SOURCES) @echo "installing to $(GOPATH)/bin/$(EXECUTABLE)" - $(GO) install -v -buildmode=pie $(GOFLAGS) + $(GO) install -v $(BUILDMODE) $(GOFLAGS) .PHONY: build build: $(EXECUTABLE) $(EXECUTABLE): $(SOURCES) -ifeq ($(STATIC),true) - @echo "enabling static build, make sure you have glibc-static (or equivalent) installed" -endif - $(GO) build -buildmode=pie $(GOFLAGS) -o $@ + $(GO) build $(BUILDMODE) $(GOFLAGS) -o $@ .PHONY: build-image build-image: diff --git a/README.md b/README.md index 87e9bd9..052f49d 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,19 @@ ``` tea - command line tool to interact with Gitea - version 0.7.0-preview + version 0.8.0-preview USAGE tea command [subcommand] [command options] [arguments...] DESCRIPTION - tea is a productivity helper for Gitea. It can be used to manage most entities on one - or multiple Gitea instances and provides local helpers like 'tea pull checkout'. - tea makes use of context provided by the repository in $PWD if available, but is still - usable independently of $PWD. Configuration is persisted in $XDG_CONFIG_HOME/tea. + tea is a productivity helper for Gitea. It can be used to manage most entities on + one or multiple Gitea instances & provides local helpers like 'tea pr checkout'. + + tea tries to make use of context provided by the repository in $PWD if available. + tea works best in a upstream/fork workflow, when the local main branch tracks the + upstream repo. tea assumes that local git state is published on the remote before + doing operations with tea. Configuration is persisted in $XDG_CONFIG_HOME/tea. COMMANDS help, h Shows a list of commands or help for one command @@ -30,13 +33,16 @@ times, time, t Operate on tracked times of a repository's issues & pulls organizations, organization, org List, create, delete organizations repos, repo Show repository details + comment, c Add a comment to an issue / pr HELPERS: open, o Open something of the repository in web browser notifications, notification, n Show notifications + clone, C Clone a repository locally SETUP: logins, login Log in to a Gitea server logout Log out from a Gitea server shellcompletion, autocomplete Install shell completion for tea + whoami Show current logged in user OPTIONS --help, -h show help (default: false) @@ -79,29 +85,32 @@ There are different ways to get `tea`: brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea brew install tea ``` - - arch linux ([gitea-tea](https://aur.archlinux.org/packages/gitea-tea), thirdparty) + - arch linux ([gitea-tea-git](https://aur.archlinux.org/packages/gitea-tea-git), thirdparty) - alpine linux ([tea](https://pkgs.alpinelinux.org/packages?name=tea&branch=edge), thirdparty) 2. Use the prebuilt binaries from [dl.gitea.io](https://dl.gitea.io/tea/) -3. Install from source (go 1.13 or newer is required): - ```sh - go get code.gitea.io/tea - go install code.gitea.io/tea - ``` +3. Install from source: [see *Compilation*](#compilation) 4. Docker (thirdparty): [tgerczei/tea](https://hub.docker.com/r/tgerczei/tea) ## Compilation -Make sure you have installed a current go version. -To compile the sources yourself run the following: +Make sure you have a current go version installed (1.13 or newer). -```sh -git clone https://gitea.com/gitea/tea.git -cd tea -make STATIC=true -``` +- To compile the source yourself with the recommended flags & tags: + ```sh + git clone https://gitea.com/gitea/tea.git # or: tea clone gitea.com/gitea/tea ;) + cd tea + make + ``` + Note that GNU Make (gmake on OpenBSD) is required. + +- For a quick installation without `git` & `make`: + ```sh + go get code.gitea.io/tea + go install code.gitea.io/tea + ``` ## Contributing diff --git a/build.go b/build.go index 4a0d72c..0f7e838 100644 --- a/build.go +++ b/build.go @@ -1,7 +1,8 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -//+build vendor +//go:build vendor +// +build vendor package main diff --git a/cmd/admin.go b/cmd/admin.go index c8640df..6dacebf 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -5,20 +5,51 @@ package cmd import ( + "code.gitea.io/tea/cmd/admin/users" + "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/print" "github.com/urfave/cli/v2" ) -// CmdAdmin represents the admin sub-commands +// CmdAdmin represents the namespace of admin commands. +// The command itself has no functionality, but hosts subcommands. var CmdAdmin = cli.Command{ Name: "admin", + Usage: "Operations requiring admin access on the Gitea instance", Aliases: []string{"a"}, - Category: catAdmin, + Category: catHelpers, Action: func(cmd *cli.Context) error { - // TODO: this is just a stub for all admin actions - // there is no default admin action - return nil + return cli.ShowSubcommandHelp(cmd) }, Subcommands: []*cli.Command{ &cmdAdminUsers, }, } + +var cmdAdminUsers = cli.Command{ + Name: "users", + Aliases: []string{"u"}, + Usage: "Manage registered users", + Action: func(ctx *cli.Context) error { + if ctx.Args().Len() == 1 { + return runAdminUserDetail(ctx, ctx.Args().First()) + } + return users.RunUserList(ctx) + }, + Subcommands: []*cli.Command{ + &users.CmdUserList, + }, + Flags: users.CmdUserList.Flags, +} + +func runAdminUserDetail(cmd *cli.Context, u string) error { + ctx := context.InitCommand(cmd) + client := ctx.Login.Client() + user, _, err := client.GetUserInfo(u) + if err != nil { + return err + } + + print.UserDetails(user) + return nil +} diff --git a/cmd/admin/users/list.go b/cmd/admin/users/list.go index 31cf8a9..8205aed 100644 --- a/cmd/admin/users/list.go +++ b/cmd/admin/users/list.go @@ -34,16 +34,16 @@ var CmdUserList = cli.Command{ // RunUserList list users func RunUserList(cmd *cli.Context) error { ctx := context.InitCommand(cmd) - client := ctx.Login.Client() - users, _, err := client.AdminListUsers(gitea.AdminListUsersOptions{ - ListOptions: ctx.GetListOptions(), - }) + fields, err := userFieldsFlag.GetValues(cmd) if err != nil { return err } - fields, err := userFieldsFlag.GetValues(cmd) + client := ctx.Login.Client() + users, _, err := client.AdminListUsers(gitea.AdminListUsersOptions{ + ListOptions: ctx.GetListOptions(), + }) if err != nil { return err } diff --git a/cmd/admin_users.go b/cmd/admin_users.go deleted file mode 100644 index 056c996..0000000 --- a/cmd/admin_users.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package cmd - -import ( - "code.gitea.io/tea/cmd/admin/users" - "code.gitea.io/tea/modules/context" - "code.gitea.io/tea/modules/print" - - "github.com/urfave/cli/v2" -) - -var cmdAdminUsers = cli.Command{ - Name: "user", - Aliases: []string{"u"}, - Category: catAdmin, - Action: func(ctx *cli.Context) error { - if ctx.Args().Len() == 1 { - return runAdminUserDetail(ctx, ctx.Args().First()) - } - return users.RunUserList(ctx) - }, - Subcommands: []*cli.Command{ - &users.CmdUserList, - }, - Flags: users.CmdUserList.Flags, -} - -func runAdminUserDetail(cmd *cli.Context, u string) error { - ctx := context.InitCommand(cmd) - client := ctx.Login.Client() - user, _, err := client.GetUserInfo(u) - if err != nil { - return err - } - - print.UserDetails(user) - return nil -} diff --git a/cmd/categories.go b/cmd/categories.go index c368b5a..4b2088b 100644 --- a/cmd/categories.go +++ b/cmd/categories.go @@ -8,5 +8,4 @@ var ( catSetup = "SETUP" catEntities = "ENTITIES" catHelpers = "HELPERS" - catAdmin = "ADMIN" ) diff --git a/cmd/clone.go b/cmd/clone.go new file mode 100644 index 0000000..e7e4e42 --- /dev/null +++ b/cmd/clone.go @@ -0,0 +1,89 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/modules/config" + "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/git" + "code.gitea.io/tea/modules/interact" + "code.gitea.io/tea/modules/task" + "code.gitea.io/tea/modules/utils" + + "github.com/urfave/cli/v2" +) + +// CmdRepoClone represents a sub command of repos to create a local copy +var CmdRepoClone = cli.Command{ + Name: "clone", + Aliases: []string{"C"}, + Usage: "Clone a repository locally", + Description: `Clone a repository locally, without a local git installation required. +The repo slug can be specified in different formats: + gitea/tea + tea + gitea.com/gitea/tea + git@gitea.com:gitea/tea + https://gitea.com/gitea/tea + ssh://gitea.com:22/gitea/tea +When a host is specified in the repo-slug, it will override the login specified with --login. + `, + Category: catHelpers, + Action: runRepoClone, + ArgsUsage: " [target dir]", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "depth", + Aliases: []string{"d"}, + Usage: "num commits to fetch, defaults to all", + }, + &flags.LoginFlag, + }, +} + +func runRepoClone(cmd *cli.Context) error { + ctx := context.InitCommand(cmd) + + args := ctx.Args() + if args.Len() < 1 { + return cli.ShowCommandHelp(cmd, "clone") + } + dir := args.Get(1) + + var ( + login *config.Login = ctx.Login + owner string = ctx.Login.User + repo string + ) + + // parse first arg as repo specifier + repoSlug := args.Get(0) + url, err := git.ParseURL(repoSlug) + if err != nil { + return err + } + + owner, repo = utils.GetOwnerAndRepo(url.Path, login.User) + if url.Host != "" { + login = config.GetLoginByHost(url.Host) + if login == nil { + return fmt.Errorf("No login configured matching host '%s', run `tea login add` first", url.Host) + } + } + + _, err = task.RepoClone( + dir, + login, + owner, + repo, + interact.PromptPassword, + ctx.Int("depth"), + ) + + return err +} diff --git a/cmd/comment.go b/cmd/comment.go index 169aaa3..67b8e08 100644 --- a/cmd/comment.go +++ b/cmd/comment.go @@ -9,13 +9,15 @@ import ( "io/ioutil" "strings" - "code.gitea.io/tea/modules/interact" - - "code.gitea.io/sdk/gitea" "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/interact" "code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/utils" + + "code.gitea.io/sdk/gitea" + "github.com/AlecAivazis/survey/v2" "github.com/urfave/cli/v2" ) @@ -54,7 +56,11 @@ func runAddComment(cmd *cli.Context) error { body = strings.Join([]string{body, string(bodyStdin)}, "\n\n") } } else if len(body) == 0 { - if body, err = interact.PromptMultiline("Content"); err != nil { + if err = survey.AskOne(interact.NewMultiline(interact.Multiline{ + Message: "Comment:", + Syntax: "md", + UseEditor: config.GetPreferences().Editor, + }), &body); err != nil { return err } } diff --git a/cmd/flags/csvflag.go b/cmd/flags/csvflag.go index 99f80fb..02def8e 100644 --- a/cmd/flags/csvflag.go +++ b/cmd/flags/csvflag.go @@ -21,15 +21,19 @@ type CsvFlag struct { // NewCsvFlag creates a CsvFlag, while setting its usage string and default values func NewCsvFlag(name, usage string, aliases, availableValues, defaults []string) *CsvFlag { + var availableDesc string + if len(availableValues) != 0 { + availableDesc = " Available values:" + } return &CsvFlag{ AvailableFields: availableValues, StringFlag: cli.StringFlag{ Name: name, Aliases: aliases, Value: strings.Join(defaults, ","), - Usage: fmt.Sprintf(`Comma-separated list of %s. Available values: + Usage: fmt.Sprintf(`Comma-separated list of %s.%s %s - `, usage, strings.Join(availableValues, ",")), + `, usage, availableDesc, strings.Join(availableValues, ",")), }, } } diff --git a/cmd/flags/flags.go b/cmd/flags/generic.go similarity index 57% rename from cmd/flags/flags.go rename to cmd/flags/generic.go index c3de546..b86ab3c 100644 --- a/cmd/flags/flags.go +++ b/cmd/flags/generic.go @@ -5,14 +5,6 @@ package flags import ( - "fmt" - "strings" - - "code.gitea.io/sdk/gitea" - "code.gitea.io/tea/modules/context" - "code.gitea.io/tea/modules/task" - - "github.com/araddon/dateparse" "github.com/urfave/cli/v2" ) @@ -44,13 +36,6 @@ var OutputFlag = cli.StringFlag{ Usage: "Output format. (csv, simple, table, tsv, yaml)", } -// StateFlag provides flag to specify issue/pr state, defaulting to "open" -var StateFlag = cli.StringFlag{ - Name: "state", - Usage: "Filter by state (all|open|closed)", - DefaultText: "open", -} - // PaginationPageFlag provides flag for pagination options var PaginationPageFlag = cli.StringFlag{ Name: "page", @@ -93,13 +78,6 @@ var AllDefaultFlags = append([]cli.Flag{ &RemoteFlag, }, LoginOutputFlags...) -// IssuePRFlags defines flags that should be available on issue & pr listing flags. -var IssuePRFlags = append([]cli.Flag{ - &StateFlag, - &PaginationPageFlag, - &PaginationLimitFlag, -}, AllDefaultFlags...) - // NotificationFlags defines flags that should be available on notifications. var NotificationFlags = append([]cli.Flag{ NotificationStateFlag, @@ -121,82 +99,6 @@ var NotificationStateFlag = NewCsvFlag( []string{"unread", "pinned"}, ) -// IssuePREditFlags defines flags for properties of issues and PRs -var IssuePREditFlags = append([]cli.Flag{ - &cli.StringFlag{ - Name: "title", - Aliases: []string{"t"}, - }, - &cli.StringFlag{ - Name: "description", - Aliases: []string{"d"}, - }, - &cli.StringFlag{ - Name: "assignees", - Aliases: []string{"a"}, - Usage: "Comma-separated list of usernames to assign", - }, - &cli.StringFlag{ - Name: "labels", - Aliases: []string{"L"}, - Usage: "Comma-separated list of labels to assign", - }, - &cli.StringFlag{ - Name: "deadline", - Aliases: []string{"D"}, - Usage: "Deadline timestamp to assign", - }, - &cli.StringFlag{ - Name: "milestone", - Aliases: []string{"m"}, - Usage: "Milestone to assign", - }, -}, LoginRepoFlags...) - -// GetIssuePREditFlags parses all IssuePREditFlags -func GetIssuePREditFlags(ctx *context.TeaContext) (*gitea.CreateIssueOption, error) { - opts := gitea.CreateIssueOption{ - Title: ctx.String("title"), - Body: ctx.String("description"), - Assignees: strings.Split(ctx.String("assignees"), ","), - } - var err error - - date := ctx.String("deadline") - if date != "" { - t, err := dateparse.ParseAny(date) - if err != nil { - return nil, err - } - opts.Deadline = &t - } - - client := ctx.Login.Client() - - labelNames := strings.Split(ctx.String("labels"), ",") - if len(labelNames) != 0 { - if client == nil { - client = ctx.Login.Client() - } - if opts.Labels, err = task.ResolveLabelNames(client, ctx.Owner, ctx.Repo, labelNames); err != nil { - return nil, err - } - } - - if milestoneName := ctx.String("milestone"); len(milestoneName) != 0 { - if client == nil { - client = ctx.Login.Client() - } - ms, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, milestoneName) - if err != nil { - return nil, fmt.Errorf("Milestone '%s' not found", milestoneName) - } - opts.Milestone = ms.ID - } - - return &opts, nil -} - // FieldsFlag generates a flag selecting printable fields. // To retrieve the value, use f.GetValues() func FieldsFlag(availableFields, defaultFields []string) *CsvFlag { diff --git a/cmd/flags/issue_pr.go b/cmd/flags/issue_pr.go new file mode 100644 index 0000000..7f4a514 --- /dev/null +++ b/cmd/flags/issue_pr.go @@ -0,0 +1,161 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package flags + +import ( + "fmt" + "strings" + + "code.gitea.io/sdk/gitea" + "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/task" + + "github.com/araddon/dateparse" + "github.com/urfave/cli/v2" +) + +// StateFlag provides flag to specify issue/pr state, defaulting to "open" +var StateFlag = cli.StringFlag{ + Name: "state", + Usage: "Filter by state (all|open|closed)", + DefaultText: "open", +} + +// MilestoneFilterFlag is a CSV flag used to filter issues by milestones +var MilestoneFilterFlag = NewCsvFlag( + "milestones", + "milestones to match issues against", + []string{"m"}, nil, nil) + +// LabelFilterFlag is a CSV flag used to filter issues by labels +var LabelFilterFlag = NewCsvFlag( + "labels", + "labels to match issues against", + []string{"L"}, nil, nil) + +// PRListingFlags defines flags that should be available on pr listing flags. +var PRListingFlags = append([]cli.Flag{ + &StateFlag, + &PaginationPageFlag, + &PaginationLimitFlag, +}, AllDefaultFlags...) + +// IssueListingFlags defines flags that should be available on issue listing flags. +var IssueListingFlags = append([]cli.Flag{ + &StateFlag, + &cli.StringFlag{ + Name: "kind", + Aliases: []string{"K"}, + Usage: "Whether to return `issues`, `pulls`, or `all` (you can use this to apply advanced search filters to PRs)", + DefaultText: "issues", + }, + &cli.StringFlag{ + Name: "keyword", + Aliases: []string{"k"}, + Usage: "Filter by search string", + }, + LabelFilterFlag, + MilestoneFilterFlag, + &cli.StringFlag{ + Name: "author", + Aliases: []string{"A"}, + }, + &cli.StringFlag{ + Name: "assignee", + Aliases: []string{"a"}, + }, + &cli.StringFlag{ + Name: "mentions", + Aliases: []string{"M"}, + }, + &cli.StringFlag{ + Name: "from", + Aliases: []string{"F"}, + Usage: "Filter by activity after this date", + }, + &cli.StringFlag{ + Name: "until", + Aliases: []string{"u"}, + Usage: "Filter by activity before this date", + }, + &PaginationPageFlag, + &PaginationLimitFlag, +}, AllDefaultFlags...) + +// IssuePREditFlags defines flags for properties of issues and PRs +var IssuePREditFlags = append([]cli.Flag{ + &cli.StringFlag{ + Name: "title", + Aliases: []string{"t"}, + }, + &cli.StringFlag{ + Name: "description", + Aliases: []string{"d"}, + }, + &cli.StringFlag{ + Name: "assignees", + Aliases: []string{"a"}, + Usage: "Comma-separated list of usernames to assign", + }, + &cli.StringFlag{ + Name: "labels", + Aliases: []string{"L"}, + Usage: "Comma-separated list of labels to assign", + }, + &cli.StringFlag{ + Name: "deadline", + Aliases: []string{"D"}, + Usage: "Deadline timestamp to assign", + }, + &cli.StringFlag{ + Name: "milestone", + Aliases: []string{"m"}, + Usage: "Milestone to assign", + }, +}, LoginRepoFlags...) + +// GetIssuePREditFlags parses all IssuePREditFlags +func GetIssuePREditFlags(ctx *context.TeaContext) (*gitea.CreateIssueOption, error) { + opts := gitea.CreateIssueOption{ + Title: ctx.String("title"), + Body: ctx.String("description"), + Assignees: strings.Split(ctx.String("assignees"), ","), + } + var err error + + date := ctx.String("deadline") + if date != "" { + t, err := dateparse.ParseAny(date) + if err != nil { + return nil, err + } + opts.Deadline = &t + } + + client := ctx.Login.Client() + + labelNames := strings.Split(ctx.String("labels"), ",") + if len(labelNames) != 0 { + if client == nil { + client = ctx.Login.Client() + } + if opts.Labels, err = task.ResolveLabelNames(client, ctx.Owner, ctx.Repo, labelNames); err != nil { + return nil, err + } + } + + if milestoneName := ctx.String("milestone"); len(milestoneName) != 0 { + if client == nil { + client = ctx.Login.Client() + } + ms, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, milestoneName) + if err != nil { + return nil, fmt.Errorf("Milestone '%s' not found", milestoneName) + } + opts.Milestone = ms.ID + } + + return &opts, nil +} diff --git a/cmd/issues.go b/cmd/issues.go index 658f98d..3648e4f 100644 --- a/cmd/issues.go +++ b/cmd/issues.go @@ -34,7 +34,7 @@ var CmdIssues = cli.Command{ Flags: append([]cli.Flag{ &cli.BoolFlag{ Name: "comments", - Usage: "Wether to display comments (will prompt if not provided & run interactively)", + Usage: "Whether to display comments (will prompt if not provided & run interactively)", }, }, issues.CmdIssuesList.Flags...), } diff --git a/cmd/issues/list.go b/cmd/issues/list.go index 3089af3..e5999fb 100644 --- a/cmd/issues/list.go +++ b/cmd/issues/list.go @@ -5,11 +5,15 @@ package issues import ( + "fmt" + "time" + "code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/print" "code.gitea.io/sdk/gitea" + "github.com/araddon/dateparse" "github.com/urfave/cli/v2" ) @@ -24,7 +28,7 @@ var CmdIssuesList = cli.Command{ Usage: "List issues of the repository", Description: `List issues of the repository`, Action: RunIssuesList, - Flags: append([]cli.Flag{issueFieldsFlag}, flags.IssuePRFlags...), + Flags: append([]cli.Flag{issueFieldsFlag}, flags.IssueListingFlags...), } // RunIssuesList list issues @@ -36,16 +40,57 @@ func RunIssuesList(cmd *cli.Context) error { switch ctx.String("state") { case "all": state = gitea.StateAll - case "open": + case "", "open": state = gitea.StateOpen case "closed": state = gitea.StateClosed + default: + return fmt.Errorf("unknown state '%s'", ctx.String("state")) } + kind := gitea.IssueTypeIssue + switch ctx.String("kind") { + case "", "issues", "issue": + kind = gitea.IssueTypeIssue + case "pulls", "pull", "pr": + kind = gitea.IssueTypePull + case "all": + kind = gitea.IssueTypeAll + default: + return fmt.Errorf("unknown kind '%s'", ctx.String("kind")) + } + + var err error + var from, until time.Time + if ctx.IsSet("from") { + from, err = dateparse.ParseLocal(ctx.String("from")) + if err != nil { + return err + } + } + if ctx.IsSet("until") { + until, err = dateparse.ParseLocal(ctx.String("until")) + if err != nil { + return err + } + } + + // ignore error, as we don't do any input validation on these flags + labels, _ := flags.LabelFilterFlag.GetValues(cmd) + milestones, _ := flags.MilestoneFilterFlag.GetValues(cmd) + issues, _, err := ctx.Login.Client().ListRepoIssues(ctx.Owner, ctx.Repo, gitea.ListIssueOption{ ListOptions: ctx.GetListOptions(), State: state, - Type: gitea.IssueTypeIssue, + Type: kind, + KeyWord: ctx.String("keyword"), + CreatedBy: ctx.String("author"), + AssignedBy: ctx.String("assigned-to"), + MentionedBy: ctx.String("mentions"), + Labels: labels, + Milestones: milestones, + Since: from, + Before: until, }) if err != nil { diff --git a/cmd/milestones/create.go b/cmd/milestones/create.go index 0a3001b..e0f41d2 100644 --- a/cmd/milestones/create.go +++ b/cmd/milestones/create.go @@ -55,7 +55,7 @@ func runMilestonesCreate(cmd *cli.Context) error { deadline := &time.Time{} if date != "" { t, err := dateparse.ParseAny(date) - if err == nil { + if err != nil { return err } deadline = &t diff --git a/cmd/pulls.go b/cmd/pulls.go index 8cb3a85..76dd842 100644 --- a/cmd/pulls.go +++ b/cmd/pulls.go @@ -30,7 +30,7 @@ var CmdPulls = cli.Command{ Flags: append([]cli.Flag{ &cli.BoolFlag{ Name: "comments", - Usage: "Wether to display comments (will prompt if not provided & run interactively)", + Usage: "Whether to display comments (will prompt if not provided & run interactively)", }, }, pulls.CmdPullsList.Flags...), Subcommands: []*cli.Command{ diff --git a/cmd/pulls/create.go b/cmd/pulls/create.go index e1a1d11..bc9de1e 100644 --- a/cmd/pulls/create.go +++ b/cmd/pulls/create.go @@ -18,17 +18,17 @@ var CmdPullsCreate = cli.Command{ Name: "create", Aliases: []string{"c"}, Usage: "Create a pull-request", - Description: "Create a pull-request", + Description: "Create a pull-request in the current repo", Action: runPullsCreate, Flags: append([]cli.Flag{ &cli.StringFlag{ Name: "head", - Usage: "Set head branch (default is current one)", + Usage: "Branch name of the PR source (default is current one). To specify a different head repo, use :", }, &cli.StringFlag{ Name: "base", Aliases: []string{"b"}, - Usage: "Set base branch (default is default branch)", + Usage: "Branch name of the PR target (default is repos default branch)", }, }, flags.IssuePREditFlags...), } diff --git a/cmd/pulls/list.go b/cmd/pulls/list.go index 54f3a1a..b09f6bc 100644 --- a/cmd/pulls/list.go +++ b/cmd/pulls/list.go @@ -24,7 +24,7 @@ var CmdPullsList = cli.Command{ Usage: "List pull requests of the repository", Description: `List pull requests of the repository`, Action: RunPullsList, - Flags: append([]cli.Flag{pullFieldsFlag}, flags.IssuePRFlags...), + Flags: append([]cli.Flag{pullFieldsFlag}, flags.PRListingFlags...), } // RunPullsList return list of pulls diff --git a/cmd/releases/create.go b/cmd/releases/create.go index ef203f3..3fe3483 100644 --- a/cmd/releases/create.go +++ b/cmd/releases/create.go @@ -27,11 +27,11 @@ var CmdReleaseCreate = cli.Command{ Flags: append([]cli.Flag{ &cli.StringFlag{ Name: "tag", - Usage: "Tag name", + Usage: "Tag name. If the tag does not exist yet, it will be created by Gitea", }, &cli.StringFlag{ Name: "target", - Usage: "Target refs, branch name or commit id", + Usage: "Target branch name or commit hash. Defaults to the default branch of the repo", }, &cli.StringFlag{ Name: "title", @@ -56,7 +56,7 @@ var CmdReleaseCreate = cli.Command{ &cli.StringSliceFlag{ Name: "asset", Aliases: []string{"a"}, - Usage: "List of files to attach", + Usage: "Path to file attachment. Can be specified multiple times", }, }, flags.AllDefaultFlags...), } diff --git a/cmd/repos.go b/cmd/repos.go index 3962706..47f0237 100644 --- a/cmd/repos.go +++ b/cmd/repos.go @@ -27,6 +27,8 @@ var CmdRepos = cli.Command{ &repos.CmdReposList, &repos.CmdReposSearch, &repos.CmdRepoCreate, + &repos.CmdRepoCreateFromTemplate, + &repos.CmdRepoFork, }, Flags: repos.CmdReposListFlags, } diff --git a/cmd/repos/create_from_template.go b/cmd/repos/create_from_template.go new file mode 100644 index 0000000..f6511c0 --- /dev/null +++ b/cmd/repos/create_from_template.go @@ -0,0 +1,121 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repos + +import ( + "fmt" + + "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/print" + "code.gitea.io/tea/modules/utils" + + "code.gitea.io/sdk/gitea" + "github.com/urfave/cli/v2" +) + +// CmdRepoCreateFromTemplate represents a sub command of repos to generate one from a template repo +var CmdRepoCreateFromTemplate = cli.Command{ + Name: "create-from-template", + Aliases: []string{"ct"}, + Usage: "Create a repository based on an existing template", + Description: "Create a repository based on an existing template", + Action: runRepoCreateFromTemplate, + Flags: append([]cli.Flag{ + &cli.StringFlag{ + Name: "template", + Aliases: []string{"t"}, + Required: true, + Usage: "source template to copy from", + }, + &cli.StringFlag{ + Name: "name", + Aliases: []string{"n"}, + Required: true, + Usage: "name of new repo", + }, + &cli.StringFlag{ + Name: "owner", + Aliases: []string{"O"}, + Usage: "name of repo owner", + }, + &cli.BoolFlag{ + Name: "private", + Usage: "make new repo private", + }, + &cli.StringFlag{ + Name: "description", + Aliases: []string{"desc"}, + Usage: "add custom description to repo", + }, + &cli.BoolFlag{ + Name: "content", + Value: true, + Usage: "copy git content from template", + }, + &cli.BoolFlag{ + Name: "githooks", + Value: true, + Usage: "copy git hooks from template", + }, + &cli.BoolFlag{ + Name: "avatar", + Value: true, + Usage: "copy repo avatar from template", + }, + &cli.BoolFlag{ + Name: "labels", + Value: true, + Usage: "copy repo labels from template", + }, + &cli.BoolFlag{ + Name: "topics", + Value: true, + Usage: "copy topics from template", + }, + &cli.BoolFlag{ + Name: "webhooks", + Usage: "copy webhooks from template", + }, + }, flags.LoginOutputFlags...), +} + +func runRepoCreateFromTemplate(cmd *cli.Context) error { + ctx := context.InitCommand(cmd) + client := ctx.Login.Client() + + templateOwner, templateRepo := utils.GetOwnerAndRepo(ctx.String("template"), ctx.Login.User) + owner := ctx.Login.User + if ctx.IsSet("owner") { + owner = ctx.String("owner") + } + + opts := gitea.CreateRepoFromTemplateOption{ + Name: ctx.String("name"), + Owner: owner, + Description: ctx.String("description"), + Private: ctx.Bool("private"), + GitContent: ctx.Bool("content"), + GitHooks: ctx.Bool("githooks"), + Avatar: ctx.Bool("avatar"), + Labels: ctx.Bool("labels"), + Topics: ctx.Bool("topics"), + Webhooks: ctx.Bool("webhooks"), + } + + repo, _, err := client.CreateRepoFromTemplate(templateOwner, templateRepo, opts) + if err != nil { + return err + } + + topics, _, err := client.ListRepoTopics(repo.Owner.UserName, repo.Name, gitea.ListRepoTopicsOptions{}) + if err != nil { + return err + } + print.RepoDetails(repo, topics) + + fmt.Printf("%s\n", repo.HTMLURL) + return nil +} diff --git a/cmd/repos/fork.go b/cmd/repos/fork.go new file mode 100644 index 0000000..1811fce --- /dev/null +++ b/cmd/repos/fork.go @@ -0,0 +1,57 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repos + +import ( + "fmt" + + "code.gitea.io/tea/cmd/flags" + "code.gitea.io/tea/modules/context" + "code.gitea.io/tea/modules/print" + + "code.gitea.io/sdk/gitea" + "github.com/urfave/cli/v2" +) + +// CmdRepoFork represents a sub command of repos to fork an existing repo +var CmdRepoFork = cli.Command{ + Name: "fork", + Aliases: []string{"f"}, + Usage: "Fork an existing repository", + Description: "Create a repository from an existing repo", + Action: runRepoFork, + Flags: append([]cli.Flag{ + &cli.StringFlag{ + Name: "owner", + Aliases: []string{"O"}, + Usage: "name of fork's owner, defaults to current user", + }, + }, flags.LoginRepoFlags...), +} + +func runRepoFork(cmd *cli.Context) error { + ctx := context.InitCommand(cmd) + client := ctx.Login.Client() + + opts := gitea.CreateForkOption{} + if ctx.IsSet("owner") { + owner := ctx.String("owner") + opts.Organization = &owner + } + + repo, _, err := client.CreateFork(ctx.Owner, ctx.Repo, opts) + if err != nil { + return err + } + + topics, _, err := client.ListRepoTopics(repo.Owner.UserName, repo.Name, gitea.ListRepoTopicsOptions{}) + if err != nil { + return err + } + print.RepoDetails(repo, topics) + + fmt.Printf("%s\n", repo.HTMLURL) + return nil +} diff --git a/go.mod b/go.mod index def58ab..d5ec365 100644 --- a/go.mod +++ b/go.mod @@ -37,5 +37,3 @@ require ( golang.org/x/tools v0.1.5 // indirect gopkg.in/yaml.v2 v2.4.0 ) - -replace github.com/charmbracelet/glamour => github.com/noerw/glamour v0.3.0-patch diff --git a/go.sum b/go.sum index 8b763cd..ddb6b1c 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/charmbracelet/glamour v0.3.0 h1:3H+ZrKlSg8s+WU6V7eF2eRVYt8lCueffbi7r2+ffGkc= +github.com/charmbracelet/glamour v0.3.0/go.mod h1:TzF0koPZhqq0YVBNL100cPHznAAjVj7fksX2RInwjGw= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -129,8 +131,6 @@ github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/f github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8= github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/noerw/glamour v0.3.0-patch h1:yc3wdbUIySok6KYeX5BtWnlj+PvP1uYeCeTSwq2rtSw= -github.com/noerw/glamour v0.3.0-patch/go.mod h1:TzF0koPZhqq0YVBNL100cPHznAAjVj7fksX2RInwjGw= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/main.go b/main.go index 130e893..ccd83d9 100644 --- a/main.go +++ b/main.go @@ -49,6 +49,7 @@ func main() { &cmd.CmdOpen, &cmd.CmdNotifications, + &cmd.CmdRepoClone, &cmd.CmdAdmin, } @@ -70,10 +71,13 @@ func formatBuiltWith(Tags string) string { return " built with: " + strings.Replace(Tags, " ", ", ", -1) } -var appDescription = `tea is a productivity helper for Gitea. It can be used to manage most entities on one -or multiple Gitea instances and provides local helpers like 'tea pull checkout'. -tea makes use of context provided by the repository in $PWD if available, but is still -usable independently of $PWD. Configuration is persisted in $XDG_CONFIG_HOME/tea. +var appDescription = `tea is a productivity helper for Gitea. It can be used to manage most entities on +one or multiple Gitea instances & provides local helpers like 'tea pr checkout'. + +tea tries to make use of context provided by the repository in $PWD if available. +tea works best in a upstream/fork workflow, when the local main branch tracks the +upstream repo. tea assumes that local git state is published on the remote before +doing operations with tea. Configuration is persisted in $XDG_CONFIG_HOME/tea. ` var helpTemplate = bold(` diff --git a/modules/config/config.go b/modules/config/config.go index 74c7b5a..de6f25a 100644 --- a/modules/config/config.go +++ b/modules/config/config.go @@ -17,9 +17,16 @@ import ( "gopkg.in/yaml.v2" ) +// Preferences that are stored in and read from the config file +type Preferences struct { + // Prefer using an external text editor over inline multiline prompts + Editor bool `yaml:"editor"` +} + // LocalConfig represents local configurations type LocalConfig struct { - Logins []Login `yaml:"logins"` + Logins []Login `yaml:"logins"` + Prefs Preferences `yaml:"preferences"` } var ( @@ -55,6 +62,11 @@ func GetConfigPath() string { return configFilePath } +// GetPreferences returns preferences based on the config file +func GetPreferences() Preferences { + return config.Prefs +} + // loadConfig load config from file func loadConfig() (err error) { loadConfigOnce.Do(func() { diff --git a/modules/config/login.go b/modules/config/login.go index 47f944c..ddecd2f 100644 --- a/modules/config/login.go +++ b/modules/config/login.go @@ -111,6 +111,25 @@ func GetLoginByToken(token string) *Login { return nil } +// GetLoginByHost finds a login by it's server URL +func GetLoginByHost(host string) *Login { + err := loadConfig() + if err != nil { + log.Fatal(err) + } + + for _, l := range config.Logins { + loginURL, err := url.Parse(l.URL) + if err != nil { + log.Fatal(err) + } + if loginURL.Host == host { + return &l + } + } + return nil +} + // DeleteLogin delete a login by name from config func DeleteLogin(name string) error { var idx = -1 diff --git a/modules/context/context.go b/modules/context/context.go index e07ff9c..bfdaf64 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -164,6 +164,11 @@ func contextFromLocalRepo(repoPath, remoteValue string) (*git.TeaRepo, *config.L remoteValue = remote } if len(gitConfig.Remotes) > 1 { + // prefer origin if there is multiple remotes + _, ok := gitConfig.Remotes["origin"] + if ok { + remoteValue = "origin" + } // if master branch is present, use it as the default remote mainBranches := []string{"main", "master", "trunk"} for _, b := range mainBranches { diff --git a/modules/git/url.go b/modules/git/url.go index 5b5a9a5..f76a666 100644 --- a/modules/git/url.go +++ b/modules/git/url.go @@ -22,13 +22,18 @@ type URLParser struct { func (p *URLParser) Parse(rawURL string) (u *url.URL, err error) { rawURL = strings.TrimSpace(rawURL) - // convert the weird git ssh url format to a canonical url: - // git@gitea.com:gitea/tea -> ssh://git@gitea.com/gitea/tea - if !protocolRe.MatchString(rawURL) && - strings.Contains(rawURL, ":") && - // not a Windows path - !strings.Contains(rawURL, "\\") { - rawURL = "ssh://" + strings.Replace(rawURL, ":", "/", 1) + if !protocolRe.MatchString(rawURL) { + // convert the weird git ssh url format to a canonical url: + // git@gitea.com:gitea/tea -> ssh://git@gitea.com/gitea/tea + if strings.Contains(rawURL, ":") && + // not a Windows path + !strings.Contains(rawURL, "\\") { + rawURL = "ssh://" + strings.Replace(rawURL, ":", "/", 1) + } else if !strings.Contains(rawURL, "@") && + strings.Count(rawURL, "/") == 2 { + // match cases like gitea.com/gitea/tea + rawURL = "https://" + rawURL + } } u, err = url.Parse(rawURL) diff --git a/modules/git/url_test.go b/modules/git/url_test.go new file mode 100644 index 0000000..f342525 --- /dev/null +++ b/modules/git/url_test.go @@ -0,0 +1,56 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package git + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseUrl(t *testing.T) { + u, err := ParseURL("ssh://git@gitea.com:3000/gitea/tea") + assert.NoError(t, err) + assert.Equal(t, "gitea.com:3000", u.Host) + assert.Equal(t, "ssh", u.Scheme) + assert.Equal(t, "/gitea/tea", u.Path) + + u, err = ParseURL("https://gitea.com/gitea/tea") + assert.NoError(t, err) + assert.Equal(t, "gitea.com", u.Host) + assert.Equal(t, "https", u.Scheme) + assert.Equal(t, "/gitea/tea", u.Path) + + u, err = ParseURL("git@gitea.com:gitea/tea") + assert.NoError(t, err) + assert.Equal(t, "gitea.com", u.Host) + assert.Equal(t, "ssh", u.Scheme) + assert.Equal(t, "/gitea/tea", u.Path) + + u, err = ParseURL("gitea.com/gitea/tea") + assert.NoError(t, err) + assert.Equal(t, "gitea.com", u.Host) + assert.Equal(t, "https", u.Scheme) + assert.Equal(t, "/gitea/tea", u.Path) + + u, err = ParseURL("foo/bar") + assert.NoError(t, err) + assert.Equal(t, "", u.Host) + assert.Equal(t, "", u.Scheme) + assert.Equal(t, "foo/bar", u.Path) + + u, err = ParseURL("/foo/bar") + assert.NoError(t, err) + assert.Equal(t, "", u.Host) + assert.Equal(t, "https", u.Scheme) + assert.Equal(t, "/foo/bar", u.Path) + + // this case is unintuitive, but to ambiguous to be handled differently + u, err = ParseURL("gitea.com") + assert.NoError(t, err) + assert.Equal(t, "", u.Host) + assert.Equal(t, "", u.Scheme) + assert.Equal(t, "gitea.com", u.Path) +} diff --git a/modules/interact/comments.go b/modules/interact/comments.go index aebe4bd..a1adcef 100644 --- a/modules/interact/comments.go +++ b/modules/interact/comments.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/sdk/gitea" "code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/print" + "github.com/AlecAivazis/survey/v2" "golang.org/x/crypto/ssh/terminal" ) diff --git a/modules/interact/issue_create.go b/modules/interact/issue_create.go index e9b79a8..4cdab29 100644 --- a/modules/interact/issue_create.go +++ b/modules/interact/issue_create.go @@ -43,7 +43,12 @@ func promptIssueProperties(login *config.Login, owner, repo string, o *gitea.Cre } // description - promptD := &survey.Multiline{Message: "Issue description:", Default: o.Body} + promptD := NewMultiline(Multiline{ + Message: "Issue description:", + Default: o.Body, + Syntax: "md", + UseEditor: config.GetPreferences().Editor, + }) if err = survey.AskOne(promptD, &o.Body); err != nil { return err } diff --git a/modules/interact/milestone_create.go b/modules/interact/milestone_create.go index 8e8b2b1..07846b5 100644 --- a/modules/interact/milestone_create.go +++ b/modules/interact/milestone_create.go @@ -33,7 +33,11 @@ func CreateMilestone(login *config.Login, owner, repo string) error { } // description - promptM := &survey.Multiline{Message: "Milestone description:"} + promptM := NewMultiline(Multiline{ + Message: "Milestone description:", + Syntax: "md", + UseEditor: config.GetPreferences().Editor, + }) if err := survey.AskOne(promptM, &description); err != nil { return err } diff --git a/modules/interact/prompts.go b/modules/interact/prompts.go index debe944..4775f39 100644 --- a/modules/interact/prompts.go +++ b/modules/interact/prompts.go @@ -14,9 +14,26 @@ import ( "github.com/araddon/dateparse" ) -// PromptMultiline runs a textfield-style prompt and blocks until input was made. -func PromptMultiline(message string) (content string, err error) { - err = survey.AskOne(&survey.Multiline{Message: message}, &content) +// Multiline represents options for a prompt that expects multiline input +type Multiline struct { + Message string + Default string + Syntax string + UseEditor bool +} + +// NewMultiline creates a prompt that switches between the inline multiline text +// and a texteditor based prompt +func NewMultiline(opts Multiline) (prompt survey.Prompt) { + if opts.UseEditor { + prompt = &survey.Editor{ + Message: opts.Message, + Default: opts.Default, + FileName: "*." + opts.Syntax, + } + } else { + prompt = &survey.Multiline{Message: opts.Message, Default: opts.Default} + } return } diff --git a/modules/interact/pull_review.go b/modules/interact/pull_review.go index ea766d7..3b0affd 100644 --- a/modules/interact/pull_review.go +++ b/modules/interact/pull_review.go @@ -8,6 +8,7 @@ import ( "fmt" "os" + "code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/task" @@ -55,7 +56,11 @@ func ReviewPull(ctx *context.TeaContext, idx int64) error { if (state == gitea.ReviewStateComment && len(codeComments) == 0) || state == gitea.ReviewStateRequestChanges { promptOpts = survey.WithValidator(survey.Required) } - err = survey.AskOne(&survey.Multiline{Message: "Concluding comment:"}, &comment, promptOpts) + err = survey.AskOne(NewMultiline(Multiline{ + Message: "Concluding comment:", + Syntax: "md", + UseEditor: config.GetPreferences().Editor, + }), &comment, promptOpts) if err != nil { return err } @@ -65,6 +70,7 @@ func ReviewPull(ctx *context.TeaContext, idx int64) error { // DoDiffReview (1) fetches & saves diff in tempfile, (2) starts $VISUAL or $EDITOR to comment on diff, // (3) parses resulting file into code comments. +// It doesn't really make sense to use survey.Editor() here, as we'd read the file content at least twice. func DoDiffReview(ctx *context.TeaContext, idx int64) ([]gitea.CreatePullReviewComment, error) { tmpFile, err := task.SavePullDiff(ctx, idx) if err != nil { diff --git a/modules/print/comment.go b/modules/print/comment.go index 14f2764..3fbe5f1 100644 --- a/modules/print/comment.go +++ b/modules/print/comment.go @@ -15,7 +15,7 @@ import ( func Comments(comments []*gitea.Comment) { var baseURL string if len(comments) != 0 { - baseURL = comments[0].HTMLURL + baseURL = getRepoURL(comments[0].HTMLURL) } var out = make([]string, len(comments)) @@ -32,7 +32,7 @@ func Comments(comments []*gitea.Comment) { // Comment renders a comment to stdout func Comment(c *gitea.Comment) { - outputMarkdown(formatComment(c), c.HTMLURL) + outputMarkdown(formatComment(c), getRepoURL(c.HTMLURL)) } func formatComment(c *gitea.Comment) string { diff --git a/modules/print/formatters.go b/modules/print/formatters.go index af7a899..fc33151 100644 --- a/modules/print/formatters.go +++ b/modules/print/formatters.go @@ -6,12 +6,20 @@ package print import ( "fmt" + "regexp" "time" "code.gitea.io/sdk/gitea" "github.com/muesli/termenv" ) +// captures the repo URL part // of an url +var repoURLRegex = regexp.MustCompile("^([[:alnum:]]+://[^/]+(?:/[[:alnum:]]+){2})/.*") + +func getRepoURL(resourceURL string) string { + return repoURLRegex.ReplaceAllString(resourceURL, "$1/") +} + // formatSize get kb in int and return string func formatSize(kb int64) string { if kb < 1024 { diff --git a/modules/print/issue.go b/modules/print/issue.go index b78e7be..8f2fb5f 100644 --- a/modules/print/issue.go +++ b/modules/print/issue.go @@ -28,7 +28,7 @@ func IssueDetails(issue *gitea.Issue, reactions []*gitea.Reaction) { out += fmt.Sprintf("\n---\n\n%s\n", formatReactions(reactions)) } - outputMarkdown(out, issue.HTMLURL) + outputMarkdown(out, getRepoURL(issue.HTMLURL)) } func formatReactions(reactions []*gitea.Reaction) string { diff --git a/modules/print/pull.go b/modules/print/pull.go index 5007bde..2bc14b5 100644 --- a/modules/print/pull.go +++ b/modules/print/pull.go @@ -64,7 +64,7 @@ func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview, ciStatus *g } } - outputMarkdown(out, pr.HTMLURL) + outputMarkdown(out, getRepoURL(pr.HTMLURL)) } func formatPRHead(pr *gitea.PullRequest) string { diff --git a/modules/print/table.go b/modules/print/table.go index 7f6988b..26f49f0 100644 --- a/modules/print/table.go +++ b/modules/print/table.go @@ -140,7 +140,7 @@ func outputyaml(headers []string, values [][]string) { func isMachineReadable(outputFormat string) bool { switch outputFormat { - case "yml", "yaml", "csv": + case "yml", "yaml", "csv", "tsv": return true } return false diff --git a/modules/print/user.go b/modules/print/user.go index 21ef43b..34efd3e 100644 --- a/modules/print/user.go +++ b/modules/print/user.go @@ -78,6 +78,8 @@ var UserFields = []string{ "description", "visibility", "activated", + "lastlogin_at", + "created_at", } type printableUser struct{ *gitea.User } @@ -124,6 +126,10 @@ func (x printableUser) FormatField(field string, machineReadable bool) string { return x.Description case "visibility": return string(x.Visibility) + case "created_at": + return FormatTime(x.Created) + case "lastlogin_at": + return FormatTime(x.LastLogin) } return "" } diff --git a/modules/task/pull_create.go b/modules/task/pull_create.go index b7505b5..8ed6ec9 100644 --- a/modules/task/pull_create.go +++ b/modules/task/pull_create.go @@ -108,7 +108,7 @@ func GetDefaultPRHead(localRepo *local_git.TeaRepo) (owner, branch string, err e if err != nil { return } - owner, _ = utils.GetOwnerAndRepo(strings.TrimLeft(url.Path, "/"), "") + owner, _ = utils.GetOwnerAndRepo(url.Path, "") return } diff --git a/modules/task/repo_clone.go b/modules/task/repo_clone.go new file mode 100644 index 0000000..bc164fc --- /dev/null +++ b/modules/task/repo_clone.go @@ -0,0 +1,93 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package task + +import ( + "fmt" + "net/url" + + "code.gitea.io/sdk/gitea" + "code.gitea.io/tea/modules/config" + local_git "code.gitea.io/tea/modules/git" + + "github.com/go-git/go-git/v5" + git_config "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" +) + +// RepoClone creates a local git clone in the given path, and sets up upstream remote +// for fork repos, for good usability with tea. +func RepoClone( + path string, + login *config.Login, + repoOwner, repoName string, + callback func(string) (string, error), + depth int, +) (*local_git.TeaRepo, error) { + + repoMeta, _, err := login.Client().GetRepo(repoOwner, repoName) + if err != nil { + return nil, err + } + + originURL, err := cloneURL(repoMeta, login) + if err != nil { + return nil, err + } + + auth, err := local_git.GetAuthForURL(originURL, login.Token, login.SSHKey, callback) + if err != nil { + return nil, err + } + + // default path behaviour as native git + if path == "" { + path = repoName + } + + repo, err := git.PlainClone(path, false, &git.CloneOptions{ + URL: originURL.String(), + Auth: auth, + Depth: depth, + InsecureSkipTLS: login.Insecure, + }) + if err != nil { + return nil, err + } + + // set up upstream remote for forks + if repoMeta.Fork && repoMeta.Parent != nil { + upstreamURL, err := cloneURL(repoMeta.Parent, login) + if err != nil { + return nil, err + } + upstreamBranch := repoMeta.Parent.DefaultBranch + repo.CreateRemote(&git_config.RemoteConfig{ + Name: "upstream", + URLs: []string{upstreamURL.String()}, + }) + repoConf, err := repo.Config() + if err != nil { + return nil, err + } + if b, ok := repoConf.Branches[upstreamBranch]; ok { + b.Remote = "upstream" + b.Merge = plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", upstreamBranch)) + } + if err = repo.SetConfig(repoConf); err != nil { + return nil, err + } + } + + return &local_git.TeaRepo{Repository: repo}, nil +} + +func cloneURL(repo *gitea.Repository, login *config.Login) (*url.URL, error) { + urlStr := repo.CloneURL + if login.SSHKey != "" { + urlStr = repo.SSHURL + } + return local_git.ParseURL(urlStr) +} diff --git a/modules/utils/parse.go b/modules/utils/parse.go index 41e88ae..63fd19e 100644 --- a/modules/utils/parse.go +++ b/modules/utils/parse.go @@ -33,7 +33,7 @@ func GetOwnerAndRepo(repoPath, user string) (string, string) { if len(repoPath) == 0 { return "", "" } - p := strings.Split(repoPath, "/") + p := strings.Split(strings.TrimLeft(repoPath, "/"), "/") if len(p) >= 2 { return p[0], p[1] } diff --git a/vendor/github.com/charmbracelet/glamour/README.md b/vendor/github.com/charmbracelet/glamour/README.md index 648ab9d..c7c3cec 100644 --- a/vendor/github.com/charmbracelet/glamour/README.md +++ b/vendor/github.com/charmbracelet/glamour/README.md @@ -6,10 +6,10 @@ GoDoc Build Status Coverage Status - Go ReportCard + Go ReportCard

-Stylesheet-based markdown rendering for your CLI apps. +Write handsome command-line tools with *Glamour*. ![Glamour dark style example](https://stuff.charm.sh/glamour/glamour-example.png) diff --git a/vendor/github.com/charmbracelet/glamour/ansi/elements.go b/vendor/github.com/charmbracelet/glamour/ansi/elements.go index cd253cf..c09ed7f 100644 --- a/vendor/github.com/charmbracelet/glamour/ansi/elements.go +++ b/vendor/github.com/charmbracelet/glamour/ansi/elements.go @@ -129,9 +129,6 @@ func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element { } if node.Parent().(*ast.List).IsOrdered() { e = l - if node.Parent().(*ast.List).Start != 1 { - e += uint(node.Parent().(*ast.List).Start) - 1 - } } post := "\n" @@ -156,7 +153,6 @@ func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element { return Element{ Exiting: post, Renderer: &ItemElement{ - IsOrdered: node.Parent().(*ast.List).IsOrdered(), Enumeration: e, }, } diff --git a/vendor/github.com/charmbracelet/glamour/ansi/image.go b/vendor/github.com/charmbracelet/glamour/ansi/image.go index 9f31c4d..f5edfaf 100644 --- a/vendor/github.com/charmbracelet/glamour/ansi/image.go +++ b/vendor/github.com/charmbracelet/glamour/ansi/image.go @@ -25,7 +25,7 @@ func (e *ImageElement) Render(w io.Writer, ctx RenderContext) error { } if len(e.URL) > 0 { el := &BaseElement{ - Token: resolveURL(e.BaseURL, e.URL), + Token: resolveRelativeURL(e.BaseURL, e.URL), Prefix: " ", Style: ctx.options.Styles.Image, } diff --git a/vendor/github.com/charmbracelet/glamour/ansi/link.go b/vendor/github.com/charmbracelet/glamour/ansi/link.go index 3f0dbba..4cb5931 100644 --- a/vendor/github.com/charmbracelet/glamour/ansi/link.go +++ b/vendor/github.com/charmbracelet/glamour/ansi/link.go @@ -64,7 +64,7 @@ func (e *LinkElement) Render(w io.Writer, ctx RenderContext) error { } el := &BaseElement{ - Token: resolveURL(e.BaseURL, e.URL), + Token: resolveRelativeURL(e.BaseURL, e.URL), Prefix: pre, Style: style, } diff --git a/vendor/github.com/charmbracelet/glamour/ansi/listitem.go b/vendor/github.com/charmbracelet/glamour/ansi/listitem.go index 4e47af8..a64b10d 100644 --- a/vendor/github.com/charmbracelet/glamour/ansi/listitem.go +++ b/vendor/github.com/charmbracelet/glamour/ansi/listitem.go @@ -7,13 +7,12 @@ import ( // An ItemElement is used to render items inside a list. type ItemElement struct { - IsOrdered bool Enumeration uint } func (e *ItemElement) Render(w io.Writer, ctx RenderContext) error { var el *BaseElement - if e.IsOrdered { + if e.Enumeration > 0 { el = &BaseElement{ Style: ctx.options.Styles.Enumeration, Prefix: strconv.FormatInt(int64(e.Enumeration), 10), diff --git a/vendor/github.com/charmbracelet/glamour/ansi/paragraph.go b/vendor/github.com/charmbracelet/glamour/ansi/paragraph.go index 0d3f99a..71e0725 100644 --- a/vendor/github.com/charmbracelet/glamour/ansi/paragraph.go +++ b/vendor/github.com/charmbracelet/glamour/ansi/paragraph.go @@ -38,7 +38,7 @@ func (e *ParagraphElement) Finish(w io.Writer, ctx RenderContext) error { mw := NewMarginWriter(ctx, w, rules) if len(strings.TrimSpace(bs.Current().Block.String())) > 0 { flow := wordwrap.NewWriter(int(bs.Width(ctx))) - flow.KeepNewlines = ctx.options.PreserveNewLines + flow.KeepNewlines = false _, _ = flow.Write(bs.Current().Block.Bytes()) flow.Close() diff --git a/vendor/github.com/charmbracelet/glamour/ansi/renderer.go b/vendor/github.com/charmbracelet/glamour/ansi/renderer.go index 8275694..ddadbb7 100644 --- a/vendor/github.com/charmbracelet/glamour/ansi/renderer.go +++ b/vendor/github.com/charmbracelet/glamour/ansi/renderer.go @@ -3,6 +3,7 @@ package ansi import ( "io" "net/url" + "strings" "github.com/muesli/termenv" east "github.com/yuin/goldmark-emoji/ast" @@ -14,11 +15,10 @@ import ( // Options is used to configure an ANSIRenderer. type Options struct { - BaseURL string - WordWrap int - PreserveNewLines bool - ColorProfile termenv.Profile - Styles StyleConfig + BaseURL string + WordWrap int + ColorProfile termenv.Profile + Styles StyleConfig } // ANSIRenderer renders markdown content as ANSI escaped sequences. @@ -149,7 +149,7 @@ func isChild(node ast.Node) bool { return false } -func resolveURL(baseURL string, rel string) string { +func resolveRelativeURL(baseURL string, rel string) string { u, err := url.Parse(rel) if err != nil { return rel @@ -157,6 +157,7 @@ func resolveURL(baseURL string, rel string) string { if u.IsAbs() { return rel } + u.Path = strings.TrimPrefix(u.Path, "/") base, err := url.Parse(baseURL) if err != nil { diff --git a/vendor/github.com/charmbracelet/glamour/glamour.go b/vendor/github.com/charmbracelet/glamour/glamour.go index 048d45a..7b4a14c 100644 --- a/vendor/github.com/charmbracelet/glamour/glamour.go +++ b/vendor/github.com/charmbracelet/glamour/glamour.go @@ -185,14 +185,6 @@ func WithWordWrap(wordWrap int) TermRendererOption { } } -// WithWordWrap sets a TermRenderer's word wrap. -func WithPreservedNewLines() TermRendererOption { - return func(tr *TermRenderer) error { - tr.ansiOptions.PreserveNewLines = true - return nil - } -} - // WithEmoji sets a TermRenderer's emoji rendering. func WithEmoji() TermRendererOption { return func(tr *TermRenderer) error { diff --git a/vendor/modules.txt b/vendor/modules.txt index 325dd7a..2a4d913 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -74,7 +74,7 @@ github.com/araddon/dateparse # github.com/aymerick/douceur v0.2.0 github.com/aymerick/douceur/css github.com/aymerick/douceur/parser -# github.com/charmbracelet/glamour v0.3.0 => github.com/noerw/glamour v0.3.0-patch +# github.com/charmbracelet/glamour v0.3.0 github.com/charmbracelet/glamour github.com/charmbracelet/glamour/ansi # github.com/cpuguy83/go-md2man/v2 v2.0.1 -- 2.40.1 From 93c0d3cbc22d5f88435ced28a94d54208e930482 Mon Sep 17 00:00:00 2001 From: Norwin Date: Tue, 13 Sep 2022 21:24:05 +0200 Subject: [PATCH 5/6] fix merge error --- modules/print/user.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/print/user.go b/modules/print/user.go index 34efd3e..52380d3 100644 --- a/modules/print/user.go +++ b/modules/print/user.go @@ -127,9 +127,9 @@ func (x printableUser) FormatField(field string, machineReadable bool) string { case "visibility": return string(x.Visibility) case "created_at": - return FormatTime(x.Created) + return FormatTime(x.Created, machineReadable) case "lastlogin_at": - return FormatTime(x.LastLogin) + return FormatTime(x.LastLogin, machineReadable) } return "" } -- 2.40.1 From d1efad5660d8422036ee5fe1b000f3d7d509f4e2 Mon Sep 17 00:00:00 2001 From: Norwin Date: Tue, 13 Sep 2022 21:27:31 +0200 Subject: [PATCH 6/6] improve cmd categories --- cmd/admin.go | 2 +- cmd/categories.go | 1 + cmd/whoami.go | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/admin.go b/cmd/admin.go index 6dacebf..b20ead3 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -17,7 +17,7 @@ var CmdAdmin = cli.Command{ Name: "admin", Usage: "Operations requiring admin access on the Gitea instance", Aliases: []string{"a"}, - Category: catHelpers, + Category: catMisc, Action: func(cmd *cli.Context) error { return cli.ShowSubcommandHelp(cmd) }, diff --git a/cmd/categories.go b/cmd/categories.go index 4b2088b..f3bb3d5 100644 --- a/cmd/categories.go +++ b/cmd/categories.go @@ -8,4 +8,5 @@ var ( catSetup = "SETUP" catEntities = "ENTITIES" catHelpers = "HELPERS" + catMisc = "MISCELLANEOUS" ) diff --git a/cmd/whoami.go b/cmd/whoami.go index 2b56828..81d7713 100644 --- a/cmd/whoami.go +++ b/cmd/whoami.go @@ -14,7 +14,7 @@ import ( // CmdWhoami represents the command to show current logged in user var CmdWhoami = cli.Command{ Name: "whoami", - Category: catSetup, + Category: catMisc, Description: `For debugging purposes, show the user that is currently logged in.`, Usage: "Show current logged in user", ArgsUsage: " ", // command does not accept arguments -- 2.40.1