74 Commits

Author SHA1 Message Date
6c9b2f8745 move s3 endpoint to secrets 2022-09-13 22:36:45 +02:00
1a256291dc Changelog 0.9.0 (#503)
Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/503
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
2022-09-14 03:52:46 +08:00
832136b6d4 Add user list command (#427)
Co-authored-by: Matti R <matti@mdranta.net>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/427
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-committed-by: techknowlogick <techknowlogick@gitea.io>
2022-09-14 03:49:34 +08:00
99e49991bb Add --fields to notification & milestone listings (#422)
Together with #415 this finally adds the field flag to all entity listings.
closes #342

### ⚠️ breaking changes ⚠️
This changes the column names of `tea milestones ls`:

```diff
 - TITLE  | OPEN/CLOSED ISSUES | DUEDATE
 + TITLE  | ITEMS | DUEDATE
```

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/422
Reviewed-by: delvh <dev.lh@web.de>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-09-14 03:08:18 +08:00
bbb287e29e markdown: dont emit ansi sequences when not emitting to tty (#491)
Allows generating a plain text version of an issue (i.e. without colors and other terminal formatting) when storing stdout in a file.
```
tea issue --comments 1 > issue1.txt
```

`IsInteractive()` had to be moved to avoid a recursive import chain.

---

In the future, it would be nice to also respect the `--output` flag. This flag is currently designed for tabular output, but we could add more supported values like `markdown` `ansi`, `plain` to it

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/491
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: delvh <dev.lh@web.de>
2022-09-14 02:35:15 +08:00
5e7c702e07 Clarify command descriptions when no arguments are taken (#496)
This changes the command help string from eg
```
NAME:
   tea label create - Create a label

USAGE:
   tea label [command options] [arguments...]
```
to
```
NAME:
   tea label create - Create a label

USAGE:
   tea label [command options]
```

Hopefully improving usability.

---

edit: this also changes `tea release create` to take the `--tag` flag value optionally via the first argument, as this seems to be a clear UX improvement.

fixes #483

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/496
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: delvh <delvh@noreply.gitea.io>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-09-14 02:14:02 +08:00
b8dbf899d2 Update dependencies (#501)
- The go-sdk update fixes #463

- To review the other updates:
  - glamour [changelog](https://github.com/charmbracelet/glamour/releases), [diff](https://github.com/charmbracelet/glamour/compare/v0.3.0...v0.5.0)
    - enhancement: we now can use `WithPreservedNewLines()` to render markdow the same way as the web ui
  - termenv [changelog](https://github.com/muesli/termenv/releases), [diff](https://github.com/muesli/termenv/compare/v0.9.0...v0.12.0)
    - enhancement: correct feature detection for more terminals
  - xdg [changelog](https://github.com/adrg/xdg/releases), [diff](https://github.com/adrg/xdg/compare/v0.3.3...v0.4.0)
    - no notable changes for us, but good to stay up to date 🤷
  - survey [changelog](https://github.com/AlecAivazis/survey/releases), [diff](https://github.com/AlecAivazis/survey/compare/v2.3.1...v2.3.6)
    - bugfixes
  - cli [changelog](https://github.com/urfave/cli/releases), [diff](https://github.com/urfave/cli/compare/v2.3.0...v2.16.3)
    - bugfixes?

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/501
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-09-14 01:52:44 +08:00
0b8be54186 Rename master branch to main (#495)
This updates drone CI to use the new main branch name `main`.

### ⚠️ breaking

The download URLs on https://dl.gitea.io/tea/master will no longer be updated.

@Owners: is there a way to add a redirect for these URLs from `/tea/master` to `/tea/main`?

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/495
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-08-26 23:34:40 +08:00
2b1bca9e5d Add license for gitea.com/noerw/unidiff-comments (#493)
The updated version just clarifies the license to be MIT, as [added by the original author](865648740d).
fixes #492

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/493
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-08-22 21:14:09 +08:00
d5a258213d Fix CI: disable package-comments lint rule (#494)
There was an unintended change to the rule in revive that now makes our CI trip:
https://github.com/mgechev/revive/pull/694
The package-comments now expected are not really worth writing as these are tiny internal packages, so this change just disables that rule for now.

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/494
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-08-22 20:53:19 +08:00
f83f579dea Show more version info (#486)
Reviewed-on: https://gitea.com/gitea/tea/pulls/486
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Wim <42wim@noreply.gitea.io>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2022-06-18 22:34:18 +08:00
65535bd948 Use latest go-sdk and bump golang to 1.18 (#485)
Reviewed-on: https://gitea.com/gitea/tea/pulls/485
Reviewed-by: KN4CK3R <kn4ck3r@noreply.gitea.io>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2022-06-10 22:31:24 +08:00
02f5f15269 Fix go install for go 1.17 (#481)
Reviewed-on: https://gitea.com/gitea/tea/pulls/481
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Sandro <supersandro2000@noreply.gitea.io>
Co-committed-by: Sandro <supersandro2000@noreply.gitea.io>
2022-04-26 14:07:15 +08:00
883a27b14e Fetch all items where needed. (#475)
Disable pagination in all places where we need all items.
Ideally we'd do multiple paginated requests until the needed items are local, but this is blocked by https://gitea.com/gitea/go-sdk/pulls/473. So this is a stopgap to get correct behaviour.

Fixes #464

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/475
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-04-08 03:44:16 +08:00
e54b32493d fix pipeline to release builds (#479)
fix regression of #478

Reviewed-on: https://gitea.com/gitea/tea/pulls/479
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: John Olheiser <john.olheiser@gmail.com>
2022-03-29 08:29:47 +08:00
329200b1ef Fix running in repos without remote (#472)
For tea, the case of no remotes in the local repo context is equal to `errNotAGiteaRepo`.
This error type is already gracefully handled, so with this change, tea doesn't reject working from a repo without remotes.

fixes #455, closes #465

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/472
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-03-29 08:01:37 +08:00
6663d9f19b Add preference flag_defaults.remote, refactor (#466)
This is a refactor of the code last touched in #458, making the control flow less backwards.

Additionally, this adds a preference `preferences.flag_defaults.remote` that allows to skip this heuristic and set a custom fixed default value for the `--remote` flag.
I'm not sure this is actually needed, I can revert that commit.

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Andrew Thornton <art27@cantab.net>
Reviewed-on: https://gitea.com/gitea/tea/pulls/466
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-03-29 07:34:14 +08:00
d06f35482e Use golang v1.18 and drop vendor folder (#478)
* remove vendor folder
* use golang v1.18 in ci
* use "go install"
* use vendor folder as cache

Reviewed-on: https://gitea.com/gitea/tea/pulls/478
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: John Olheiser <john.olheiser@gmail.com>
2022-03-29 07:11:11 +08:00
9ab36c55fa Return RFC3339 UTC timestamps for machine-readable output (#470)
### ⚠️ breaking changes ⚠️

- unset timestamps will not be printed as `"0001-01-01 00:00"`, but as empty value `""`
- output formats `csv`, `tsv`, `yaml` output timestamps in UTC instead of local time, and adhere to [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339)

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/470
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: John Olheiser <john.olheiser@gmail.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-03-29 06:37:13 +08:00
40e606561f GOPROXY: https://goproxy.io 2022-03-28 23:59:48 +02:00
0970b94552 Fix context requirements of subcommands (#474)
`tea repo fork` and `tea pr checkout` were missing the requirement of a remote repo.

fixes https://gitea.com/gitea/tea/issues/444

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/474
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-03-26 15:32:53 +08:00
dda94a5dea Refactor errorhandling in getReleaseByTag() (#477)
small refactor for consistency

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/477
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-03-25 21:41:08 +08:00
16ba594a28 Interactive issue/pr posting: properly fetch assignees (#476)
Gitea 1.15.0 added a proper API for listing assignee candidates.
imho that release is old enough that tea can start using this without workarounds.

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/476
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-03-19 19:08:58 +08:00
d8f4273ed0 Add TSV to machine-readable formats (#467)
...so no ansi formatting (colors) is emitted in that format (eg `tea labels -o tsv`)

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/467
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-03-13 12:00:21 +08:00
637e3f0666 Fix CI: run make fmt (#469)
Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/469
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-03-13 08:53:18 +08:00
ced24ccabb update to new s3 location (#468)
Reviewed-on: https://gitea.com/gitea/tea/pulls/468
2022-03-11 05:01:29 +08:00
fb3e1f75e9 Prefer origin if there are multiple remotes (#458)
Usually, `origin` is the name of a default remote, which corresponds with upstream. This change improves remote selection when non of `main`, `master` nor `trunk` local branches is present.

Co-authored-by: Petr Vaněk <arkamar@atlas.cz>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/458
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: arkamar <arkamar@noreply.gitea.io>
Co-committed-by: arkamar <arkamar@noreply.gitea.io>
2022-03-09 09:35:51 +08:00
0e24009fe9 Fix create milestone with deadline bug (#462)
Fix #461

Reviewed-on: https://gitea.com/gitea/tea/pulls/462
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-03-09 08:47:58 +08:00
cd24fd8e28 Fix few typos in contribution guidelines (#459)
I found few typos in `CONTRIBUTING.md` file.

Co-authored-by: Petr Vaněk <arkamar@atlas.cz>
Reviewed-on: https://gitea.com/gitea/tea/pulls/459
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: arkamar <arkamar@noreply.gitea.io>
Co-committed-by: arkamar <arkamar@noreply.gitea.io>
2022-02-22 22:34:58 +08:00
dd300c1269 Fix typo in bug.md (#460)
occured -> occurred

Reviewed-on: https://gitea.com/gitea/tea/pulls/460
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Ikko Ashimine <eltociear@noreply.gitea.io>
Co-committed-by: Ikko Ashimine <eltociear@noreply.gitea.io>
2022-02-20 00:30:58 +08:00
44c9e7e664 Correct spelling of "wether" to "whether" in usage output (#453)
Noticed "whether" misspelled as "wether" in the output of `tea issues --help` and corrected it in a few locations.

Co-authored-by: Alex Kelly <kellya@arachnitech.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/453
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: kellya <kellya@noreply.gitea.io>
Co-committed-by: kellya <kellya@noreply.gitea.io>
2022-02-01 07:11:37 +08:00
268aa06179 Add bug report issue template (#448)
As we repeatedly ask for information about users' environments, I added this template

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/448
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-12-04 11:20:36 +08:00
a7d83ee416 Simplify build & update installation instructions (#437)
- "revert" the work done in #349. It turns out that this elaborate workaround to get statically built PIEs was only needed due to a bug in go, which got fixed in go 1.16.
- Add an exception to the `-buildmode=pie` flag for OpenBSD, as discovered in #436
- Simplify & update README installation instructions (the Arch AUR package got deleted as it wasn't maintained, so we link to `gitea-tea-git` now.)

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/437
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-12-03 04:01:10 +08:00
a89f51f9ec Implement more issue filters (#400)
This adds new filters to `tea issues ls` and `tea pr ls`, made available in SDK 0.15:

```
--state value                 Filter by state (all|open|closed) (default: open)
--keyword value, -k value     Filter by search string
--labels value, -L value      Comma-separated list of labels to match issues against.
--milestones value, -m value  Comma-separated list of milestones to match issues against.
--author value, -A value
--assignee value, -a value
--mentions value, -M value
--from value, -F value        Filter by activity after this date
--until value, -u value       Filter by activity before this date
```

Note: I felt free to change parameter names as exposed by SDK & API, as the names exposed by them are partially bollocks (eg `mentioned_by`) and or inconsistent with usage in other commands (eg `tea times --until`)

fixes #376, related #323

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/400
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-12-03 03:26:48 +08:00
d2295828d0 Fix resolving of URLs in markdown (#401)
Path-only URLs need an absolute reference to be resolved against for printing in markdown
Previously we resolved against the URL to the resource we were operating on (eg comment or issue URL).
The markdown renderer in the web UI resolves all such URLs relative to the repo base URL. This PR adopts this behaviour in tea, by trimming the URL to a repo base URL via regex.

This makes a custom patch to our markdown renderer `glamour` obsolete, which turned out to be an incorrect patch, meaning we can make use of upstream glamour again.

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/401
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-12-03 02:59:02 +08:00
dc16643e0d Improve Documentation (#433)
- document more assumptions about usage context of gitea
- improve some flag descriptions (#432, #377)

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/433
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-12-03 02:33:56 +08:00
ac25e89ebf Add tea repo create-from-template (#408)
I went with a new subcommand instead of `tea repo create --template`, as the options are quite different (bool instead of values, partially different set)

fixes #362

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/408
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-10-20 09:31:35 +08:00
819cc1ab21 Add tea clone (#411)
Adds a new subcommand to clone repos:
```
tea clone --login try --depth 1 norwin/test
tea clone gitea/tea
tea clone noerw/tea           # will set up `master` to track `upstream` remote
tea clone try.gitea.io/noerw/test # will automatically set --login
```

This is just a replacement for `git clone` with small benefits:
- [x] does not depend on `git`, as tea ships with go-git
- [x] spares you typing of URLs and autoselects https/ssh based on your login config
- [x] forked repos: set up origin + upstream remote

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-on: https://gitea.com/gitea/tea/pulls/411
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-10-18 20:09:27 +08:00
78a95f1ca4 Allow editing multiline prompts with external text editor (#429)
- Adds a new `Preferences` struct to the config, initially only containing `Editor: bool (default false)`.
  This struct will be serialized to configs once there is a first tea induced change to the config (eg `tea login default <name>` or `tea login add`).
- Use external editor for all multiline prompts if preferred.

We already had a function for starting a texteditor for diff reviews; it does not really make sense to replace it with `survey.Editor`, as there is a big interface mismatch: survey expects strings as inputs, while our diff functions operate on files,

fixes #424

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/429
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-10-14 22:36:08 +08:00
5b77345b03 Add tea repo fork (#410)
Adds a new subcommand to fork repos.

To specify the repo to fork, use the `--repo` flag. This feels a bit weird, other tea commands would put this as the first argument.
I decided to follow the flag style, as this is what all other subcommands of `tea repo` do. We might want to reconsider and make the primary subject of such commands an argument, instead of an required flag.. see #430

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-on: https://gitea.com/gitea/tea/pulls/410
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-10-14 15:27:39 +08:00
23ce7b351d fix CI release upload: switch to woodpecker/plugin-s3 (#428)
- this fixes the CI release upload issues, as the docker image for this is freshly built (unlike the mostly unmaintained "official" drone plugins), thus containing current CA certs needed for letsencrypt since 2021-09-31.
- woodpecker is a drone-ci fork maintained partially by @6543. it's API compatible with current drone plugins afaik

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/428
Reviewed-by: Alexey 〒erentyev <axifive@noreply.gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-10-10 20:48:51 +08:00
375ece06d2 fix lint regression (#425)
On PR #421 CI did not work and it was force merged. Thus we managed to have failing CI on master.. :( sorry

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/425
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-10-05 01:43:06 +08:00
4ffd994549 Add tea whoami command (#426)
The User print will be used in future for list of users for admin

Co-authored-by: Matti R <matti@mdranta.net>
Reviewed-on: https://gitea.com/gitea/tea/pulls/426
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: Alexey 〒erentyev <axifive@noreply.gitea.io>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-committed-by: techknowlogick <techknowlogick@gitea.io>
2021-10-05 01:41:59 +08:00
58aaa17e7e Show issue reactions (#421)
```
$ tea issue 230

   #230 issue/pull details: show reactions (open)

  @6543 created 2020-10-22 16:39

  since reactions are utf8 now and most terminals too, we can display them nicely :)

  https://gitea.com/api/v1/repos/gitea/tea/issues/230/reactions

  --------

  1x 🎉  |  1x 👀  |  1x :gitea:  |  1x 👍  |  1x 👎  |  1x 😆  |  1x 😕  |  1x ❤️
```

caveats:
- reactions are not returned as UTF8 (as was claimed in #230), so they need to be parsed. the library I use doesn't (and can't → :gitea:) support all reactions available in gitea
- currently only for issues, as reactions for comments mean an additional API request for each comment..

fixes #230

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/421
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-10-01 16:13:32 +08:00
7a05be436c Add more flags to tea repo create (#409)
adds the `--template` and `--trustmodel` flags

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/409
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-09-29 04:42:51 +08:00
3cf084cb96 PR listing: add --fields & expose additional fields (#415)
This PR adds the `--fields` flag to `tea pr ls` (#342), and exposes more fields specific to the `PullRequest` type:
```
   --fields value, -f value   Comma-separated list of fields to print.
                              Available values:
                              index,state,author,author-id,url,title,body,mergeable,base,base-commit,head,diff,patch,created,updated,deadline,assignees,milestone,labels,comments
                              (default: "index,title,state,author,milestone,updated,labels")

```

Co-authored-by: justusbunsi <61625851+justusbunsi@users.noreply.github.com>
Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/415
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: justusbunsi <justusbunsi@noreply.gitea.io>
Co-committed-by: justusbunsi <justusbunsi@noreply.gitea.io>
2021-09-29 04:36:33 +08:00
1e59dee685 Add tea org create <name> (#420)
fixes #287, fixes #363

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/420
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-09-29 04:32:16 +08:00
42e423470c Fix notification example (#416)
Co-authored-by: justusbunsi <61625851+justusbunsi@users.noreply.github.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/416
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: justusbunsi <justusbunsi@noreply.gitea.io>
Co-committed-by: justusbunsi <justusbunsi@noreply.gitea.io>
2021-09-29 03:29:48 +08:00
555f1ae516 Makefile: add STATIC=true for static PIE builds (#349)
- `make build` + `make install` now support the `STATIC=true` parameter, creating statically linked builds that are also position independent executables
- this requires CGO and a static libc on the build system
- `CGO_ENABLED=0` is set for all make build targets, unless `STATIC=true` is set
- Debug symbols are stripped (`-s -w`) for all make build targets
- Release binaries are built statically by gox (no PIE), as before.

I also took the liberty to declutter the makefile from unused & duplicated variables.

Co-authored-by: Norwin Roosen <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/349
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-09-24 00:01:07 +08:00
1c690c5ff8 Changelog v0.8.0 (#404)
Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/404
Reviewed-by: Alexey 〒erentyev <axifive@noreply.gitea.io>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2021-09-23 03:53:33 +08:00
802bdf7dc5 Don't require a body for comment PR reviews (#399)
fixes #372

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/399
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-09-23 02:18:25 +08:00
1731e00ebd Don't skip reading the local repo when --repo specifies a repo slug (#398)
I added this check in #327, but it wasn't needed at all
as the error case it intended to catch where already handled by checking if the path exists.

fixes #378

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/398
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-09-23 00:12:56 +08:00
7b7c7f57be tea pr create: make local repo optional (#393)
this is a partial fix to #378, making the command available outside of a local repo.

new behaviour:
- when run interactively without local repo context, the head repo prompt is not pre-populated
- when run with flags without local repo context, it will complain unless `--head` is specified

refactor:
- pass TeaContext down to task.CreatePull

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/393
Reviewed-by: Alexey 〒erentyev <axifive@noreply.gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-09-22 23:48:21 +08:00
6e728cf812 Accept more main branch names for login detection (#396)
Also consider `main` and `trunk` as options
to determine a login through its configured remote

fixes #381

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/396
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Alexey 〒erentyev <axifive@noreply.gitea.io>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-09-14 14:59:11 +08:00
808e8b1c5a Correctly match login by ssh host with port (#391)
fixes #380

note: It seems like it was expected that `SSHHost` only contains the host portion.  So this may be breaking (although I don't believe many people used the feature like that with a custom ssh port). I can't think of a good reason to *not* specify the port in that field, including the port seems more intuitive

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/391
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-09-06 18:52:34 +08:00
9201250f74 fix printing issue deadline (#388)
fixes #387

Reviewed-on: https://gitea.com/gitea/tea/pulls/388
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-09-06 18:45:24 +08:00
5b28a05eb7 Implement notification subcommands (#389)
- [x] enhance notification listing
  - add `--states` and `--type` filters
  - toggle per-user or per-repo listing via `--mine` flag
  - print more fields
- [x] add subcommands to mark notifications as read, unread, pinned, unpinned. operates on
  - all notifications matching the `--state` and `--mine` filter flags, or
  - a notification specified by ID.
- [ ] ~~add a `--fields` flag for notifications listing.~~ *not in this PR*
- [ ] ~~interactive mode~~ *not in this PR*. this would go well together with #324

fixes #243, fixes #155

based on initial work in #283 and #386, but opening a new PR for @6543 to review as I changed quite a lot

---

### ⚠️ breaking ⚠️
- `tea notifications --all` has moved to `tea notifications --mine`
- `tea notifications` now only works with the context of a remote repo.
  To run this outside of a local git dir, run either `tea n --mine` or `tea n --repo <my/repo>`

---

Co-authored-by: Karl Heinz Marbaise <kama@soebes.de>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin Roosen <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/389
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Alexey 〒erentyev <axifive@noreply.gitea.io>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-09-06 01:11:17 +08:00
3fca309f2c Fix adding login without token on private instances (#392)
fixes #365

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/392
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-08-30 23:19:45 +08:00
d6df0a53b5 Update Dependencies (#390)
Co-authored-by: Norwin Roosen <git@nroo.de>
Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/390
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-08-30 23:18:50 +08:00
4b9907fb54 Notifications Add State Field (#384)
Reviewed-on: https://gitea.com/gitea/tea/pulls/384
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2021-08-16 21:23:16 +08:00
ab4e11ae4d Update gitea go-sdk to v0.15.0 (#385)
Update "code.gitea.io/sdk/gitea" to latest release

Reviewed-on: https://gitea.com/gitea/tea/pulls/385
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2021-08-16 20:46:15 +08:00
546fcc16de Handle XDG directories with spaces in autocomplete commands (#383)
On some OSs ([i.e. macOS](https://github.com/adrg/xdg#xdg-base-directory)), XDG directories contain spaces, so we need to wrap the resulting path used in autocomplete sourcing commands in quotation marks.

Co-authored-by: Matthew Daley <mattd@bugfuzz.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/383
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Co-authored-by: nukuta <nukuta@noreply.gitea.io>
Co-committed-by: nukuta <nukuta@noreply.gitea.io>
2021-07-24 00:14:23 +08:00
0f4f669cf0 Fix parsing of --description for issue/pr create (#371)
The `--description` flag didn't work; regression of #331

Co-authored-by: Norwin Roosen <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/371
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: KN4CK3R <kn4ck3r@noreply.gitea.io>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-07-03 23:23:38 +08:00
2bdd72dfff Improve error messages (#370)
fixes #367

Co-authored-by: Norwin Roosen <git@nroo.de>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/370
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: KN4CK3R <kn4ck3r@noreply.gitea.io>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-07-03 22:39:05 +08:00
64770a771f rename s3 bucket name (#375)
Reviewed-on: https://gitea.com/gitea/tea/pulls/375
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-committed-by: techknowlogick <techknowlogick@gitea.io>
2021-06-30 04:36:05 +08:00
ebb2c38a0a Return useful error on wrong sshkey path (#374)
close #366

Reviewed-on: https://gitea.com/gitea/tea/pulls/374
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2021-06-29 15:54:43 +08:00
616127cedc Add missing flags (#369)
fixes #368

Co-authored-by: Norwin Roosen <git@nroo.de>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/369
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-06-22 22:23:09 +08:00
3129e60a73 text editor selection: follow unix defacto standards (#356)
Currently, `tea` only supports the $EDITOR env var to open the user's preferred editor (used for reviewing pull requests).

Standard \*nix practice is, however, to check for $VISUAL first and only then use $EDITOR as fallback.
This is also done by Git itself, see man git-var(1).
(Actually, the order there is $GIT_EDITOR > core.editor > $VISUAL > $EDITOR > vi)

Co-authored-by: plgruener <pl.gruener@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/356
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: plgruener <plgruener@noreply.gitea.io>
Co-committed-by: plgruener <plgruener@noreply.gitea.io>
2021-06-21 20:08:27 +08:00
df724b4006 Add tab completion for fish shell (#364)
as title, fixes #361

Handling of fish shell is different in urfave/cli; urfave/cli provides a generator for the shell script needed (probably because the fish `completion` syntax isn't flexible enough to let the application handle the completion at runtime? idk)

This means that the fish completion can become out of sync with the tea binary.
If we want to account for that, on each application run we need to
- check if `~/.config/fish/conf.d/tea_completion.fish` exists; if so
- check if the tea version that wrote it is the currently running version
- if not, rewrite the file.

Not sure this is worth the complexity & cost

It generates a completion that also suggests file names, which looks kinda messy: Didn't find a way around this, but [there may be a way](5bb54ace57/fish.go (L160-L180))
![grafik](/attachments/b08541c9-0f37-4c70-a2e3-1ec9da15a430)

Co-authored-by: Norwin Roosen <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/364
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-05-24 04:42:21 +08:00
ffdbdb3d02 Check negative limit command parameter (#358) (#359)
fix #358

Co-authored-by: Brahim Hamdouni <brahim@hamdouni.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/359
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Co-authored-by: Brahim HAMDOUNI <hamdouni@noreply.gitea.io>
Co-committed-by: Brahim HAMDOUNI <hamdouni@noreply.gitea.io>
2021-05-15 22:16:24 +08:00
568fde1ce5 Add missing flags to org & labels subcommands (#357)
The `tea orgs` command is an alias to `tea orgs list`, and as such should have the same flags.

fixes #354

Co-authored-by: Norwin Roosen <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/357
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-05-13 02:32:20 +08:00
46945415c9 Enable release builds for darwin/arm64 (#360)
Co-authored-by: Norwin Roosen <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/360
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-05-11 08:03:39 +08:00
195bd2199c contributed container build definition (#350)
as discussed with @noerw on Discord

Co-authored-by: Tamás Gérczei <tamas@gerczei.eu>
Reviewed-on: https://gitea.com/gitea/tea/pulls/350
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: Tamás Gérczei <tgerczei@noreply.gitea.io>
Co-committed-by: Tamás Gérczei <tgerczei@noreply.gitea.io>
2021-03-30 19:13:23 +08:00
0bf844018c Add tea pr merge (#348)
fixes #343

Co-authored-by: Norwin Roosen <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/348
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-03-18 03:56:05 +08:00
1732 changed files with 2741 additions and 429303 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
Dockerfile
tea

View File

@ -7,18 +7,25 @@ platform:
arch: amd64 arch: amd64
steps: steps:
- name: vendor
pull: always
image: golang:1.18
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
commands:
- make vendor # use vendor folder as cache
- name: build - name: build
pull: always pull: always
image: golang:1.16 image: golang:1.18
environment: environment:
GOPROXY: https://goproxy.cn GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
commands: commands:
- make clean - make clean
- make vet - make vet
- make lint - make lint
- make fmt-check - make fmt-check
- make misspell-check - make misspell-check
- make test-vendor
- make build - make build
when: when:
event: event:
@ -27,24 +34,28 @@ steps:
- pull_request - pull_request
- name: unit-test - name: unit-test
image: golang:1.16 image: golang:1.18
commands: commands:
- make unit-test-coverage - make unit-test-coverage
settings: settings:
group: test group: test
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
when: when:
branch: branch:
- master - main
event: event:
- push - push
- pull_request - pull_request
- name: release-test - name: release-test
image: golang:1.16 image: golang:1.18
commands: commands:
- make test - make test
settings: settings:
group: test group: test
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
when: when:
branch: branch:
- "release/*" - "release/*"
@ -54,21 +65,22 @@ steps:
- name: tag-test - name: tag-test
pull: always pull: always
image: golang:1.16 image: golang:1.18
commands: commands:
- make test - make test
settings: settings:
group: test group: test
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
when: when:
event: event:
- tag - tag
- name: static - name: static
image: golang:1.16 image: golang:1.18
environment: environment:
GOPROXY: https://goproxy.cn GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
commands: commands:
- export PATH=$PATH:$GOPATH/bin
- make release - make release
when: when:
event: event:
@ -96,11 +108,12 @@ steps:
- name: tag-release - name: tag-release
pull: always pull: always
image: plugins/s3:1 image: woodpeckerci/plugin-s3:latest
settings: settings:
acl: public-read acl: public-read
bucket: gitea-artifacts bucket: gitea-artifacts
endpoint: https://storage.gitea.io endpoint:
from_secret: aws_endpoint
path_style: true path_style: true
source: "dist/release/*" source: "dist/release/*"
strip_prefix: dist/release/ strip_prefix: dist/release/
@ -116,12 +129,12 @@ steps:
- name: release-branch-release - name: release-branch-release
pull: always pull: always
image: plugins/s3:1 image: woodpeckerci/plugin-s3:latest
settings: settings:
acl: public-read acl: public-read
bucket: gitea-artifacts bucket: gitea-artifacts
endpoint: https://storage.gitea.io endpoint:
path_style: true from_secret: aws_endpoint
source: "dist/release/*" source: "dist/release/*"
strip_prefix: dist/release/ strip_prefix: dist/release/
target: "/tea/${DRONE_BRANCH##release/v}" target: "/tea/${DRONE_BRANCH##release/v}"
@ -138,15 +151,15 @@ steps:
- name: release - name: release
pull: always pull: always
image: plugins/s3:1 image: woodpeckerci/plugin-s3:latest
settings: settings:
acl: public-read acl: public-read
bucket: gitea-artifacts bucket: gitea-artifacts
endpoint: https://storage.gitea.io endpoint:
path_style: true from_secret: aws_endpoint
source: "dist/release/*" source: "dist/release/*"
strip_prefix: dist/release/ strip_prefix: dist/release/
target: /tea/master target: /tea/main
environment: environment:
AWS_ACCESS_KEY_ID: AWS_ACCESS_KEY_ID:
from_secret: aws_access_key_id from_secret: aws_access_key_id
@ -154,7 +167,7 @@ steps:
from_secret: aws_secret_access_key from_secret: aws_secret_access_key
when: when:
branch: branch:
- master - main
event: event:
- push - push

View File

@ -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)

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ tea
dist/ dist/
.vscode/ .vscode/
vendor/

View File

@ -16,7 +16,6 @@ warningCode = 1
[rule.increment-decrement] [rule.increment-decrement]
[rule.var-naming] [rule.var-naming]
[rule.var-declaration] [rule.var-declaration]
[rule.package-comments]
[rule.range] [rule.range]
[rule.receiver-naming] [rule.receiver-naming]
[rule.time-naming] [rule.time-naming]

View File

@ -1,5 +1,73 @@
# Changelog # Changelog
## [v0.9.0](https://gitea.com/gitea/tea/releases/tag/v0.9.0) - 2022-09-13
* BREAKING
* Rename master branch to main (#495)
* Return RFC3339 UTC timestamps for machine-readable output (#470)
* FEATURES
* Allow editing multiline prompts with external text editor (#429)
* Add `tea admin user list` (#427)
* Add `tea whoami` command (#426)
* Add `tea org create <name>` (#420)
* Add `tea clone` (#411)
* Add `tea repo fork` (#410)
* Add `tea repo create-from-template` (#408)
* BUGFIXES
* Fetch all items where needed. (#475)
* Fix running in repos without remote (#472)
* Add TSV to machine-readable formats (#467)
* Fix create milestone with deadline bug (#462)
* Fix resolving of URLs in markdown (#401)
* ENHANCEMENTS
* Don't emit ANSI sequences when not emitting to TTY for markdown (#491)
* Show more version info (#486)
* Add preference `flag_defaults.remote`, refactor (#466)
* Add `--fields` to notification & milestone listings (#422)
* PR listing: add --fields & expose additional fields (#415)
* Add more flags to `tea repo create` (#409)
* Implement more issue filters (#400)
* MISC
* Simplify build & update installation instructions (#437)
* Clarify command descriptions when no arguments are taken (#496)
* Improve Documentation (#433)
* Use golang v1.18 and drop vendor folder (#478)
* Correct spelling of "wether" to "whether" in usage output (#453)
## [v0.8.0](https://gitea.com/gitea/tea/releases/tag/v0.8.0) - 2021-09-22
* BREAKING
* `tea notifications --all` has moved to `tea notifications --mine` (#389)
* `tea notifications` now only works with the context of a remote repo. (#389)
To run this outside of a local git dir, run either tea n `--mine` or `tea n --repo <my/repo>`
* FEATURES
* Add `tea pr merge` (#348)
* BUGFIXES
* Don't skip reading the local repo when `--repo` specifies a repo slug (#398)
* Fix adding login without token on private instances (#392)
* Correctly match login by ssh host with port (#391)
* Fix printing issue deadline (#388)
* Return useful error on wrong sshkey path (#374)
* Fix parsing of `--description` for issue/pr create (#371)
* Add missing flags (#369)
* Check negative limit command parameter (#358) (#359)
* Add missing flags to org & labels subcommands (#357)
* ENHANCEMENTS
* Don't require a body for comment PR reviews (#399)
* Accept more main branch names for login detection (#396)
* Make local repo optional for `tea pr create`(#393)
* Notifications Add State Field (#384)
* Improve error messages (#370)
* Add tab completion for fish shell (#364)
* Text editor selection: follow unix defacto standards (#356)
* MISC
* Update Dependencies (#390)
## [v0.7.1](https://gitea.com/gitea/tea/releases/tag/v0.7.1) - 2021-08-27
* BUILD
* Enable release builds for darwin/arm64 (#360)
## [v0.7.0](https://gitea.com/gitea/tea/releases/tag/v0.7.0) - 2021-03-12 ## [v0.7.0](https://gitea.com/gitea/tea/releases/tag/v0.7.0) - 2021-03-12
* BREAKING * BREAKING

View File

@ -57,8 +57,8 @@ high-level discussions.
## Testing redux ## Testing redux
Before sending code out for review, run all the test by execting: `make test` 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 localy first. Since TEA is an cli tool it should be obvious to test your feature locally first.
## Vendoring ## Vendoring
@ -77,7 +77,7 @@ You can find more information on how to get started with it on the [dep project
## Code review ## Code review
Changes to TEA must be reviewed before they are accepted—no matter who 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. 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 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. - Always make sure that the help texts are properly set, and as concise as possible.
### Internal Module Structure ### Internal Module Structure
- `cmd`: only contains comand/flag options for `urfave/cli` - `cmd`: only contains command/flag options for `urfave/cli`
- subcomands are in a subpackage named after its parent comand - subcommands are in a subpackage named after its parent command
- `modules/task`: contain func for doing something with gitea - `modules/task`: contain func for doing something with gitea
(e.g. create token by user/passwd) (e.g. create token by user/passwd)
- `modules/print`: contain all functions that print to stdout - `modules/print`: contain all functions that print to stdout

25
Dockerfile Normal file
View File

@ -0,0 +1,25 @@
ARG GOVERSION="1.16.2"
FROM golang:${GOVERSION}-alpine AS buildenv
ARG GOOS="linux"
COPY . $GOPATH/src/
WORKDIR $GOPATH/src
RUN apk add --quiet --no-cache \
build-base \
make \
git && \
make clean build STATIC=true
FROM scratch
ARG VERSION="0.7.0"
LABEL org.opencontainers.image.title="tea - CLI for Gitea - git with a cup of tea"
LABEL org.opencontainers.image.description="A command line tool to interact with Gitea servers"
LABEL org.opencontainers.image.version="${VERSION}"
LABEL org.opencontainers.image.authors="Tamás Gérczei <tamas@gerczei.eu>"
LABEL org.opencontainers.image.vendor="The Gitea Authors"
COPY --from=buildenv /go/src/tea /
ENV HOME="/app"
ENTRYPOINT ["/tea"]

101
Makefile
View File

@ -1,31 +1,15 @@
DIST := dist DIST := dist
IMPORT := code.gitea.io/tea
export GO111MODULE=on export GO111MODULE=on
export CGO_ENABLED=0
GO ?= go GO ?= go
SED_INPLACE := sed -i
SHASUM ?= shasum -a 256 SHASUM ?= shasum -a 256
export PATH := $($(GO) env GOPATH)/bin:$(PATH) export PATH := $($(GO) env GOPATH)/bin:$(PATH)
ifeq ($(OS), Windows_NT)
EXECUTABLE := tea.exe
else
EXECUTABLE := tea
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
SED_INPLACE := sed -i ''
endif
endif
GOFILES := $(shell find . -name "*.go" -type f ! -path "./vendor/*" ! -path "*/bindata.go") GOFILES := $(shell find . -name "*.go" -type f ! -path "./vendor/*" ! -path "*/bindata.go")
GOFMT ?= gofmt -s GOFMT ?= gofmt -s
GOFLAGS := -i -v
EXTRA_GOFLAGS ?=
MAKE_VERSION := $(shell make -v | head -n 1)
ifneq ($(DRONE_TAG),) ifneq ($(DRONE_TAG),)
VERSION ?= $(subst v,,$(DRONE_TAG)) VERSION ?= $(subst v,,$(DRONE_TAG))
TEA_VERSION ?= $(VERSION) TEA_VERSION ?= $(VERSION)
@ -37,23 +21,28 @@ else
endif endif
TEA_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') TEA_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
endif endif
TEA_VERSION_TAG ?= $(shell sed 's/+/_/' <<< $(TEA_VERSION))
LDFLAGS := -X "main.Version=$(TEA_VERSION)" -X "main.Tags=$(TAGS)" TAGS ?=
SDK ?= $(shell $(GO) list -f '{{.Version}}' -m code.gitea.io/sdk/gitea)
LDFLAGS := -X "main.Version=$(TEA_VERSION)" -X "main.Tags=$(TAGS)" -X "main.SDK=$(SDK)" -s -w
# 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/) PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/)
SOURCES ?= $(shell find . -name "*.go" -type f) SOURCES ?= $(shell find . -name "*.go" -type f)
TAGS ?= # OS specific vars.
ifeq ($(OS), Windows_NT) ifeq ($(OS), Windows_NT)
EXECUTABLE := tea.exe EXECUTABLE := tea.exe
else else
EXECUTABLE := tea EXECUTABLE := tea
ifneq ($(shell uname -s), OpenBSD)
override BUILDMODE := -buildmode=pie
endif
endif endif
# $(call strip-suffix,filename)
strip-suffix = $(firstword $(subst ., ,$(1)))
.PHONY: all .PHONY: all
all: build all: build
@ -75,24 +64,15 @@ vet:
$(GO) vet -vettool=gitea-vet $(PACKAGES) $(GO) vet -vettool=gitea-vet $(PACKAGES)
.PHONY: lint .PHONY: lint
lint: lint: install-lint-tools
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
cd /tmp && $(GO) get -u github.com/mgechev/revive; \
fi
revive -config .revive.toml -exclude=./vendor/... ./... || exit 1 revive -config .revive.toml -exclude=./vendor/... ./... || exit 1
.PHONY: misspell-check .PHONY: misspell-check
misspell-check: misspell-check: install-lint-tools
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
cd /tmp && $(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi
misspell -error -i unknwon,destory $(GOFILES) misspell -error -i unknwon,destory $(GOFILES)
.PHONY: misspell .PHONY: misspell
misspell: misspell: install-lint-tools
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
cd /tmp && $(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi
misspell -w -i unknwon $(GOFILES) misspell -w -i unknwon $(GOFILES)
.PHONY: fmt-check .PHONY: fmt-check
@ -117,30 +97,26 @@ unit-test-coverage:
vendor: vendor:
$(GO) mod tidy && $(GO) mod vendor $(GO) mod tidy && $(GO) mod vendor
.PHONY: test-vendor
test-vendor: vendor
@diff=$$(git diff vendor/); \
if [ -n "$$diff" ]; then \
echo "Please run 'make vendor' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi;
.PHONY: check .PHONY: check
check: test check: test
.PHONY: install .PHONY: install
install: $(wildcard *.go) install: $(SOURCES)
$(GO) install -mod=vendor -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' @echo "installing to $(GOPATH)/bin/$(EXECUTABLE)"
$(GO) install -v $(BUILDMODE) $(GOFLAGS)
.PHONY: build .PHONY: build
build: $(EXECUTABLE) build: $(EXECUTABLE)
$(EXECUTABLE): $(SOURCES) $(EXECUTABLE): $(SOURCES)
$(GO) build -mod=vendor $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@ $(GO) build $(BUILDMODE) $(GOFLAGS) -o $@
.PHONY: build-image
build-image:
docker build --build-arg VERSION=$(TEA_VERSION) -t gitea/tea:$(TEA_VERSION_TAG) .
.PHONY: release .PHONY: release
release: release-dirs release-os release-compress release-check release: release-dirs install-release-tools release-os release-compress release-check
.PHONY: release-dirs .PHONY: release-dirs
release-dirs: release-dirs:
@ -148,18 +124,29 @@ release-dirs:
.PHONY: release-os .PHONY: release-os
release-os: release-os:
@hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ CGO_ENABLED=0 gox -verbose -cgo=false $(GOFLAGS) -osarch='!darwin/386 !darwin/arm' -os="windows linux darwin" -arch="386 amd64 arm arm64" -output="$(DIST)/release/tea-$(VERSION)-{{.OS}}-{{.Arch}}"
cd /tmp && $(GO) get -u github.com/mitchellh/gox; \
fi
CGO_ENABLED=0 gox -verbose -cgo=false -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -osarch='!darwin/386 !darwin/arm' -os="windows linux darwin" -arch="386 amd64 arm arm64" -output="$(DIST)/release/tea-$(VERSION)-{{.OS}}-{{.Arch}}"
.PHONY: release-compress .PHONY: release-compress
release-compress: release-compress: install-release-tools
@hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
GO111MODULE=off $(GO) get -u github.com/ulikunitz/xz/cmd/gxz; \
fi
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done; cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done;
.PHONY: release-check .PHONY: release-check
release-check: release-check: install-release-tools
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "checksumming $${file}" && $(SHASUM) `echo $${file} | sed 's/^..//'` > $${file}.sha256; done; cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "checksumming $${file}" && $(SHASUM) `echo $${file} | sed 's/^..//'` > $${file}.sha256; done;
### tools
install-release-tools:
@hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install github.com/mitchellh/gox@latest; \
fi
@hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install github.com/ulikunitz/xz/cmd/gxz@latest; \
fi
install-lint-tools:
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install github.com/mgechev/revive@latest; \
fi
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install github.com/client9/misspell/cmd/misspell@latest; \
fi

View File

@ -8,16 +8,19 @@
``` ```
tea - command line tool to interact with Gitea tea - command line tool to interact with Gitea
version 0.7.0-preview version 0.8.0-preview
USAGE USAGE
tea command [subcommand] [command options] [arguments...] tea command [subcommand] [command options] [arguments...]
DESCRIPTION DESCRIPTION
tea is a productivity helper for Gitea. It can be used to manage most entities on one tea is a productivity helper for Gitea. It can be used to manage most entities on
or multiple Gitea instances and provides local helpers like 'tea pull checkout'. one or multiple Gitea instances & provides local helpers like 'tea pr 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 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 COMMANDS
help, h Shows a list of commands or help for one command 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 times, time, t Operate on tracked times of a repository's issues & pulls
organizations, organization, org List, create, delete organizations organizations, organization, org List, create, delete organizations
repos, repo Show repository details repos, repo Show repository details
comment, c Add a comment to an issue / pr
HELPERS: HELPERS:
open, o Open something of the repository in web browser open, o Open something of the repository in web browser
notifications, notification, n Show notifications notifications, notification, n Show notifications
clone, C Clone a repository locally
SETUP: SETUP:
logins, login Log in to a Gitea server logins, login Log in to a Gitea server
logout Log out from a Gitea server logout Log out from a Gitea server
shellcompletion, autocomplete Install shell completion for tea shellcompletion, autocomplete Install shell completion for tea
whoami Show current logged in user
OPTIONS OPTIONS
--help, -h show help (default: false) --help, -h show help (default: false)
@ -58,7 +64,7 @@
tea open milestones # open web ui for milestones tea open milestones # open web ui for milestones
# send gitea desktop notifications every 5 minutes (bash + libnotify) # send gitea desktop notifications every 5 minutes (bash + libnotify)
while :; do tea notifications --all -o simple | xargs -i notify-send {}; sleep 300; done while :; do tea notifications --mine -o simple | xargs -i notify-send {}; sleep 300; done
ABOUT ABOUT
Written & maintained by The Gitea Authors. Written & maintained by The Gitea Authors.
@ -79,29 +85,31 @@ There are different ways to get `tea`:
brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea
brew install tea 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) - 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/) 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): 3. Install from source: [see *Compilation*](#compilation)
```sh
go get code.gitea.io/tea
go install code.gitea.io/tea
```
4. Docker (thirdparty): [tgerczei/tea](https://hub.docker.com/r/tgerczei/tea) 4. Docker (thirdparty): [tgerczei/tea](https://hub.docker.com/r/tgerczei/tea)
## Compilation ## Compilation
Make sure you have installed a current go version. Make sure you have a current go version installed (1.13 or newer).
To compile the sources yourself run the following:
```sh - To compile the source yourself with the recommended flags & tags:
git clone https://gitea.com/gitea/tea.git ```sh
cd tea git clone https://gitea.com/gitea/tea.git # or: tea clone gitea.com/gitea/tea ;)
make cd tea
``` make
```
Note that GNU Make (gmake on OpenBSD) is required.
- For a quick installation without `git` & `make`:
```sh
go install code.gitea.io/tea@latest
```
## Contributing ## Contributing

View File

@ -1,7 +1,8 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//+build vendor //go:build vendor
// +build vendor
package main package main

55
cmd/admin.go Normal file
View File

@ -0,0 +1,55 @@
// 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"
)
// 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: catMisc,
Action: func(cmd *cli.Context) error {
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
}

54
cmd/admin/users/list.go Normal file
View File

@ -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 users
func RunUserList(cmd *cli.Context) error {
ctx := context.InitCommand(cmd)
fields, err := userFieldsFlag.GetValues(cmd)
if err != nil {
return err
}
client := ctx.Login.Client()
users, _, err := client.AdminListUsers(gitea.AdminListUsersOptions{
ListOptions: ctx.GetListOptions(),
})
if err != nil {
return err
}
print.UserList(users, ctx.Output, fields)
return nil
}

View File

@ -22,7 +22,7 @@ var CmdAutocomplete = cli.Command{
Category: catSetup, Category: catSetup,
Usage: "Install shell completion for tea", Usage: "Install shell completion for tea",
Description: "Install shell completion for tea", Description: "Install shell completion for tea",
ArgsUsage: "<shell type> (bash, zsh, powershell)", ArgsUsage: "<shell type> (bash, zsh, powershell, fish)",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
Name: "install", Name: "install",
@ -40,20 +40,26 @@ func runAutocompleteAdd(ctx *cli.Context) error {
case "zsh": case "zsh":
remoteFile = "contrib/autocomplete.zsh" remoteFile = "contrib/autocomplete.zsh"
localFile = "autocomplete.zsh" localFile = "autocomplete.zsh"
cmds = "echo 'PROG=tea _CLI_ZSH_AUTOCOMPLETE_HACK=1 source %s' >> ~/.zshrc && source ~/.zshrc" cmds = "echo 'PROG=tea _CLI_ZSH_AUTOCOMPLETE_HACK=1 source \"%s\"' >> ~/.zshrc && source ~/.zshrc"
case "bash": case "bash":
remoteFile = "contrib/autocomplete.sh" remoteFile = "contrib/autocomplete.sh"
localFile = "autocomplete.sh" localFile = "autocomplete.sh"
cmds = "echo 'PROG=tea source %s' >> ~/.bashrc && source ~/.bashrc" cmds = "echo 'PROG=tea source \"%s\"' >> ~/.bashrc && source ~/.bashrc"
case "powershell": case "powershell":
remoteFile = "contrib/autocomplete.ps1" remoteFile = "contrib/autocomplete.ps1"
localFile = "tea.ps1" localFile = "tea.ps1"
cmds = "\"& %s\" >> $profile" cmds = "\"& %s\" >> $profile"
case "fish":
// fish is different, in that urfave/cli provides a generator for the shell script needed.
// this also means that the fish completion can become out of sync with the tea binary!
// writing to this directory suffices, as fish reads files there on startup, no cmds needed.
return writeFishAutoCompleteFile(ctx)
default: default:
return fmt.Errorf("Must specify valid shell type") return fmt.Errorf("Must specify valid %s", ctx.Command.ArgsUsage)
} }
localPath, err := xdg.ConfigFile("tea/" + localFile) localPath, err := xdg.ConfigFile("tea/" + localFile)
@ -62,8 +68,7 @@ func runAutocompleteAdd(ctx *cli.Context) error {
} }
cmds = fmt.Sprintf(cmds, localPath) cmds = fmt.Sprintf(cmds, localPath)
if err = writeRemoteAutoCompleteFile(remoteFile, localPath); err != nil {
if err := saveAutoCompleteFile(remoteFile, localPath); err != nil {
return err return err
} }
@ -85,7 +90,7 @@ func runAutocompleteAdd(ctx *cli.Context) error {
return nil return nil
} }
func saveAutoCompleteFile(file, destPath string) error { func writeRemoteAutoCompleteFile(file, destPath string) error {
url := fmt.Sprintf("https://gitea.com/gitea/tea/raw/branch/master/%s", file) url := fmt.Sprintf("https://gitea.com/gitea/tea/raw/branch/master/%s", file)
fmt.Println("Fetching " + url) fmt.Println("Fetching " + url)
@ -104,3 +109,30 @@ func saveAutoCompleteFile(file, destPath string) error {
_, err = io.Copy(writer, res.Body) _, err = io.Copy(writer, res.Body)
return err return err
} }
func writeFishAutoCompleteFile(ctx *cli.Context) error {
// NOTE: to make sure this file is in sync with tea commands, we'd need to
// - check if the file exists
// - if it does, check if the tea version that wrote it is the currently running version
// - if not, rewrite the file
// on each application run
// NOTE: this generates a completion that also suggests file names, which looks kinda messy..
script, err := ctx.App.ToFishCompletion()
if err != nil {
return err
}
localPath, err := xdg.ConfigFile("fish/conf.d/tea_completion.fish")
if err != nil {
return err
}
writer, err := os.Create(localPath)
if err != nil {
return err
}
if _, err = io.WriteString(writer, script); err != nil {
return err
}
fmt.Printf("Installed tab completion to %s\n", localPath)
return nil
}

View File

@ -8,4 +8,5 @@ var (
catSetup = "SETUP" catSetup = "SETUP"
catEntities = "ENTITIES" catEntities = "ENTITIES"
catHelpers = "HELPERS" catHelpers = "HELPERS"
catMisc = "MISCELLANEOUS"
) )

89
cmd/clone.go Normal file
View File

@ -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: "<repo-slug> [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
}

View File

@ -9,13 +9,15 @@ import (
"io/ioutil" "io/ioutil"
"strings" "strings"
"code.gitea.io/tea/modules/interact"
"code.gitea.io/sdk/gitea"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/interact"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"code.gitea.io/tea/modules/utils" "code.gitea.io/tea/modules/utils"
"code.gitea.io/sdk/gitea"
"github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/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") body = strings.Join([]string{body, string(bodyStdin)}, "\n\n")
} }
} else if len(body) == 0 { } 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 return err
} }
} }

53
cmd/flags/csvflag.go Normal file
View File

@ -0,0 +1,53 @@
// 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 flags
import (
"fmt"
"strings"
"code.gitea.io/tea/modules/utils"
"github.com/urfave/cli/v2"
)
// CsvFlag is a wrapper around cli.StringFlag, with an added GetValues() method
// to retrieve comma separated string values as a slice.
type CsvFlag struct {
cli.StringFlag
AvailableFields []string
}
// 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.%s
%s
`, usage, availableDesc, strings.Join(availableValues, ",")),
},
}
}
// GetValues returns the value of the flag, parsed as a commaseparated list
func (f CsvFlag) GetValues(ctx *cli.Context) ([]string, error) {
val := ctx.String(f.Name)
selection := strings.Split(val, ",")
if f.AvailableFields != nil && val != "" {
for _, field := range selection {
if !utils.Contains(f.AvailableFields, field) {
return nil, fmt.Errorf("Invalid field '%s'", field)
}
}
}
return selection, nil
}

View File

@ -1,205 +0,0 @@
// 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"
"code.gitea.io/tea/modules/utils"
"github.com/araddon/dateparse"
"github.com/urfave/cli/v2"
)
// LoginFlag provides flag to specify tea login profile
var LoginFlag = cli.StringFlag{
Name: "login",
Aliases: []string{"l"},
Usage: "Use a different Gitea Login. Optional",
}
// RepoFlag provides flag to specify repository
var RepoFlag = cli.StringFlag{
Name: "repo",
Aliases: []string{"r"},
Usage: "Override local repository path or gitea repository slug to interact with. Optional",
}
// RemoteFlag provides flag to specify remote repository
var RemoteFlag = cli.StringFlag{
Name: "remote",
Aliases: []string{"R"},
Usage: "Discover Gitea login from remote. Optional",
}
// OutputFlag provides flag to specify output type
var OutputFlag = cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
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",
Aliases: []string{"p"},
Usage: "specify page, default is 1",
}
// PaginationLimitFlag provides flag for pagination options
var PaginationLimitFlag = cli.StringFlag{
Name: "limit",
Aliases: []string{"lm"},
Usage: "specify limit of items per page",
}
// LoginOutputFlags defines login and output flags that should
// added to all subcommands and appended to the flags of the
// subcommand to work around issue and provide --login and --output:
// https://github.com/urfave/cli/issues/585
var LoginOutputFlags = []cli.Flag{
&LoginFlag,
&OutputFlag,
}
// LoginRepoFlags defines login and repo flags that should
// be used for all subcommands and appended to the flags of
// the subcommand to work around issue and provide --login and --repo:
// https://github.com/urfave/cli/issues/585
var LoginRepoFlags = []cli.Flag{
&LoginFlag,
&RepoFlag,
&RemoteFlag,
}
// AllDefaultFlags defines flags that should be available
// for all subcommands working with dedicated repositories
// to work around issue and provide --login, --repo and --output:
// https://github.com/urfave/cli/issues/585
var AllDefaultFlags = append([]cli.Flag{
&RepoFlag,
&RemoteFlag,
}, LoginOutputFlags...)
// IssuePRFlags defines flags that should be available on issue & pr listing flags.
var IssuePRFlags = append([]cli.Flag{
&StateFlag,
&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("body"),
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 GetFields()
func FieldsFlag(availableFields, defaultFields []string) *cli.StringFlag {
return &cli.StringFlag{
Name: "fields",
Aliases: []string{"f"},
Usage: fmt.Sprintf(`Comma-separated list of fields to print. Available values:
%s
`, strings.Join(availableFields, ",")),
Value: strings.Join(defaultFields, ","),
}
}
// GetFields parses the values provided in a fields flag, and
// optionally validates against valid values.
func GetFields(ctx *cli.Context, validFields []string) ([]string, error) {
selection := strings.Split(ctx.String("fields"), ",")
if validFields != nil {
for _, field := range selection {
if !utils.Contains(validFields, field) {
return nil, fmt.Errorf("Invalid field '%s'", field)
}
}
}
return selection, nil
}

106
cmd/flags/generic.go Normal file
View File

@ -0,0 +1,106 @@
// 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 (
"github.com/urfave/cli/v2"
)
// LoginFlag provides flag to specify tea login profile
var LoginFlag = cli.StringFlag{
Name: "login",
Aliases: []string{"l"},
Usage: "Use a different Gitea Login. Optional",
}
// RepoFlag provides flag to specify repository
var RepoFlag = cli.StringFlag{
Name: "repo",
Aliases: []string{"r"},
Usage: "Override local repository path or gitea repository slug to interact with. Optional",
}
// RemoteFlag provides flag to specify remote repository
var RemoteFlag = cli.StringFlag{
Name: "remote",
Aliases: []string{"R"},
Usage: "Discover Gitea login from remote. Optional",
}
// OutputFlag provides flag to specify output type
var OutputFlag = cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Output format. (csv, simple, table, tsv, yaml)",
}
// PaginationPageFlag provides flag for pagination options
var PaginationPageFlag = cli.StringFlag{
Name: "page",
Aliases: []string{"p"},
Usage: "specify page, default is 1",
}
// PaginationLimitFlag provides flag for pagination options
var PaginationLimitFlag = cli.StringFlag{
Name: "limit",
Aliases: []string{"lm"},
Usage: "specify limit of items per page",
}
// LoginOutputFlags defines login and output flags that should
// added to all subcommands and appended to the flags of the
// subcommand to work around issue and provide --login and --output:
// https://github.com/urfave/cli/issues/585
var LoginOutputFlags = []cli.Flag{
&LoginFlag,
&OutputFlag,
}
// LoginRepoFlags defines login and repo flags that should
// be used for all subcommands and appended to the flags of
// the subcommand to work around issue and provide --login and --repo:
// https://github.com/urfave/cli/issues/585
var LoginRepoFlags = []cli.Flag{
&LoginFlag,
&RepoFlag,
&RemoteFlag,
}
// AllDefaultFlags defines flags that should be available
// for all subcommands working with dedicated repositories
// to work around issue and provide --login, --repo and --output:
// https://github.com/urfave/cli/issues/585
var AllDefaultFlags = append([]cli.Flag{
&RepoFlag,
&RemoteFlag,
}, LoginOutputFlags...)
// NotificationFlags defines flags that should be available on notifications.
var NotificationFlags = append([]cli.Flag{
NotificationStateFlag,
&cli.BoolFlag{
Name: "mine",
Aliases: []string{"m"},
Usage: "Show notifications across all your repositories instead of the current repository only",
},
&PaginationPageFlag,
&PaginationLimitFlag,
}, AllDefaultFlags...)
// NotificationStateFlag is a csv flag applied to all notification subcommands as filter
var NotificationStateFlag = NewCsvFlag(
"states",
"notification states to filter by",
[]string{"s"},
[]string{"pinned", "unread", "read"},
[]string{"unread", "pinned"},
)
// FieldsFlag generates a flag selecting printable fields.
// To retrieve the value, use f.GetValues()
func FieldsFlag(availableFields, defaultFields []string) *CsvFlag {
return NewCsvFlag("fields", "fields to print", []string{"f"}, availableFields, defaultFields)
}

161
cmd/flags/issue_pr.go Normal file
View File

@ -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
}

View File

@ -34,7 +34,7 @@ var CmdIssues = cli.Command{
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
Name: "comments", 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...), }, issues.CmdIssuesList.Flags...),
} }
@ -54,11 +54,16 @@ func runIssueDetail(cmd *cli.Context, index string) error {
if err != nil { if err != nil {
return err return err
} }
issue, _, err := ctx.Login.Client().GetIssue(ctx.Owner, ctx.Repo, idx) client := ctx.Login.Client()
issue, _, err := client.GetIssue(ctx.Owner, ctx.Repo, idx)
if err != nil { if err != nil {
return err return err
} }
print.IssueDetails(issue) reactions, _, err := client.GetIssueReactions(ctx.Owner, ctx.Repo, idx)
if err != nil {
return err
}
print.IssueDetails(issue, reactions)
if issue.Comments > 0 { if issue.Comments > 0 {
err = interact.ShowCommentsMaybeInteractive(ctx, idx, issue.Comments) err = interact.ShowCommentsMaybeInteractive(ctx, idx, issue.Comments)

View File

@ -47,6 +47,6 @@ func editIssueState(cmd *cli.Context, opts gitea.EditIssueOption) error {
return err return err
} }
print.IssueDetails(issue) print.IssueDetails(issue, nil)
return nil return nil
} }

View File

@ -19,6 +19,7 @@ var CmdIssuesCreate = cli.Command{
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: "Create an issue on repository", Usage: "Create an issue on repository",
Description: `Create an issue on repository`, Description: `Create an issue on repository`,
ArgsUsage: " ", // command does not accept arguments
Action: runIssuesCreate, Action: runIssuesCreate,
Flags: flags.IssuePREditFlags, Flags: flags.IssuePREditFlags,
} }

View File

@ -5,26 +5,31 @@
package issues package issues
import ( import (
"fmt"
"time"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/araddon/dateparse"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var issueFieldsFlag = flags.FieldsFlag(print.IssueFields, []string{
"index", "title", "state", "author", "milestone", "labels",
})
// CmdIssuesList represents a sub command of issues to list issues // CmdIssuesList represents a sub command of issues to list issues
var CmdIssuesList = cli.Command{ var CmdIssuesList = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List issues of the repository", Usage: "List issues of the repository",
Description: `List issues of the repository`, Description: `List issues of the repository`,
ArgsUsage: " ", // command does not accept arguments
Action: RunIssuesList, Action: RunIssuesList,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{issueFieldsFlag}, flags.IssueListingFlags...),
flags.FieldsFlag(print.IssueFields, []string{
"index", "title", "state", "author", "milestone", "labels",
}),
}, flags.IssuePRFlags...),
} }
// RunIssuesList list issues // RunIssuesList list issues
@ -36,23 +41,64 @@ func RunIssuesList(cmd *cli.Context) error {
switch ctx.String("state") { switch ctx.String("state") {
case "all": case "all":
state = gitea.StateAll state = gitea.StateAll
case "open": case "", "open":
state = gitea.StateOpen state = gitea.StateOpen
case "closed": case "closed":
state = gitea.StateClosed 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{ issues, _, err := ctx.Login.Client().ListRepoIssues(ctx.Owner, ctx.Repo, gitea.ListIssueOption{
ListOptions: ctx.GetListOptions(), ListOptions: ctx.GetListOptions(),
State: state, 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 { if err != nil {
return err return err
} }
fields, err := flags.GetFields(cmd, print.IssueFields) fields, err := issueFieldsFlag.GetValues(cmd)
if err != nil { if err != nil {
return err return err
} }

View File

@ -18,6 +18,7 @@ var CmdLabels = cli.Command{
Category: catEntities, Category: catEntities,
Usage: "Manage issue labels", Usage: "Manage issue labels",
Description: `Manage issue labels`, Description: `Manage issue labels`,
ArgsUsage: " ", // command does not accept arguments
Action: runLabels, Action: runLabels,
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
&labels.CmdLabelsList, &labels.CmdLabelsList,
@ -25,6 +26,7 @@ var CmdLabels = cli.Command{
&labels.CmdLabelUpdate, &labels.CmdLabelUpdate,
&labels.CmdLabelDelete, &labels.CmdLabelDelete,
}, },
Flags: labels.CmdLabelsList.Flags,
} }
func runLabels(ctx *cli.Context) error { func runLabels(ctx *cli.Context) error {

View File

@ -10,6 +10,7 @@ import (
"os" "os"
"strings" "strings"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
@ -22,8 +23,9 @@ var CmdLabelCreate = cli.Command{
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: "Create a label", Usage: "Create a label",
Description: `Create a label`, Description: `Create a label`,
ArgsUsage: " ", // command does not accept arguments
Action: runLabelCreate, Action: runLabelCreate,
Flags: []cli.Flag{ Flags: append([]cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Usage: "label name", Usage: "label name",
@ -40,7 +42,7 @@ var CmdLabelCreate = cli.Command{
Name: "file", Name: "file",
Usage: "indicate a label file", Usage: "indicate a label file",
}, },
}, }, flags.AllDefaultFlags...),
} }
func runLabelCreate(cmd *cli.Context) error { func runLabelCreate(cmd *cli.Context) error {

View File

@ -5,6 +5,7 @@
package labels package labels
import ( import (
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -16,13 +17,14 @@ var CmdLabelDelete = cli.Command{
Aliases: []string{"rm"}, Aliases: []string{"rm"},
Usage: "Delete a label", Usage: "Delete a label",
Description: `Delete a label`, Description: `Delete a label`,
ArgsUsage: " ", // command does not accept arguments
Action: runLabelDelete, Action: runLabelDelete,
Flags: []cli.Flag{ Flags: append([]cli.Flag{
&cli.IntFlag{ &cli.IntFlag{
Name: "id", Name: "id",
Usage: "label id", Usage: "label id",
}, },
}, }, flags.AllDefaultFlags...),
} }
func runLabelDelete(cmd *cli.Context) error { func runLabelDelete(cmd *cli.Context) error {

View File

@ -20,6 +20,7 @@ var CmdLabelsList = cli.Command{
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List labels", Usage: "List labels",
Description: "List labels", Description: "List labels",
ArgsUsage: " ", // command does not accept arguments
Action: RunLabelsList, Action: RunLabelsList,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{

View File

@ -5,6 +5,7 @@
package labels package labels
import ( import (
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
@ -16,8 +17,9 @@ var CmdLabelUpdate = cli.Command{
Name: "update", Name: "update",
Usage: "Update a label", Usage: "Update a label",
Description: `Update a label`, Description: `Update a label`,
ArgsUsage: " ", // command does not accept arguments
Action: runLabelUpdate, Action: runLabelUpdate,
Flags: []cli.Flag{ Flags: append([]cli.Flag{
&cli.IntFlag{ &cli.IntFlag{
Name: "id", Name: "id",
Usage: "label id", Usage: "label id",
@ -34,7 +36,7 @@ var CmdLabelUpdate = cli.Command{
Name: "description", Name: "description",
Usage: "label description", Usage: "label description",
}, },
}, }, flags.AllDefaultFlags...),
} }
func runLabelUpdate(cmd *cli.Context) error { func runLabelUpdate(cmd *cli.Context) error {

View File

@ -16,6 +16,7 @@ var CmdLoginAdd = cli.Command{
Name: "add", Name: "add",
Usage: "Add a Gitea login", Usage: "Add a Gitea login",
Description: `Add a Gitea login, without args it will create one interactively`, Description: `Add a Gitea login, without args it will create one interactively`,
ArgsUsage: " ", // command does not accept arguments
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",

View File

@ -18,6 +18,7 @@ var CmdLoginEdit = cli.Command{
Aliases: []string{"e"}, Aliases: []string{"e"},
Usage: "Edit Gitea logins", Usage: "Edit Gitea logins",
Description: `Edit Gitea logins`, Description: `Edit Gitea logins`,
ArgsUsage: " ", // command does not accept arguments
Action: runLoginEdit, Action: runLoginEdit,
Flags: []cli.Flag{&flags.OutputFlag}, Flags: []cli.Flag{&flags.OutputFlag},
} }

View File

@ -18,6 +18,7 @@ var CmdLoginList = cli.Command{
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List Gitea logins", Usage: "List Gitea logins",
Description: `List Gitea logins`, Description: `List Gitea logins`,
ArgsUsage: " ", // command does not accept arguments
Action: RunLoginList, Action: RunLoginList,
Flags: []cli.Flag{&flags.OutputFlag}, Flags: []cli.Flag{&flags.OutputFlag},
} }

View File

@ -23,6 +23,7 @@ var CmdMilestonesCreate = cli.Command{
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: "Create an milestone on repository", Usage: "Create an milestone on repository",
Description: `Create an milestone on repository`, Description: `Create an milestone on repository`,
ArgsUsage: " ", // command does not accept arguments
Action: runMilestonesCreate, Action: runMilestonesCreate,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
@ -55,7 +56,7 @@ func runMilestonesCreate(cmd *cli.Context) error {
deadline := &time.Time{} deadline := &time.Time{}
if date != "" { if date != "" {
t, err := dateparse.ParseAny(date) t, err := dateparse.ParseAny(date)
if err == nil { if err != nil {
return err return err
} }
deadline = &t deadline = &t

View File

@ -16,6 +16,10 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var msIssuesFieldsFlag = flags.FieldsFlag(print.IssueFields, []string{
"index", "kind", "title", "state", "updated", "labels",
})
// CmdMilestonesIssues represents a sub command of milestones to manage issue/pull of an milestone // CmdMilestonesIssues represents a sub command of milestones to manage issue/pull of an milestone
var CmdMilestonesIssues = cli.Command{ var CmdMilestonesIssues = cli.Command{
Name: "issues", Name: "issues",
@ -40,9 +44,7 @@ var CmdMilestonesIssues = cli.Command{
}, },
&flags.PaginationPageFlag, &flags.PaginationPageFlag,
&flags.PaginationLimitFlag, &flags.PaginationLimitFlag,
flags.FieldsFlag(print.IssueFields, []string{ msIssuesFieldsFlag,
"index", "kind", "title", "state", "updated", "labels",
}),
}, flags.AllDefaultFlags...), }, flags.AllDefaultFlags...),
} }
@ -110,7 +112,7 @@ func runMilestoneIssueList(cmd *cli.Context) error {
return err return err
} }
fields, err := flags.GetFields(cmd, print.IssueFields) fields, err := msIssuesFieldsFlag.GetValues(cmd)
if err != nil { if err != nil {
return err return err
} }

View File

@ -13,14 +13,20 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var fieldsFlag = flags.FieldsFlag(print.MilestoneFields, []string{
"title", "items", "duedate",
})
// CmdMilestonesList represents a sub command of milestones to list milestones // CmdMilestonesList represents a sub command of milestones to list milestones
var CmdMilestonesList = cli.Command{ var CmdMilestonesList = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List milestones of the repository", Usage: "List milestones of the repository",
Description: `List milestones of the repository`, Description: `List milestones of the repository`,
ArgsUsage: " ", // command does not accept arguments
Action: RunMilestonesList, Action: RunMilestonesList,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
fieldsFlag,
&cli.StringFlag{ &cli.StringFlag{
Name: "state", Name: "state",
Usage: "Filter by milestone state (all|open|closed)", Usage: "Filter by milestone state (all|open|closed)",
@ -36,10 +42,18 @@ func RunMilestonesList(cmd *cli.Context) error {
ctx := context.InitCommand(cmd) ctx := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
fields, err := fieldsFlag.GetValues(cmd)
if err != nil {
return err
}
state := gitea.StateOpen state := gitea.StateOpen
switch ctx.String("state") { switch ctx.String("state") {
case "all": case "all":
state = gitea.StateAll state = gitea.StateAll
if !cmd.IsSet("fields") { // add to default fields
fields = append(fields, "state")
}
case "closed": case "closed":
state = gitea.StateClosed state = gitea.StateClosed
} }
@ -54,6 +68,6 @@ func RunMilestonesList(cmd *cli.Context) error {
return err return err
} }
print.MilestonesList(milestones, ctx.Output, state) print.MilestonesList(milestones, ctx.Output, fields)
return nil return nil
} }

View File

@ -5,11 +5,8 @@
package cmd package cmd
import ( import (
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/notifications"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -19,65 +16,14 @@ var CmdNotifications = cli.Command{
Aliases: []string{"notification", "n"}, Aliases: []string{"notification", "n"},
Category: catHelpers, Category: catHelpers,
Usage: "Show notifications", Usage: "Show notifications",
Description: "Show notifications, by default based of the current repo and unread one", Description: "Show notifications, by default based on the current repo if available",
Action: runNotifications, Action: notifications.RunNotificationsList,
Flags: append([]cli.Flag{ Subcommands: []*cli.Command{
&cli.BoolFlag{ &notifications.CmdNotificationsList,
Name: "all", &notifications.CmdNotificationsMarkRead,
Aliases: []string{"a"}, &notifications.CmdNotificationsMarkUnread,
Usage: "show all notifications of related gitea instance", &notifications.CmdNotificationsMarkPinned,
}, &notifications.CmdNotificationsUnpin,
&cli.BoolFlag{ },
Name: "read", Flags: notifications.CmdNotificationsList.Flags,
Aliases: []string{"rd"},
Usage: "show read notifications instead unread",
},
&cli.BoolFlag{
Name: "pinned",
Aliases: []string{"pd"},
Usage: "show pinned notifications instead unread",
},
&flags.PaginationPageFlag,
&flags.PaginationLimitFlag,
}, flags.AllDefaultFlags...),
}
func runNotifications(cmd *cli.Context) error {
var news []*gitea.NotificationThread
var err error
ctx := context.InitCommand(cmd)
client := ctx.Login.Client()
listOpts := ctx.GetListOptions()
if listOpts.Page == 0 {
listOpts.Page = 1
}
var status []gitea.NotifyStatus
if ctx.Bool("read") {
status = []gitea.NotifyStatus{gitea.NotifyStatusRead}
}
if ctx.Bool("pinned") {
status = append(status, gitea.NotifyStatusPinned)
}
if ctx.Bool("all") {
news, _, err = client.ListNotifications(gitea.ListNotificationOptions{
ListOptions: listOpts,
Status: status,
})
} else {
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
news, _, err = client.ListRepoNotifications(ctx.Owner, ctx.Repo, gitea.ListNotificationOptions{
ListOptions: listOpts,
Status: status,
})
}
if err != nil {
return err
}
print.NotificationsList(news, ctx.Output, ctx.Bool("all"))
return nil
} }

107
cmd/notifications/list.go Normal file
View File

@ -0,0 +1,107 @@
// 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 notifications
import (
"log"
"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 notifyFieldsFlag = flags.FieldsFlag(print.NotificationFields, []string{
"id", "status", "index", "type", "state", "title",
})
var notifyTypeFlag = flags.NewCsvFlag("types", "subject types to filter by", []string{"t"},
[]string{"issue", "pull", "repository", "commit"}, nil)
// CmdNotificationsList represents a sub command of notifications to list notifications
var CmdNotificationsList = cli.Command{
Name: "ls",
Aliases: []string{"list"},
Usage: "List notifications",
Description: `List notifications`,
ArgsUsage: " ", // command does not accept arguments
Action: RunNotificationsList,
Flags: append([]cli.Flag{
notifyFieldsFlag,
notifyTypeFlag,
}, flags.NotificationFlags...),
}
// RunNotificationsList list notifications
func RunNotificationsList(ctx *cli.Context) error {
var states []gitea.NotifyStatus
statesStr, err := flags.NotificationStateFlag.GetValues(ctx)
if err != nil {
return err
}
for _, s := range statesStr {
states = append(states, gitea.NotifyStatus(s))
}
var types []gitea.NotifySubjectType
typesStr, err := notifyTypeFlag.GetValues(ctx)
if err != nil {
return err
}
for _, t := range typesStr {
types = append(types, gitea.NotifySubjectType(t))
}
return listNotifications(ctx, states, types)
}
// listNotifications will get the notifications based on status and subject type
func listNotifications(cmd *cli.Context, status []gitea.NotifyStatus, subjects []gitea.NotifySubjectType) error {
var news []*gitea.NotificationThread
var err error
ctx := context.InitCommand(cmd)
client := ctx.Login.Client()
all := ctx.Bool("mine")
// This enforces pagination (see https://github.com/go-gitea/gitea/issues/16733)
listOpts := ctx.GetListOptions()
if listOpts.Page == 0 {
listOpts.Page = 1
}
fields, err := notifyFieldsFlag.GetValues(cmd)
if err != nil {
return err
}
if all {
// add repository to the default fields
if !cmd.IsSet("fields") {
fields = append(fields, "repository")
}
news, _, err = client.ListNotifications(gitea.ListNotificationOptions{
ListOptions: listOpts,
Status: status,
SubjectTypes: subjects,
})
} else {
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
news, _, err = client.ListRepoNotifications(ctx.Owner, ctx.Repo, gitea.ListNotificationOptions{
ListOptions: listOpts,
Status: status,
SubjectTypes: subjects,
})
}
if err != nil {
log.Fatal(err)
}
print.NotificationsList(news, ctx.Output, fields)
return nil
}

View File

@ -0,0 +1,139 @@
// 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 notifications
import (
"fmt"
"code.gitea.io/sdk/gitea"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/utils"
"github.com/urfave/cli/v2"
)
// CmdNotificationsMarkRead represents a sub command of notifications to list read notifications
var CmdNotificationsMarkRead = cli.Command{
Name: "read",
Aliases: []string{"r"},
Usage: "Mark all filtered or a specific notification as read",
Description: "Mark all filtered or a specific notification as read",
ArgsUsage: "[all | <notification id>]",
Flags: flags.NotificationFlags,
Action: func(ctx *cli.Context) error {
cmd := context.InitCommand(ctx)
filter, err := flags.NotificationStateFlag.GetValues(ctx)
if err != nil {
return err
}
if !cmd.IsSet(flags.NotificationStateFlag.Name) {
filter = []string{string(gitea.NotifyStatusUnread)}
}
return markNotificationAs(cmd, filter, gitea.NotifyStatusRead)
},
}
// CmdNotificationsMarkUnread will mark notifications as unread.
var CmdNotificationsMarkUnread = cli.Command{
Name: "unread",
Aliases: []string{"u"},
Usage: "Mark all filtered or a specific notification as unread",
Description: "Mark all filtered or a specific notification as unread",
ArgsUsage: "[all | <notification id>]",
Flags: flags.NotificationFlags,
Action: func(ctx *cli.Context) error {
cmd := context.InitCommand(ctx)
filter, err := flags.NotificationStateFlag.GetValues(ctx)
if err != nil {
return err
}
if !cmd.IsSet(flags.NotificationStateFlag.Name) {
filter = []string{string(gitea.NotifyStatusRead)}
}
return markNotificationAs(cmd, filter, gitea.NotifyStatusUnread)
},
}
// CmdNotificationsMarkPinned will mark notifications as unread.
var CmdNotificationsMarkPinned = cli.Command{
Name: "pin",
Aliases: []string{"p"},
Usage: "Mark all filtered or a specific notification as pinned",
Description: "Mark all filtered or a specific notification as pinned",
ArgsUsage: "[all | <notification id>]",
Flags: flags.NotificationFlags,
Action: func(ctx *cli.Context) error {
cmd := context.InitCommand(ctx)
filter, err := flags.NotificationStateFlag.GetValues(ctx)
if err != nil {
return err
}
if !cmd.IsSet(flags.NotificationStateFlag.Name) {
filter = []string{string(gitea.NotifyStatusUnread)}
}
return markNotificationAs(cmd, filter, gitea.NotifyStatusPinned)
},
}
// CmdNotificationsUnpin will mark pinned notifications as unread.
var CmdNotificationsUnpin = cli.Command{
Name: "unpin",
Usage: "Unpin all pinned or a specific notification",
Description: "Marks all pinned or a specific notification as read",
ArgsUsage: "[all | <notification id>]",
Flags: flags.NotificationFlags,
Action: func(ctx *cli.Context) error {
cmd := context.InitCommand(ctx)
filter := []string{string(gitea.NotifyStatusPinned)}
// NOTE: we implicitly mark it as read, to match web UI semantics. marking as unread might be more useful?
return markNotificationAs(cmd, filter, gitea.NotifyStatusRead)
},
}
func markNotificationAs(cmd *context.TeaContext, filterStates []string, targetState gitea.NotifyStatus) (err error) {
client := cmd.Login.Client()
subject := cmd.Args().First()
allRepos := cmd.Bool("mine")
states := []gitea.NotifyStatus{}
for _, s := range filterStates {
states = append(states, gitea.NotifyStatus(s))
}
switch subject {
case "", "all":
opts := gitea.MarkNotificationOptions{Status: states, ToStatus: targetState}
if allRepos {
_, _, err = client.ReadNotifications(opts)
} else {
cmd.Ensure(context.CtxRequirement{RemoteRepo: true})
_, _, err = client.ReadRepoNotifications(cmd.Owner, cmd.Repo, opts)
}
// TODO: print all affected notification subject URLs
// (not supported by API currently, https://github.com/go-gitea/gitea/issues/16797)
default:
id, err := utils.ArgToIndex(subject)
if err != nil {
return err
}
_, _, err = client.ReadNotification(id, targetState)
if err != nil {
return err
}
n, _, err := client.GetNotification(id)
if err != nil {
return err
}
// FIXME: this is an API URL, we want to display a web ui link..
fmt.Println(n.Subject.URL)
return nil
}
return err
}

View File

@ -5,9 +5,9 @@
package cmd package cmd
import ( import (
"fmt"
"code.gitea.io/tea/cmd/organizations" "code.gitea.io/tea/cmd/organizations"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -23,17 +23,26 @@ var CmdOrgs = cli.Command{
Action: runOrganizations, Action: runOrganizations,
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
&organizations.CmdOrganizationList, &organizations.CmdOrganizationList,
&organizations.CmdOrganizationCreate,
&organizations.CmdOrganizationDelete, &organizations.CmdOrganizationDelete,
}, },
Flags: organizations.CmdOrganizationList.Flags,
} }
func runOrganizations(ctx *cli.Context) error { func runOrganizations(cmd *cli.Context) error {
ctx := context.InitCommand(cmd)
if ctx.Args().Len() == 1 { if ctx.Args().Len() == 1 {
return runOrganizationDetail(ctx.Args().First()) return runOrganizationDetail(ctx)
} }
return organizations.RunOrganizationList(ctx) return organizations.RunOrganizationList(cmd)
} }
func runOrganizationDetail(path string) error { func runOrganizationDetail(ctx *context.TeaContext) error {
return fmt.Errorf("Not yet implemented") org, _, err := ctx.Login.Client().GetOrg(ctx.Args().First())
if err != nil {
return err
}
print.OrganizationDetails(org)
return nil
} }

View File

@ -0,0 +1,90 @@
// 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 organizations
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"
)
// CmdOrganizationCreate represents a sub command of organizations to delete a given user organization
var CmdOrganizationCreate = cli.Command{
Name: "create",
Aliases: []string{"c"},
Usage: "Create an organization",
Description: "Create an organization",
Action: RunOrganizationCreate,
ArgsUsage: "<organization name>",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
},
&cli.StringFlag{
Name: "description",
Aliases: []string{"d"},
},
&cli.StringFlag{
Name: "website",
Aliases: []string{"w"},
},
&cli.StringFlag{
Name: "location",
Aliases: []string{"L"},
},
&cli.StringFlag{
Name: "visibility",
Aliases: []string{"v"},
},
&cli.BoolFlag{
Name: "repo-admins-can-change-team-access",
},
&flags.LoginFlag,
},
}
// RunOrganizationCreate sets up a new organization
func RunOrganizationCreate(cmd *cli.Context) error {
ctx := context.InitCommand(cmd)
if ctx.Args().Len() < 1 {
return fmt.Errorf("You have to specify the organization name you want to create")
}
var visibility gitea.VisibleType
switch ctx.String("visibility") {
case "", "public":
visibility = gitea.VisibleTypePublic
case "private":
visibility = gitea.VisibleTypePrivate
case "limited":
visibility = gitea.VisibleTypeLimited
default:
return fmt.Errorf("unknown visibility '%s'", ctx.String("visibility"))
}
org, _, err := ctx.Login.Client().CreateOrg(gitea.CreateOrgOption{
Name: ctx.Args().First(),
// FullName: , // not really meaningful for orgs (not displayed in webui, use description instead?)
Description: ctx.String("description"),
Website: ctx.String("website"),
Location: ctx.String("location"),
RepoAdminChangeTeamAccess: ctx.Bool("repo-admins-can-change-team-access"),
Visibility: visibility,
})
if err != nil {
return err
}
print.OrganizationDetails(org)
return err
}

View File

@ -7,6 +7,7 @@ package organizations
import ( import (
"fmt" "fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -19,6 +20,10 @@ var CmdOrganizationDelete = cli.Command{
Description: "Delete users organizations", Description: "Delete users organizations",
ArgsUsage: "<organization name>", ArgsUsage: "<organization name>",
Action: RunOrganizationDelete, Action: RunOrganizationDelete,
Flags: []cli.Flag{
&flags.LoginFlag,
&flags.RemoteFlag,
},
} }
// RunOrganizationDelete delete user organization // RunOrganizationDelete delete user organization

View File

@ -19,6 +19,7 @@ var CmdOrganizationList = cli.Command{
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List Organizations", Usage: "List Organizations",
Description: "List users organizations", Description: "List users organizations",
ArgsUsage: " ", // command does not accept arguments
Action: RunOrganizationList, Action: RunOrganizationList,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&flags.PaginationPageFlag, &flags.PaginationPageFlag,

View File

@ -30,7 +30,7 @@ var CmdPulls = cli.Command{
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
Name: "comments", 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...), }, pulls.CmdPullsList.Flags...),
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
@ -43,6 +43,7 @@ var CmdPulls = cli.Command{
&pulls.CmdPullsReview, &pulls.CmdPullsReview,
&pulls.CmdPullsApprove, &pulls.CmdPullsApprove,
&pulls.CmdPullsReject, &pulls.CmdPullsReject,
&pulls.CmdPullsMerge,
}, },
} }
@ -70,7 +71,9 @@ func runPullDetail(cmd *cli.Context, index string) error {
return err return err
} }
reviews, _, err := client.ListPullReviews(ctx.Owner, ctx.Repo, idx, gitea.ListPullReviewsOptions{}) reviews, _, err := client.ListPullReviews(ctx.Owner, ctx.Repo, idx, gitea.ListPullReviewsOptions{
ListOptions: gitea.ListOptions{Page: -1},
})
if err != nil { if err != nil {
fmt.Printf("error while loading reviews: %v\n", err) fmt.Printf("error while loading reviews: %v\n", err)
} }

View File

@ -35,7 +35,10 @@ var CmdPullsCheckout = cli.Command{
func runPullsCheckout(cmd *cli.Context) error { func runPullsCheckout(cmd *cli.Context) error {
ctx := context.InitCommand(cmd) ctx := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{LocalRepo: true}) ctx.Ensure(context.CtxRequirement{
LocalRepo: true,
RemoteRepo: true,
})
if ctx.Args().Len() != 1 { if ctx.Args().Len() != 1 {
return fmt.Errorf("Must specify a PR index") return fmt.Errorf("Must specify a PR index")
} }

View File

@ -18,28 +18,27 @@ var CmdPullsCreate = cli.Command{
Name: "create", Name: "create",
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: "Create a pull-request", Usage: "Create a pull-request",
Description: "Create a pull-request", Description: "Create a pull-request in the current repo",
Action: runPullsCreate, Action: runPullsCreate,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "head", 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 <user>:<branch>",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "base", Name: "base",
Aliases: []string{"b"}, 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...), }, flags.IssuePREditFlags...),
} }
func runPullsCreate(cmd *cli.Context) error { func runPullsCreate(cmd *cli.Context) error {
ctx := context.InitCommand(cmd) ctx := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{LocalRepo: true})
// no args -> interactive mode // no args -> interactive mode
if ctx.NumFlags() == 0 { if ctx.NumFlags() == 0 {
return interact.CreatePull(ctx.Login, ctx.Owner, ctx.Repo) return interact.CreatePull(ctx)
} }
// else use args to create PR // else use args to create PR
@ -49,9 +48,7 @@ func runPullsCreate(cmd *cli.Context) error {
} }
return task.CreatePull( return task.CreatePull(
ctx.Login, ctx,
ctx.Owner,
ctx.Repo,
ctx.String("base"), ctx.String("base"),
ctx.String("head"), ctx.String("head"),
opts, opts,

View File

@ -13,14 +13,19 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var pullFieldsFlag = flags.FieldsFlag(print.PullFields, []string{
"index", "title", "state", "author", "milestone", "updated", "labels",
})
// CmdPullsList represents a sub command of issues to list pulls // CmdPullsList represents a sub command of issues to list pulls
var CmdPullsList = cli.Command{ var CmdPullsList = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List pull requests of the repository", Usage: "List pull requests of the repository",
Description: `List pull requests of the repository`, Description: `List pull requests of the repository`,
ArgsUsage: " ", // command does not accept arguments
Action: RunPullsList, Action: RunPullsList,
Flags: flags.IssuePRFlags, Flags: append([]cli.Flag{pullFieldsFlag}, flags.PRListingFlags...),
} }
// RunPullsList return list of pulls // RunPullsList return list of pulls
@ -46,6 +51,11 @@ func RunPullsList(cmd *cli.Context) error {
return err return err
} }
print.PullsList(prs, ctx.Output) fields, err := pullFieldsFlag.GetValues(cmd)
if err != nil {
return err
}
print.PullsList(prs, ctx.Output, fields)
return nil return nil
} }

70
cmd/pulls/merge.go Normal file
View File

@ -0,0 +1,70 @@
// 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 pulls
import (
"fmt"
"code.gitea.io/sdk/gitea"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/utils"
"github.com/urfave/cli/v2"
)
// CmdPullsMerge merges a PR
var CmdPullsMerge = cli.Command{
Name: "merge",
Aliases: []string{"m"},
Usage: "Merge a pull request",
Description: "Merge a pull request",
ArgsUsage: "<pull index>",
Flags: append([]cli.Flag{
&cli.StringFlag{
Name: "style",
Aliases: []string{"s"},
Usage: "Kind of merge to perform: merge, rebase, squash, rebase-merge",
Value: "merge",
},
&cli.StringFlag{
Name: "title",
Aliases: []string{"t"},
Usage: "Merge commit title",
},
&cli.StringFlag{
Name: "message",
Aliases: []string{"m"},
Usage: "Merge commit message",
},
}, flags.AllDefaultFlags...),
Action: func(cmd *cli.Context) error {
ctx := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
if ctx.Args().Len() != 1 {
return fmt.Errorf("Must specify a PR index")
}
idx, err := utils.ArgToIndex(ctx.Args().First())
if err != nil {
return err
}
success, _, err := ctx.Login.Client().MergePullRequest(ctx.Owner, ctx.Repo, idx, gitea.MergePullRequestOption{
Style: gitea.MergeStyle(ctx.String("style")),
Title: ctx.String("title"),
Message: ctx.String("message"),
})
if err != nil {
return err
}
if !success {
return fmt.Errorf("Failed to merge PR. Is it still open?")
}
return nil
},
}

View File

@ -19,6 +19,7 @@ var CmdReleases = cli.Command{
Category: catEntities, Category: catEntities,
Usage: "Manage releases", Usage: "Manage releases",
Description: "Manage releases", Description: "Manage releases",
ArgsUsage: " ", // command does not accept arguments
Action: releases.RunReleasesList, Action: releases.RunReleasesList,
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
&releases.CmdReleaseList, &releases.CmdReleaseList,

View File

@ -22,16 +22,17 @@ var CmdReleaseCreate = cli.Command{
Name: "create", Name: "create",
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: "Create a release", Usage: "Create a release",
Description: `Create a release`, Description: `Create a release for a new or existing git tag`,
ArgsUsage: "[<tag>]",
Action: runReleaseCreate, Action: runReleaseCreate,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "tag", Name: "tag",
Usage: "Tag name", Usage: "Tag name. If the tag does not exist yet, it will be created by Gitea",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "target", 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{ &cli.StringFlag{
Name: "title", Name: "title",
@ -56,7 +57,7 @@ var CmdReleaseCreate = cli.Command{
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "asset", Name: "asset",
Aliases: []string{"a"}, Aliases: []string{"a"},
Usage: "List of files to attach", Usage: "Path to file attachment. Can be specified multiple times",
}, },
}, flags.AllDefaultFlags...), }, flags.AllDefaultFlags...),
} }
@ -65,8 +66,16 @@ func runReleaseCreate(cmd *cli.Context) error {
ctx := context.InitCommand(cmd) ctx := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
tag := ctx.String("tag")
if cmd.Args().Present() {
if len(tag) != 0 {
return fmt.Errorf("ambiguous arguments: provide tagname via --tag or argument, but not both")
}
tag = cmd.Args().First()
}
release, resp, err := ctx.Login.Client().CreateRelease(ctx.Owner, ctx.Repo, gitea.CreateReleaseOption{ release, resp, err := ctx.Login.Client().CreateRelease(ctx.Owner, ctx.Repo, gitea.CreateReleaseOption{
TagName: ctx.String("tag"), TagName: tag,
Target: ctx.String("target"), Target: ctx.String("target"),
Title: ctx.String("title"), Title: ctx.String("title"),
Note: ctx.String("note"), Note: ctx.String("note"),

View File

@ -54,10 +54,6 @@ func runReleaseDelete(cmd *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
if release == nil {
return nil
}
_, err = client.DeleteRelease(ctx.Owner, ctx.Repo, release.ID) _, err = client.DeleteRelease(ctx.Owner, ctx.Repo, release.ID)
if err != nil { if err != nil {
return err return err

View File

@ -72,10 +72,6 @@ func runReleaseEdit(cmd *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
if release == nil {
return nil
}
var isDraft, isPre *bool var isDraft, isPre *bool
if ctx.IsSet("draft") { if ctx.IsSet("draft") {
isDraft = gitea.OptionalBool(strings.ToLower(ctx.String("draft"))[:1] == "t") isDraft = gitea.OptionalBool(strings.ToLower(ctx.String("draft"))[:1] == "t")

View File

@ -21,6 +21,7 @@ var CmdReleaseList = cli.Command{
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List Releases", Usage: "List Releases",
Description: "List Releases", Description: "List Releases",
ArgsUsage: " ", // command does not accept arguments
Action: RunReleasesList, Action: RunReleasesList,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&flags.PaginationPageFlag, &flags.PaginationPageFlag,
@ -45,19 +46,19 @@ func RunReleasesList(cmd *cli.Context) error {
} }
func getReleaseByTag(owner, repo, tag string, client *gitea.Client) (*gitea.Release, error) { func getReleaseByTag(owner, repo, tag string, client *gitea.Client) (*gitea.Release, error) {
rl, _, err := client.ListReleases(owner, repo, gitea.ListReleasesOptions{}) rl, _, err := client.ListReleases(owner, repo, gitea.ListReleasesOptions{
ListOptions: gitea.ListOptions{Page: -1},
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(rl) == 0 { if len(rl) == 0 {
fmt.Println("Repo does not have any release") return nil, fmt.Errorf("Repo does not have any release")
return nil, nil
} }
for _, r := range rl { for _, r := range rl {
if r.TagName == tag { if r.TagName == tag {
return r, nil return r, nil
} }
} }
fmt.Println("Release tag does not exist") return nil, fmt.Errorf("Release tag does not exist")
return nil, nil
} }

View File

@ -27,6 +27,8 @@ var CmdRepos = cli.Command{
&repos.CmdReposList, &repos.CmdReposList,
&repos.CmdReposSearch, &repos.CmdReposSearch,
&repos.CmdRepoCreate, &repos.CmdRepoCreate,
&repos.CmdRepoCreateFromTemplate,
&repos.CmdRepoFork,
}, },
Flags: repos.CmdReposListFlags, Flags: repos.CmdReposListFlags,
} }

View File

@ -21,6 +21,7 @@ var CmdRepoCreate = cli.Command{
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: "Create a repository", Usage: "Create a repository",
Description: "Create a repository", Description: "Create a repository",
ArgsUsage: " ", // command does not accept arguments
Action: runRepoCreate, Action: runRepoCreate,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
@ -79,6 +80,14 @@ var CmdRepoCreate = cli.Command{
Required: false, Required: false,
Usage: "use custom default branch (need --init)", Usage: "use custom default branch (need --init)",
}, },
&cli.BoolFlag{
Name: "template",
Usage: "make repo a template repo",
},
&cli.StringFlag{
Name: "trustmodel",
Usage: "select trust model (committer,collaborator,collaborator+committer)",
},
}, flags.LoginOutputFlags...), }, flags.LoginOutputFlags...),
} }
@ -86,9 +95,24 @@ func runRepoCreate(cmd *cli.Context) error {
ctx := context.InitCommand(cmd) ctx := context.InitCommand(cmd)
client := ctx.Login.Client() client := ctx.Login.Client()
var ( var (
repo *gitea.Repository repo *gitea.Repository
err error err error
trustmodel gitea.TrustModel
) )
if ctx.IsSet("trustmodel") {
switch ctx.String("trustmodel") {
case "committer":
trustmodel = gitea.TrustModelCommitter
case "collaborator":
trustmodel = gitea.TrustModelCollaborator
case "collaborator+committer":
trustmodel = gitea.TrustModelCollaboratorCommitter
default:
return fmt.Errorf("unknown trustmodel type '%s'", ctx.String("trustmodel"))
}
}
opts := gitea.CreateRepoOption{ opts := gitea.CreateRepoOption{
Name: ctx.String("name"), Name: ctx.String("name"),
Description: ctx.String("description"), Description: ctx.String("description"),
@ -99,6 +123,8 @@ func runRepoCreate(cmd *cli.Context) error {
License: ctx.String("license"), License: ctx.String("license"),
Readme: ctx.String("readme"), Readme: ctx.String("readme"),
DefaultBranch: ctx.String("branch"), DefaultBranch: ctx.String("branch"),
Template: ctx.Bool("template"),
TrustModel: trustmodel,
} }
if len(ctx.String("owner")) != 0 { if len(ctx.String("owner")) != 0 {
repo, _, err = client.CreateOrgRepo(ctx.String("owner"), opts) repo, _, err = client.CreateOrgRepo(ctx.String("owner"), opts)

View File

@ -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
}

59
cmd/repos/fork.go Normal file
View File

@ -0,0 +1,59 @@
// 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",
ArgsUsage: " ", // command does not accept arguments
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)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
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
}

View File

@ -13,6 +13,10 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var repoFieldsFlag = flags.FieldsFlag(print.RepoFields, []string{
"owner", "name", "type", "ssh",
})
// CmdReposListFlags contains all flags needed for repo listing // CmdReposListFlags contains all flags needed for repo listing
var CmdReposListFlags = append([]cli.Flag{ var CmdReposListFlags = append([]cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
@ -27,9 +31,7 @@ var CmdReposListFlags = append([]cli.Flag{
Required: false, Required: false,
Usage: "List your starred repos instead", Usage: "List your starred repos instead",
}, },
flags.FieldsFlag(print.RepoFields, []string{ repoFieldsFlag,
"owner", "name", "type", "ssh",
}),
&typeFilterFlag, &typeFilterFlag,
&flags.PaginationPageFlag, &flags.PaginationPageFlag,
&flags.PaginationLimitFlag, &flags.PaginationLimitFlag,
@ -82,7 +84,7 @@ func RunReposList(cmd *cli.Context) error {
reposFiltered = filterReposByType(rps, typeFilter) reposFiltered = filterReposByType(rps, typeFilter)
} }
fields, err := flags.GetFields(cmd, print.RepoFields) fields, err := repoFieldsFlag.GetValues(cmd)
if err != nil { if err != nil {
return err return err
} }

View File

@ -50,9 +50,7 @@ var CmdReposSearch = cli.Command{
Required: false, Required: false,
Usage: "Filter archived repos (true|false)", Usage: "Filter archived repos (true|false)",
}, },
flags.FieldsFlag(print.RepoFields, []string{ repoFieldsFlag,
"owner", "name", "type", "ssh",
}),
&flags.PaginationPageFlag, &flags.PaginationPageFlag,
&flags.PaginationLimitFlag, &flags.PaginationLimitFlag,
}, flags.LoginOutputFlags...), }, flags.LoginOutputFlags...),
@ -125,7 +123,7 @@ func runReposSearch(cmd *cli.Context) error {
return err return err
} }
fields, err := flags.GetFields(cmd, nil) fields, err := repoFieldsFlag.GetValues(cmd)
if err != nil { if err != nil {
return err return err
} }

View File

@ -19,6 +19,17 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
// NOTE: not using NewCsvFlag, as we don't want an alias & default value.
var timeFieldsFlag = &flags.CsvFlag{
AvailableFields: print.TrackedTimeFields,
StringFlag: cli.StringFlag{
Name: "fields",
Usage: fmt.Sprintf(`Comma-separated list of fields to print. Available values:
%s
`, strings.Join(print.TrackedTimeFields, ",")),
},
}
// CmdTrackedTimesList represents a sub command of times to list them // CmdTrackedTimesList represents a sub command of times to list them
var CmdTrackedTimesList = cli.Command{ var CmdTrackedTimesList = cli.Command{
Name: "list", Name: "list",
@ -53,12 +64,7 @@ Depending on your permissions on the repository, only your own tracked times mig
Aliases: []string{"m"}, Aliases: []string{"m"},
Usage: "Show all times tracked by you across all repositories (overrides command arguments)", Usage: "Show all times tracked by you across all repositories (overrides command arguments)",
}, },
&cli.StringFlag{ timeFieldsFlag,
Name: "fields",
Usage: fmt.Sprintf(`Comma-separated list of fields to print. Available values:
%s
`, strings.Join(print.TrackedTimeFields, ",")),
},
}, flags.AllDefaultFlags...), }, flags.AllDefaultFlags...),
} }
@ -116,7 +122,7 @@ func RunTimesList(cmd *cli.Context) error {
} }
if ctx.IsSet("fields") { if ctx.IsSet("fields") {
if fields, err = flags.GetFields(cmd, print.TrackedTimeFields); err != nil { if fields, err = timeFieldsFlag.GetValues(cmd); err != nil {
return err return err
} }
} }

28
cmd/whoami.go Normal file
View File

@ -0,0 +1,28 @@
// 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/modules/context"
"code.gitea.io/tea/modules/print"
"github.com/urfave/cli/v2"
)
// CmdWhoami represents the command to show current logged in user
var CmdWhoami = cli.Command{
Name: "whoami",
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
Action: func(cmd *cli.Context) error {
ctx := context.InitCommand(cmd)
client := ctx.Login.Client()
user, _, _ := client.GetMyUserInfo()
print.UserDetails(user)
return nil
},
}

84
go.mod
View File

@ -1,38 +1,68 @@
module code.gitea.io/tea module code.gitea.io/tea
go 1.13 go 1.18
require ( require (
code.gitea.io/gitea-vet v0.2.1 code.gitea.io/gitea-vet v0.2.1
code.gitea.io/sdk/gitea v0.13.1-0.20210304201955-ff82113459b5 code.gitea.io/sdk/gitea v0.15.1-0.20220831004139-a0127ed0e7fe
gitea.com/noerw/unidiff-comments v0.0.0-20201219085024-64aec5658f2b gitea.com/noerw/unidiff-comments v0.0.0-20220822113322-50f4daa0e35c
github.com/AlecAivazis/survey/v2 v2.2.8 github.com/AlecAivazis/survey/v2 v2.3.6
github.com/Microsoft/go-winio v0.4.16 // indirect github.com/adrg/xdg v0.4.0
github.com/adrg/xdg v0.3.1 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e github.com/charmbracelet/glamour v0.5.0
github.com/charmbracelet/glamour v0.2.0 github.com/enescakir/emoji v1.0.0
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/go-git/go-git/v5 v5.4.2
github.com/go-git/go-git/v5 v5.2.0 github.com/muesli/termenv v0.12.0
github.com/imdario/mergo v0.3.11 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/muesli/termenv v0.7.4
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.16.3
github.com/xanzy/ssh-agent v0.3.0 // indirect golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210305034016-7844c3c200c3 // indirect
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
golang.org/x/text v0.3.5 // indirect
golang.org/x/tools v0.1.0 // indirect
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
replace github.com/charmbracelet/glamour => github.com/noerw/glamour v0.2.1-0.20210305125354-f0a29f1de0c2 require (
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/alecthomas/chroma v0.10.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/cloudflare/circl v1.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/microcosm-cc/bluemonday v1.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/xanzy/ssh-agent v0.3.2 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yuin/goldmark v1.4.14 // indirect
github.com/yuin/goldmark-emoji v1.0.1 // indirect
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
golang.org/x/sys v0.0.0-20220913153101-76c7481b5158 // indirect
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

257
go.sum
View File

@ -1,122 +1,132 @@
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s= code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
code.gitea.io/sdk/gitea v0.13.1-0.20210304201955-ff82113459b5 h1:va0KddYHN8bH6MCUaWf5e4p+il55blUw5J0ha5vTMaQ= code.gitea.io/sdk/gitea v0.15.1-0.20220831004139-a0127ed0e7fe h1:PeLyxnUZE85QuJtBZ4P8qCQcgWG5Ked67mlNgr0WkCQ=
code.gitea.io/sdk/gitea v0.13.1-0.20210304201955-ff82113459b5/go.mod h1:89WiyOX1KEcvjP66sRHdu0RafojGo60bT9UqW17VbWs= code.gitea.io/sdk/gitea v0.15.1-0.20220831004139-a0127ed0e7fe/go.mod h1:aRmrQC3CAHdJAU1LQt0C9zqzqI8tUB/5oQtNE746aYE=
gitea.com/noerw/unidiff-comments v0.0.0-20201219085024-64aec5658f2b h1:CLYsMGcGLohESQDMth+RgJ4cB3CCHToxnj0zBbvB3sE= gitea.com/noerw/unidiff-comments v0.0.0-20220822113322-50f4daa0e35c h1:8fTkq2UaVkLHZCF+iB4wTxINmVAToe2geZGayk9LMbA=
gitea.com/noerw/unidiff-comments v0.0.0-20201219085024-64aec5658f2b/go.mod h1:Fc8iyPm4NINRWujeIk2bTfcbGc4ZYY29/oMAAGcr4qI= gitea.com/noerw/unidiff-comments v0.0.0-20220822113322-50f4daa0e35c/go.mod h1:Fc8iyPm4NINRWujeIk2bTfcbGc4ZYY29/oMAAGcr4qI=
github.com/AlecAivazis/survey/v2 v2.2.8 h1:TgxCwybKdBckmC+/P9/5h49rw/nAHe/itZL0dgHs+Q0= github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw=
github.com/AlecAivazis/survey/v2 v2.2.8/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u8fAS/SduGdoPk= github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/adrg/xdg v0.3.1 h1:uIyL9BYfXaFgDyVRKE8wjtm6ETQULweQqTofphEFJYY= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/adrg/xdg v0.3.1/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/alecthomas/chroma v0.8.1 h1:ym20sbvyC6RXz45u4qDglcgr8E313oPROshcuCHqiEE= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/alecthomas/chroma v0.8.1/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e h1:OjdSMCht0ZVX7IH0nTdf00xEustvbtUGRgMh3gbdmOg= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 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/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE= github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec=
github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/enescakir/emoji v1.0.0 h1:W+HsNql8swfCQFtioDGDHCHri8nudlK1n5p2rHCJoog=
github.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkKp+WKFD0=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI= github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs= github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDEq1axMbGg= github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= github.com/microcosm-cc/bluemonday v1.0.20 h1:flpzsq4KU3QIYAYGV/szUat7H+GPOXR0B2JU5A1Wp8Y=
github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/muesli/reflow v0.2.0 h1:2o0UBJPHHH4fa2GCXU4Rg4DwOtWPMekCeyc5EWbAQp0= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.2.0/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVhUqLX8= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8= github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc= github.com/muesli/termenv v0.12.0 h1:KuQRUE3PgxRFWhq4gHvZtPSLCGDqM5q/cYr1pZ39ytc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/noerw/glamour v0.2.1-0.20210305125354-f0a29f1de0c2 h1:ACjOTGUGi7rt3JQU9GIFFs8sueFGShy6GcGjQhMmKjs=
github.com/noerw/glamour v0.2.1-0.20210305125354-f0a29f1de0c2/go.mod h1:WIVFX8Y2VIK1Y/1qtXYL/Vvzqlcbo3VgVop9i2piPkE=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 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/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -125,111 +135,124 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
github.com/seletskiy/tplutil v0.0.0-20200921103632-f880f6245597 h1:nZY1S2jo+VtDrUfjO9XYI137O41hhRkxZNV5Fb5ixCA= github.com/seletskiy/tplutil v0.0.0-20200921103632-f880f6245597 h1:nZY1S2jo+VtDrUfjO9XYI137O41hhRkxZNV5Fb5ixCA=
github.com/seletskiy/tplutil v0.0.0-20200921103632-f880f6245597/go.mod h1:F8CBHSOjnzjx9EeXyWJTAzJyVxN+Y8JH2WjLMn4utiw= github.com/seletskiy/tplutil v0.0.0-20200921103632-f880f6245597/go.mod h1:F8CBHSOjnzjx9EeXyWJTAzJyVxN+Y8JH2WjLMn4utiw=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/xanzy/ssh-agent v0.3.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM=
github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.1 h1:eVwehsLsZlCJCwXyGLgg+Q4iFWE/eTIMG0e8waCmm/I= github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
github.com/yuin/goldmark v1.3.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.14 h1:jwww1XQfhJN7Zm+/a1ZA/3WUiEBEroYFNTiV3dKwM8U=
github.com/yuin/goldmark v1.4.14/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305034016-7844c3c200c3 h1:RdE7htvBru4I4VZQofQjCZk5W9+aLNlSF5n0zgVwm8s= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305034016-7844c3c200c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220913153101-76c7481b5158 h1:XQphkCZeKYaMRSo28HqvvNYuLOoM5CIOOvTZfthvTgI=
golang.org/x/sys v0.0.0-20220913153101-76c7481b5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/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=

39
main.go
View File

@ -8,6 +8,7 @@ package main // import "code.gitea.io/tea"
import ( import (
"fmt" "fmt"
"os" "os"
"runtime"
"strings" "strings"
"code.gitea.io/tea/cmd" "code.gitea.io/tea/cmd"
@ -21,6 +22,9 @@ var Version = "development"
// Tags holds the build tags used // Tags holds the build tags used
var Tags = "" var Tags = ""
// SDK holds the sdk version from go.mod
var SDK = ""
func main() { func main() {
// make parsing tea --version easier, by printing /just/ the version string // make parsing tea --version easier, by printing /just/ the version string
cli.VersionPrinter = func(c *cli.Context) { fmt.Fprintln(c.App.Writer, c.App.Version) } cli.VersionPrinter = func(c *cli.Context) { fmt.Fprintln(c.App.Writer, c.App.Version) }
@ -30,11 +34,12 @@ func main() {
app.Usage = "command line tool to interact with Gitea" app.Usage = "command line tool to interact with Gitea"
app.Description = appDescription app.Description = appDescription
app.CustomAppHelpTemplate = helpTemplate app.CustomAppHelpTemplate = helpTemplate
app.Version = Version + formatBuiltWith(Tags) app.Version = formatVersion()
app.Commands = []*cli.Command{ app.Commands = []*cli.Command{
&cmd.CmdLogin, &cmd.CmdLogin,
&cmd.CmdLogout, &cmd.CmdLogout,
&cmd.CmdAutocomplete, &cmd.CmdAutocomplete,
&cmd.CmdWhoami,
&cmd.CmdIssues, &cmd.CmdIssues,
&cmd.CmdPulls, &cmd.CmdPulls,
@ -48,6 +53,9 @@ func main() {
&cmd.CmdOpen, &cmd.CmdOpen,
&cmd.CmdNotifications, &cmd.CmdNotifications,
&cmd.CmdRepoClone,
&cmd.CmdAdmin,
} }
app.EnableBashCompletion = true app.EnableBashCompletion = true
err := app.Run(os.Args) err := app.Run(os.Args)
@ -59,18 +67,29 @@ func main() {
} }
} }
func formatBuiltWith(Tags string) string { func formatVersion() string {
if len(Tags) == 0 { version := fmt.Sprintf("Version: %s\tgolang: %s",
return "" bold(Version),
strings.ReplaceAll(runtime.Version(), "go", ""))
if len(Tags) != 0 {
version += fmt.Sprintf("\tbuilt with: %s", strings.Replace(Tags, " ", ", ", -1))
} }
return " built with: " + strings.Replace(Tags, " ", ", ", -1) if len(SDK) != 0 {
version += fmt.Sprintf("\tgo-sdk: %s", SDK)
}
return version
} }
var appDescription = `tea is a productivity helper for Gitea. It can be used to manage most entities on one var appDescription = `tea is a productivity helper for Gitea. It can be used to manage most entities on
or multiple Gitea instances and provides local helpers like 'tea pull checkout'. one or multiple Gitea instances & provides local helpers like 'tea pr 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 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(` var helpTemplate = bold(`
@ -108,7 +127,7 @@ var helpTemplate = bold(`
tea open milestones # open web ui for milestones tea open milestones # open web ui for milestones
# send gitea desktop notifications every 5 minutes (bash + libnotify) # send gitea desktop notifications every 5 minutes (bash + libnotify)
while :; do tea notifications --all -o simple | xargs -i notify-send {}; sleep 300; done while :; do tea notifications --mine -o simple | xargs -i notify-send {}; sleep 300; done
ABOUT ABOUT
Written & maintained by The Gitea Authors. Written & maintained by The Gitea Authors.

View File

@ -17,9 +17,26 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
// FlagDefaults defines all flags that can be overridden with a default value
// via the config file
type FlagDefaults struct {
// Prefer a specific git remote to use for selecting a repository on gitea,
// instead of relying on the remote associated with main/master/trunk branch.
// The --remote flag still has precedence over this value.
Remote string `yaml:"remote"`
}
// 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"`
FlagDefaults FlagDefaults `yaml:"flag_defaults"`
}
// LocalConfig represents local configurations // LocalConfig represents local configurations
type LocalConfig struct { type LocalConfig struct {
Logins []Login `yaml:"logins"` Logins []Login `yaml:"logins"`
Prefs Preferences `yaml:"preferences"`
} }
var ( var (
@ -55,6 +72,12 @@ func GetConfigPath() string {
return configFilePath return configFilePath
} }
// GetPreferences returns preferences based on the config file
func GetPreferences() Preferences {
loadConfig()
return config.Prefs
}
// loadConfig load config from file // loadConfig load config from file
func loadConfig() (err error) { func loadConfig() (err error) {
loadConfigOnce.Do(func() { loadConfigOnce.Do(func() {

View File

@ -111,6 +111,25 @@ func GetLoginByToken(token string) *Login {
return nil 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 // DeleteLogin delete a login by name from config
func DeleteLogin(name string) error { func DeleteLogin(name string) error {
var idx = -1 var idx = -1
@ -142,8 +161,9 @@ func AddLogin(login *Login) error {
return saveConfig() return saveConfig()
} }
// Client returns a client to operate Gitea API // Client returns a client to operate Gitea API. You may provide additional modifiers
func (l *Login) Client() *gitea.Client { // for the client like gitea.SetBasicAuth() for customization
func (l *Login) Client(options ...gitea.ClientOption) *gitea.Client {
httpClient := &http.Client{} httpClient := &http.Client{}
if l.Insecure { if l.Insecure {
cookieJar, _ := cookiejar.New(nil) cookieJar, _ := cookiejar.New(nil)
@ -155,10 +175,9 @@ func (l *Login) Client() *gitea.Client {
}} }}
} }
client, err := gitea.NewClient(l.URL, options = append(options, gitea.SetToken(l.Token), gitea.SetHTTPClient(httpClient))
gitea.SetToken(l.Token),
gitea.SetHTTPClient(httpClient), client, err := gitea.NewClient(l.URL, options...)
)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -39,6 +39,9 @@ type TeaContext struct {
func (ctx *TeaContext) GetListOptions() gitea.ListOptions { func (ctx *TeaContext) GetListOptions() gitea.ListOptions {
page := ctx.Int("page") page := ctx.Int("page")
limit := ctx.Int("limit") limit := ctx.Int("limit")
if limit < 0 {
limit = 0
}
if limit != 0 && page == 0 { if limit != 0 && page == 0 {
page = 1 page = 1
} }
@ -96,14 +99,17 @@ func InitCommand(ctx *cli.Context) *TeaContext {
} }
} }
if len(repoFlag) == 0 || repoFlagPathExists { if len(remoteFlag) == 0 {
// try to read git repo & extract context, ignoring if PWD is not a repo remoteFlag = config.GetPreferences().FlagDefaults.Remote
if c.LocalRepo, c.Login, c.RepoSlug, err = contextFromLocalRepo(repoPath, remoteFlag); err != nil { }
if err == errNotAGiteaRepo || err == gogit.ErrRepositoryNotExists {
// we can deal with that, commands needing the optional values use ctx.Ensure() // try to read local git repo & extract context: if repoFlag specifies a valid path, read repo in that dir,
} else { // otherwise attempt PWD. if no repo is found, continue with default login
log.Fatal(err.Error()) if c.LocalRepo, c.Login, c.RepoSlug, err = contextFromLocalRepo(repoPath, remoteFlag); err != nil {
} if err == errNotAGiteaRepo || err == gogit.ErrRepositoryNotExists {
// we can deal with that, commands needing the optional values use ctx.Ensure()
} else {
log.Fatal(err.Error())
} }
} }
@ -120,8 +126,16 @@ func InitCommand(ctx *cli.Context) *TeaContext {
} }
} else if c.Login == nil { } else if c.Login == nil {
if c.Login, err = config.GetDefaultLogin(); err != nil { if c.Login, err = config.GetDefaultLogin(); err != nil {
log.Fatal(err.Error()) if err.Error() == "No available login" {
// TODO: maybe we can directly start interact.CreateLogin() (only if
// we're sure we can interactively!), as gh cli does.
fmt.Println(`No gitea login configured. To start using tea, first run
tea login add
and then run your command again.`)
}
os.Exit(1)
} }
fmt.Printf("NOTE: no gitea login detected, falling back to login '%s'\n", c.Login.Name)
} }
// parse reposlug (owner falling back to login owner if reposlug contains only repo name) // parse reposlug (owner falling back to login owner if reposlug contains only repo name)
@ -143,25 +157,39 @@ func contextFromLocalRepo(repoPath, remoteValue string) (*git.TeaRepo, *config.L
return repo, nil, "", err return repo, nil, "", err
} }
// if no remote
if len(gitConfig.Remotes) == 0 { if len(gitConfig.Remotes) == 0 {
return repo, nil, "", errors.New("No remote(s) found in this Git repository") return repo, nil, "", errNotAGiteaRepo
} }
// if only one remote exists // When no preferred value is given, choose a remote to find a
if len(gitConfig.Remotes) >= 1 && len(remoteValue) == 0 { // matching login based on its URL.
for remote := range gitConfig.Remotes { if len(gitConfig.Remotes) > 1 && len(remoteValue) == 0 {
remoteValue = remote // if master branch is present, use it as the default remote
} mainBranches := []string{"main", "master", "trunk"}
if len(gitConfig.Remotes) > 1 { for _, b := range mainBranches {
// if master branch is present, use it as the default remote masterBranch, ok := gitConfig.Branches[b]
masterBranch, ok := gitConfig.Branches["master"]
if ok { if ok {
if len(masterBranch.Remote) > 0 { if len(masterBranch.Remote) > 0 {
remoteValue = masterBranch.Remote remoteValue = masterBranch.Remote
} }
break
} }
} }
// if no branch has matched, default to origin or upstream remote.
if len(remoteValue) == 0 {
if _, ok := gitConfig.Remotes["upstream"]; ok {
remoteValue = "upstream"
} else if _, ok := gitConfig.Remotes["origin"]; ok {
remoteValue = "origin"
}
}
}
// make sure a remote is selected
if len(remoteValue) == 0 {
for remote := range gitConfig.Remotes {
remoteValue = remote
break
}
} }
remoteConfig, ok := gitConfig.Remotes[remoteValue] remoteConfig, ok := gitConfig.Remotes[remoteValue]
@ -174,8 +202,9 @@ func contextFromLocalRepo(repoPath, remoteValue string) (*git.TeaRepo, *config.L
return repo, nil, "", err return repo, nil, "", err
} }
for _, l := range logins { for _, l := range logins {
sshHost := l.GetSSHHost()
for _, u := range remoteConfig.URLs { for _, u := range remoteConfig.URLs {
p, err := git.ParseURL(strings.TrimSpace(u)) p, err := git.ParseURL(u)
if err != nil { if err != nil {
return repo, nil, "", fmt.Errorf("Git remote URL parse failed: %s", err.Error()) return repo, nil, "", fmt.Errorf("Git remote URL parse failed: %s", err.Error())
} }
@ -186,8 +215,8 @@ func contextFromLocalRepo(repoPath, remoteValue string) (*git.TeaRepo, *config.L
return repo, &l, strings.TrimSuffix(path, ".git"), nil return repo, &l, strings.TrimSuffix(path, ".git"), nil
} }
} else if strings.EqualFold(p.Scheme, "ssh") { } else if strings.EqualFold(p.Scheme, "ssh") {
if l.GetSSHHost() == strings.Split(p.Host, ":")[0] { if sshHost == p.Host {
return repo, &l, strings.TrimLeft(strings.TrimSuffix(p.Path, ".git"), "/"), nil return repo, &l, strings.TrimLeft(p.Path, "/"), nil
} }
} }
} }

View File

@ -55,7 +55,7 @@ func readSSHPrivKey(keyFile string, passwordCallback pwCallback) (sig ssh.Signer
} }
sshKey, err := ioutil.ReadFile(keyFile) sshKey, err := ioutil.ReadFile(keyFile)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("can not read ssh key '%s'", keyFile)
} }
sig, err = ssh.ParsePrivateKey(sshKey) sig, err = ssh.ParsePrivateKey(sshKey)
if _, ok := err.(*ssh.PassphraseMissingError); ok && passwordCallback != nil { if _, ok := err.(*ssh.PassphraseMissingError); ok && passwordCallback != nil {

View File

@ -20,11 +20,20 @@ type URLParser struct {
// Parse parses the git URL // Parse parses the git URL
func (p *URLParser) Parse(rawURL string) (u *url.URL, err error) { func (p *URLParser) Parse(rawURL string) (u *url.URL, err error) {
if !protocolRe.MatchString(rawURL) && rawURL = strings.TrimSpace(rawURL)
strings.Contains(rawURL, ":") &&
// not a Windows path if !protocolRe.MatchString(rawURL) {
!strings.Contains(rawURL, "\\") { // convert the weird git ssh url format to a canonical url:
rawURL = "ssh://" + strings.Replace(rawURL, ":", "/", 1) // 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) u, err = url.Parse(rawURL)

56
modules/git/url_test.go Normal file
View File

@ -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)
}

View File

@ -11,6 +11,7 @@ import (
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
) )
@ -26,7 +27,7 @@ func ShowCommentsMaybeInteractive(ctx *context.TeaContext, idx int64, totalComme
return err return err
} }
print.Comments(comments) print.Comments(comments)
} else if IsInteractive() && !ctx.IsSet("comments") { } else if print.IsInteractive() && !ctx.IsSet("comments") {
// if we're interactive, but --comments hasn't been explicitly set to false // if we're interactive, but --comments hasn't been explicitly set to false
if err := ShowCommentsPaginated(ctx, idx, totalComments); err != nil { if err := ShowCommentsPaginated(ctx, idx, totalComments); err != nil {
fmt.Printf("error while loading comments: %v\n", err) fmt.Printf("error while loading comments: %v\n", err)
@ -69,11 +70,6 @@ func ShowCommentsPaginated(ctx *context.TeaContext, idx int64, totalComments int
return nil return nil
} }
// IsInteractive checks if the output is piped, but NOT if the session is run interactively..
func IsInteractive() bool {
return terminal.IsTerminal(int(os.Stdout.Fd()))
}
// IsStdinPiped checks if stdin is piped // IsStdinPiped checks if stdin is piped
func IsStdinPiped() bool { func IsStdinPiped() bool {
return !terminal.IsTerminal(int(os.Stdin.Fd())) return !terminal.IsTerminal(int(os.Stdin.Fd()))

View File

@ -43,7 +43,12 @@ func promptIssueProperties(login *config.Login, owner, repo string, o *gitea.Cre
} }
// description // 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 { if err = survey.AskOne(promptD, &o.Body); err != nil {
return err return err
} }
@ -60,7 +65,7 @@ func promptIssueProperties(login *config.Login, owner, repo string, o *gitea.Cre
} }
// assignees // assignees
if o.Assignees, err = promptMultiSelect("Assignees:", selectables.Collaborators, "[other]"); err != nil { if o.Assignees, err = promptMultiSelect("Assignees:", selectables.Assignees, "[other]"); err != nil {
return err return err
} }
@ -94,7 +99,7 @@ func promptIssueProperties(login *config.Login, owner, repo string, o *gitea.Cre
type issueSelectables struct { type issueSelectables struct {
Repo *gitea.Repository Repo *gitea.Repository
Collaborators []string Assignees []string
MilestoneList []string MilestoneList []string
MilestoneMap map[string]int64 MilestoneMap map[string]int64
LabelList []string LabelList []string
@ -119,17 +124,15 @@ func fetchIssueSelectables(login *config.Login, owner, repo string, done chan is
return return
} }
// FIXME: this should ideally be ListAssignees(), https://github.com/go-gitea/gitea/issues/14856 assignees, _, err := c.GetAssignees(owner, repo)
colabs, _, err := c.ListCollaborators(owner, repo, gitea.ListCollaboratorsOptions{})
if err != nil { if err != nil {
r.Err = err r.Err = err
done <- r done <- r
return return
} }
r.Collaborators = make([]string, len(colabs)+1) r.Assignees = make([]string, len(assignees))
r.Collaborators[0] = login.User for i, u := range assignees {
for i, u := range colabs { r.Assignees[i] = u.UserName
r.Collaborators[i+1] = u.UserName
} }
milestones, _, err := c.ListRepoMilestones(owner, repo, gitea.ListMilestoneOption{}) milestones, _, err := c.ListRepoMilestones(owner, repo, gitea.ListMilestoneOption{})
@ -145,7 +148,9 @@ func fetchIssueSelectables(login *config.Login, owner, repo string, done chan is
r.MilestoneList[i] = m.Title r.MilestoneList[i] = m.Title
} }
labels, _, err := c.ListRepoLabels(owner, repo, gitea.ListLabelsOptions{}) labels, _, err := c.ListRepoLabels(owner, repo, gitea.ListLabelsOptions{
ListOptions: gitea.ListOptions{Page: -1},
})
if err != nil { if err != nil {
r.Err = err r.Err = err
done <- r done <- r

View File

@ -33,7 +33,11 @@ func CreateMilestone(login *config.Login, owner, repo string) error {
} }
// description // 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 { if err := survey.AskOne(promptM, &description); err != nil {
return err return err
} }

View File

@ -14,9 +14,26 @@ import (
"github.com/araddon/dateparse" "github.com/araddon/dateparse"
) )
// PromptMultiline runs a textfield-style prompt and blocks until input was made. // Multiline represents options for a prompt that expects multiline input
func PromptMultiline(message string) (content string, err error) { type Multiline struct {
err = survey.AskOne(&survey.Multiline{Message: message}, &content) 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 return
} }

View File

@ -6,26 +6,23 @@ package interact
import ( import (
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/git"
"code.gitea.io/tea/modules/task" "code.gitea.io/tea/modules/task"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
) )
// CreatePull interactively creates a PR // CreatePull interactively creates a PR
func CreatePull(login *config.Login, owner, repo string) error { func CreatePull(ctx *context.TeaContext) (err error) {
var base, head string var base, head string
// owner, repo // owner, repo
owner, repo, err := promptRepoSlug(owner, repo) if ctx.Owner, ctx.Repo, err = promptRepoSlug(ctx.Owner, ctx.Repo); err != nil {
if err != nil {
return err return err
} }
// base // base
base, err = task.GetDefaultPRBase(login, owner, repo) if base, err = task.GetDefaultPRBase(ctx.Login, ctx.Owner, ctx.Repo); err != nil {
if err != nil {
return err return err
} }
promptI := &survey.Input{Message: "Target branch:", Default: base} promptI := &survey.Input{Message: "Target branch:", Default: base}
@ -34,14 +31,14 @@ func CreatePull(login *config.Login, owner, repo string) error {
} }
// head // head
localRepo, err := git.RepoForWorkdir() var headOwner, headBranch string
if err != nil {
return err
}
promptOpts := survey.WithValidator(survey.Required) promptOpts := survey.WithValidator(survey.Required)
headOwner, headBranch, err := task.GetDefaultPRHead(localRepo)
if err == nil { if ctx.LocalRepo != nil {
promptOpts = nil headOwner, headBranch, err = task.GetDefaultPRHead(ctx.LocalRepo)
if err == nil {
promptOpts = nil
}
} }
promptI = &survey.Input{Message: "Source repo owner:", Default: headOwner} promptI = &survey.Input{Message: "Source repo owner:", Default: headOwner}
if err := survey.AskOne(promptI, &headOwner); err != nil { if err := survey.AskOne(promptI, &headOwner); err != nil {
@ -52,17 +49,15 @@ func CreatePull(login *config.Login, owner, repo string) error {
return err return err
} }
head = task.GetHeadSpec(headOwner, headBranch, owner) head = task.GetHeadSpec(headOwner, headBranch, ctx.Owner)
opts := gitea.CreateIssueOption{Title: task.GetDefaultPRTitle(head)} opts := gitea.CreateIssueOption{Title: task.GetDefaultPRTitle(head)}
if err = promptIssueProperties(login, owner, repo, &opts); err != nil { if err = promptIssueProperties(ctx.Login, ctx.Owner, ctx.Repo, &opts); err != nil {
return err return err
} }
return task.CreatePull( return task.CreatePull(
login, ctx,
owner,
repo,
base, base,
head, head,
&opts) &opts)

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"os" "os"
"code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/task" "code.gitea.io/tea/modules/task"
@ -52,10 +53,14 @@ func ReviewPull(ctx *context.TeaContext, idx int64) error {
// comment // comment
var promptOpts survey.AskOpt var promptOpts survey.AskOpt
if state == gitea.ReviewStateComment || state == gitea.ReviewStateRequestChanges { if (state == gitea.ReviewStateComment && len(codeComments) == 0) || state == gitea.ReviewStateRequestChanges {
promptOpts = survey.WithValidator(survey.Required) 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 { if err != nil {
return err return err
} }
@ -63,8 +68,9 @@ func ReviewPull(ctx *context.TeaContext, idx int64) error {
return task.CreatePullReview(ctx, idx, state, comment, codeComments) return task.CreatePullReview(ctx, idx, state, comment, codeComments)
} }
// DoDiffReview (1) fetches & saves diff in tempfile, (2) starts $EDITOR to comment on diff, // DoDiffReview (1) fetches & saves diff in tempfile, (2) starts $VISUAL or $EDITOR to comment on diff,
// (3) parses resulting file into code comments. // (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) { func DoDiffReview(ctx *context.TeaContext, idx int64) ([]gitea.CreatePullReviewComment, error) {
tmpFile, err := task.SavePullDiff(ctx, idx) tmpFile, err := task.SavePullDiff(ctx, idx)
if err != nil { if err != nil {

View File

@ -15,7 +15,7 @@ import (
func Comments(comments []*gitea.Comment) { func Comments(comments []*gitea.Comment) {
var baseURL string var baseURL string
if len(comments) != 0 { if len(comments) != 0 {
baseURL = comments[0].HTMLURL baseURL = getRepoURL(comments[0].HTMLURL)
} }
var out = make([]string, len(comments)) var out = make([]string, len(comments))
@ -32,18 +32,18 @@ func Comments(comments []*gitea.Comment) {
// Comment renders a comment to stdout // Comment renders a comment to stdout
func Comment(c *gitea.Comment) { func Comment(c *gitea.Comment) {
outputMarkdown(formatComment(c), c.HTMLURL) outputMarkdown(formatComment(c), getRepoURL(c.HTMLURL))
} }
func formatComment(c *gitea.Comment) string { func formatComment(c *gitea.Comment) string {
edited := "" edited := ""
if c.Updated.After(c.Created) { if c.Updated.After(c.Created) {
edited = fmt.Sprintf(" *(edited on %s)*", FormatTime(c.Updated)) edited = fmt.Sprintf(" *(edited on %s)*", FormatTime(c.Updated, false))
} }
return fmt.Sprintf( return fmt.Sprintf(
"---\n\n**@%s** wrote on %s%s:\n\n%s\n", "---\n\n**@%s** wrote on %s%s:\n\n%s\n",
c.Poster.UserName, c.Poster.UserName,
FormatTime(c.Created), FormatTime(c.Created, false),
edited, edited,
c.Body, c.Body,
) )

View File

@ -6,12 +6,27 @@ package print
import ( import (
"fmt" "fmt"
"os"
"regexp"
"time" "time"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/muesli/termenv" "github.com/muesli/termenv"
"golang.org/x/crypto/ssh/terminal"
) )
// IsInteractive checks if the output is piped, but NOT if the session is run interactively..
func IsInteractive() bool {
return terminal.IsTerminal(int(os.Stdout.Fd()))
}
// captures the repo URL part <host>/<owner>/<repo> 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 // formatSize get kb in int and return string
func formatSize(kb int64) string { func formatSize(kb int64) string {
if kb < 1024 { if kb < 1024 {
@ -28,8 +43,18 @@ func formatSize(kb int64) string {
return fmt.Sprintf("%d Tb", gb/1024) return fmt.Sprintf("%d Tb", gb/1024)
} }
// FormatTime give a date-time in local timezone if available // FormatTime provides a string for the given time value.
func FormatTime(t time.Time) string { // If machineReadable is set, a UTC RFC3339 string is returned,
// otherwise a simplified string in local time is used.
func FormatTime(t time.Time, machineReadable bool) string {
if t.IsZero() {
return ""
}
if machineReadable {
return t.UTC().Format(time.RFC3339)
}
location, err := time.LoadLocation("Local") location, err := time.LoadLocation("Local")
if err != nil { if err != nil {
return t.Format("2006-01-02 15:04 UTC") return t.Format("2006-01-02 15:04 UTC")
@ -72,3 +97,16 @@ func formatUserName(u *gitea.User) string {
} }
return u.FullName return u.FullName
} }
func formatBoolean(b bool, allowIcons bool) string {
if !allowIcons {
return fmt.Sprintf("%v", b)
}
styled := "✔"
if !b {
styled = "✖"
}
return styled
}

View File

@ -9,19 +9,40 @@ import (
"strings" "strings"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/enescakir/emoji"
) )
// IssueDetails print an issue rendered to stdout // IssueDetails print an issue rendered to stdout
func IssueDetails(issue *gitea.Issue) { func IssueDetails(issue *gitea.Issue, reactions []*gitea.Reaction) {
outputMarkdown(fmt.Sprintf( out := fmt.Sprintf(
"# #%d %s (%s)\n@%s created %s\n\n%s\n", "# #%d %s (%s)\n@%s created %s\n\n%s\n",
issue.Index, issue.Index,
issue.Title, issue.Title,
issue.State, issue.State,
issue.Poster.UserName, issue.Poster.UserName,
FormatTime(issue.Created), FormatTime(issue.Created, false),
issue.Body, issue.Body,
), issue.HTMLURL) )
if len(reactions) > 0 {
out += fmt.Sprintf("\n---\n\n%s\n", formatReactions(reactions))
}
outputMarkdown(out, getRepoURL(issue.HTMLURL))
}
func formatReactions(reactions []*gitea.Reaction) string {
reactionCounts := make(map[string]uint16)
for _, r := range reactions {
reactionCounts[r.Reaction]++
}
reactionStrings := make([]string, 0, len(reactionCounts))
for reaction, count := range reactionCounts {
reactionStrings = append(reactionStrings, fmt.Sprintf("%dx :%s:", count, reaction))
}
return emoji.Parse(strings.Join(reactionStrings, " | "))
} }
// IssuesPullsList prints a listing of issues & pulls // IssuesPullsList prints a listing of issues & pulls
@ -54,19 +75,20 @@ var IssueFields = []string{
func printIssues(issues []*gitea.Issue, output string, fields []string) { func printIssues(issues []*gitea.Issue, output string, fields []string) {
labelMap := map[int64]string{} labelMap := map[int64]string{}
var printables = make([]printable, len(issues)) var printables = make([]printable, len(issues))
machineReadable := isMachineReadable(output)
for i, x := range issues { for i, x := range issues {
// pre-serialize labels for performance // pre-serialize labels for performance
for _, label := range x.Labels { for _, label := range x.Labels {
if _, ok := labelMap[label.ID]; !ok { if _, ok := labelMap[label.ID]; !ok {
labelMap[label.ID] = formatLabel(label, !isMachineReadable(output), "") labelMap[label.ID] = formatLabel(label, !machineReadable, "")
} }
} }
// store items with printable interface // store items with printable interface
printables[i] = &printableIssue{x, &labelMap} printables[i] = &printableIssue{x, &labelMap}
} }
t := tableFromItems(fields, printables) t := tableFromItems(fields, printables, machineReadable)
t.print(output) t.print(output)
} }
@ -75,7 +97,7 @@ type printableIssue struct {
formattedLabels *map[int64]string formattedLabels *map[int64]string
} }
func (x printableIssue) FormatField(field string) string { func (x printableIssue) FormatField(field string, machineReadable bool) string {
switch field { switch field {
case "index": case "index":
return fmt.Sprintf("%d", x.Index) return fmt.Sprintf("%d", x.Index)
@ -97,11 +119,14 @@ func (x printableIssue) FormatField(field string) string {
case "body": case "body":
return x.Body return x.Body
case "created": case "created":
return FormatTime(x.Created) return FormatTime(x.Created, machineReadable)
case "updated": case "updated":
return FormatTime(x.Updated) return FormatTime(x.Updated, machineReadable)
case "deadline": case "deadline":
return FormatTime(*x.Deadline) if x.Deadline == nil {
return ""
}
return FormatTime(*x.Deadline, machineReadable)
case "milestone": case "milestone":
if x.Milestone != nil { if x.Milestone != nil {
return x.Milestone.Title return x.Milestone.Title

View File

@ -16,9 +16,17 @@ import (
// If the input could not be parsed, it is printed unformatted, the error // If the input could not be parsed, it is printed unformatted, the error
// is returned anyway. // is returned anyway.
func outputMarkdown(markdown string, baseURL string) error { func outputMarkdown(markdown string, baseURL string) error {
var styleOption glamour.TermRendererOption
if IsInteractive() {
styleOption = glamour.WithAutoStyle()
} else {
styleOption = glamour.WithStandardStyle("notty")
}
renderer, err := glamour.NewTermRenderer( renderer, err := glamour.NewTermRenderer(
glamour.WithAutoStyle(), styleOption,
glamour.WithBaseURL(baseURL), glamour.WithBaseURL(baseURL),
glamour.WithPreservedNewLines(),
glamour.WithWordWrap(getWordWrap()), glamour.WithWordWrap(getWordWrap()),
) )
if err != nil { if err != nil {

View File

@ -19,45 +19,70 @@ func MilestoneDetails(milestone *gitea.Milestone) {
fmt.Printf("\n%s\n", milestone.Description) fmt.Printf("\n%s\n", milestone.Description)
} }
if milestone.Deadline != nil && !milestone.Deadline.IsZero() { if milestone.Deadline != nil && !milestone.Deadline.IsZero() {
fmt.Printf("\nDeadline: %s\n", FormatTime(*milestone.Deadline)) fmt.Printf("\nDeadline: %s\n", FormatTime(*milestone.Deadline, false))
} }
} }
// MilestonesList prints a listing of milestones // MilestonesList prints a listing of milestones
func MilestonesList(miles []*gitea.Milestone, output string, state gitea.StateType) { func MilestonesList(news []*gitea.Milestone, output string, fields []string) {
headers := []string{ var printables = make([]printable, len(news))
"Title", for i, x := range news {
printables[i] = &printableMilestone{x}
} }
if state == gitea.StateAll { t := tableFromItems(fields, printables, isMachineReadable(output))
headers = append(headers, "State")
}
headers = append(headers,
"Open/Closed Issues",
"DueDate",
)
t := table{headers: headers}
for _, m := range miles {
var deadline = ""
if m.Deadline != nil && !m.Deadline.IsZero() {
deadline = FormatTime(*m.Deadline)
}
item := []string{
m.Title,
}
if state == gitea.StateAll {
item = append(item, string(m.State))
}
item = append(item,
fmt.Sprintf("%d/%d", m.OpenIssues, m.ClosedIssues),
deadline,
)
t.addRowSlice(item)
}
t.sort(0, true) t.sort(0, true)
t.print(output) t.print(output)
} }
// MilestoneFields are all available fields to print with MilestonesList
var MilestoneFields = []string{
"title",
"state",
"items_open",
"items_closed",
"items",
"duedate",
"description",
"created",
"updated",
"closed",
"id",
}
type printableMilestone struct {
*gitea.Milestone
}
func (m printableMilestone) FormatField(field string, machineReadable bool) string {
switch field {
case "title":
return m.Title
case "state":
return string(m.State)
case "items_open":
return fmt.Sprintf("%d", m.OpenIssues)
case "items_closed":
return fmt.Sprintf("%d", m.ClosedIssues)
case "items":
return fmt.Sprintf("%d/%d", m.OpenIssues, m.ClosedIssues)
case "duedate":
if m.Deadline != nil && !m.Deadline.IsZero() {
return FormatTime(*m.Deadline, machineReadable)
}
case "id":
return fmt.Sprintf("%d", m.ID)
case "description":
return m.Description
case "created":
return FormatTime(m.Created, machineReadable)
case "updated":
if m.Updated != nil {
return FormatTime(*m.Updated, machineReadable)
}
case "closed":
if m.Closed != nil {
return FormatTime(*m.Closed, machineReadable)
}
}
return ""
}

View File

@ -5,29 +5,58 @@
package print package print
import ( import (
"fmt"
"strings" "strings"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
) )
// NotificationsList prints a listing of notification threads // NotificationsList prints a listing of notification threads
func NotificationsList(news []*gitea.NotificationThread, output string, showRepository bool) { func NotificationsList(news []*gitea.NotificationThread, output string, fields []string) {
headers := []string{ var printables = make([]printable, len(news))
"Type", for i, x := range news {
"Index", printables[i] = &printableNotification{x}
"Title",
}
if showRepository {
headers = append(headers, "Repository")
} }
t := tableFromItems(fields, printables, isMachineReadable(output))
t.print(output)
}
t := table{headers: headers} // NotificationFields are all available fields to print with NotificationsList
var NotificationFields = []string{
"id",
"status",
"updated",
for _, n := range news { // these are about the notification subject
if n.Subject == nil { "index",
continue "type",
"state",
"title",
"repository",
}
type printableNotification struct {
*gitea.NotificationThread
}
func (n printableNotification) FormatField(field string, machineReadable bool) string {
switch field {
case "id":
return fmt.Sprintf("%d", n.ID)
case "status":
status := "read"
if n.Pinned {
status = "pinned"
} else if n.Unread {
status = "unread"
} }
// if pull or Issue get Index return status
case "updated":
return FormatTime(n.UpdatedAt, machineReadable)
case "index":
var index string var index string
if n.Subject.Type == "Issue" || n.Subject.Type == "Pull" { if n.Subject.Type == "Issue" || n.Subject.Type == "Pull" {
index = n.Subject.URL index = n.Subject.URL
@ -35,17 +64,20 @@ func NotificationsList(news []*gitea.NotificationThread, output string, showRepo
if len(urlParts) != 0 { if len(urlParts) != 0 {
index = urlParts[len(urlParts)-1] index = urlParts[len(urlParts)-1]
} }
index = "#" + index
} }
return index
item := []string{n.Subject.Type, index, n.Subject.Title} case "type":
if showRepository { return string(n.Subject.Type)
item = append(item, n.Repository.FullName)
}
t.addRowSlice(item)
}
if t.Len() != 0 { case "state":
t.print(output) return string(n.Subject.State)
case "title":
return n.Subject.Title
case "repo", "repository":
return n.Repository.FullName
} }
return ""
} }

View File

@ -10,6 +10,18 @@ import (
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
) )
// OrganizationDetails prints details of an org with formatting
func OrganizationDetails(org *gitea.Organization) {
outputMarkdown(fmt.Sprintf(
"# %s\n%s\n\n- Visibility: %s\n- Location: %s\n- Website: %s\n",
org.UserName,
org.Description,
org.Visibility,
org.Location,
org.Website,
), "")
}
// OrganizationsList prints a listing of the organizations // OrganizationsList prints a listing of the organizations
func OrganizationsList(organizations []*gitea.Organization, output string) { func OrganizationsList(organizations []*gitea.Organization, output string) {
if len(organizations) == 0 { if len(organizations) == 0 {

View File

@ -6,7 +6,6 @@ package print
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
@ -23,19 +22,8 @@ var ciStatusSymbols = map[gitea.StatusState]string{
// PullDetails print an pull rendered to stdout // PullDetails print an pull rendered to stdout
func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview, ciStatus *gitea.CombinedStatus) { func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview, ciStatus *gitea.CombinedStatus) {
base := pr.Base.Name base := pr.Base.Name
head := pr.Head.Name head := formatPRHead(pr)
if pr.Head.RepoID != pr.Base.RepoID { state := formatPRState(pr)
if pr.Head.Repository != nil {
head = pr.Head.Repository.Owner.UserName + ":" + head
} else {
head = "delete:" + head
}
}
state := pr.State
if pr.Merged != nil {
state = "merged"
}
out := fmt.Sprintf( out := fmt.Sprintf(
"# #%d %s (%s)\n@%s created %s\t**%s** <- **%s**\n\n%s\n\n", "# #%d %s (%s)\n@%s created %s\t**%s** <- **%s**\n\n%s\n\n",
@ -43,7 +31,7 @@ func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview, ciStatus *g
pr.Title, pr.Title,
state, state,
pr.Poster.UserName, pr.Poster.UserName,
FormatTime(*pr.Created), FormatTime(*pr.Created, false),
base, base,
head, head,
pr.Body, pr.Body,
@ -76,7 +64,26 @@ 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 {
head := pr.Head.Name
if pr.Head.RepoID != pr.Base.RepoID {
if pr.Head.Repository != nil {
head = pr.Head.Repository.Owner.UserName + ":" + head
} else {
head = "delete:" + head
}
}
return head
}
func formatPRState(pr *gitea.PullRequest) string {
if pr.Merged != nil {
return "merged"
}
return string(pr.State)
} }
func formatReviews(reviews []*gitea.PullReview) string { func formatReviews(reviews []*gitea.PullReview) string {
@ -114,37 +121,120 @@ func formatReviews(reviews []*gitea.PullReview) string {
} }
// PullsList prints a listing of pulls // PullsList prints a listing of pulls
func PullsList(prs []*gitea.PullRequest, output string) { func PullsList(prs []*gitea.PullRequest, output string, fields []string) {
t := tableWithHeader( printPulls(prs, output, fields)
"Index", }
"Title",
"State",
"Author",
"Milestone",
"Updated",
)
for _, pr := range prs { // PullFields are all available fields to print with PullsList()
if pr == nil { var PullFields = []string{
continue "index",
"state",
"author",
"author-id",
"url",
"title",
"body",
"mergeable",
"base",
"base-commit",
"head",
"diff",
"patch",
"created",
"updated",
"deadline",
"assignees",
"milestone",
"labels",
"comments",
}
func printPulls(pulls []*gitea.PullRequest, output string, fields []string) {
labelMap := map[int64]string{}
var printables = make([]printable, len(pulls))
machineReadable := isMachineReadable(output)
for i, x := range pulls {
// pre-serialize labels for performance
for _, label := range x.Labels {
if _, ok := labelMap[label.ID]; !ok {
labelMap[label.ID] = formatLabel(label, !machineReadable, "")
}
} }
author := pr.Poster.FullName // store items with printable interface
if len(author) == 0 { printables[i] = &printablePull{x, &labelMap}
author = pr.Poster.UserName
}
mile := ""
if pr.Milestone != nil {
mile = pr.Milestone.Title
}
t.addRow(
strconv.FormatInt(pr.Index, 10),
pr.Title,
string(pr.State),
author,
mile,
FormatTime(*pr.Updated),
)
} }
t := tableFromItems(fields, printables, machineReadable)
t.print(output) t.print(output)
} }
type printablePull struct {
*gitea.PullRequest
formattedLabels *map[int64]string
}
func (x printablePull) FormatField(field string, machineReadable bool) string {
switch field {
case "index":
return fmt.Sprintf("%d", x.Index)
case "state":
return formatPRState(x.PullRequest)
case "author":
return formatUserName(x.Poster)
case "author-id":
return x.Poster.UserName
case "url":
return x.HTMLURL
case "title":
return x.Title
case "body":
return x.Body
case "created":
return FormatTime(*x.Created, machineReadable)
case "updated":
return FormatTime(*x.Updated, machineReadable)
case "deadline":
if x.Deadline == nil {
return ""
}
return FormatTime(*x.Deadline, machineReadable)
case "milestone":
if x.Milestone != nil {
return x.Milestone.Title
}
return ""
case "labels":
var labels = make([]string, len(x.Labels))
for i, l := range x.Labels {
labels[i] = (*x.formattedLabels)[l.ID]
}
return strings.Join(labels, " ")
case "assignees":
var assignees = make([]string, len(x.Assignees))
for i, a := range x.Assignees {
assignees[i] = formatUserName(a)
}
return strings.Join(assignees, " ")
case "comments":
return fmt.Sprintf("%d", x.Comments)
case "mergeable":
isMergeable := x.Mergeable && x.State == gitea.StateOpen
return formatBoolean(isMergeable, !machineReadable)
case "base":
return x.Base.Ref
case "base-commit":
return x.MergeBase
case "head":
return formatPRHead(x.PullRequest)
case "diff":
return x.DiffURL
case "patch":
return x.PatchURL
}
return ""
}

View File

@ -28,7 +28,7 @@ func ReleasesList(releases []*gitea.Release, output string) {
t.addRow( t.addRow(
release.TagName, release.TagName,
release.Title, release.Title,
FormatTime(release.PublishedAt), FormatTime(release.PublishedAt, isMachineReadable(output)),
status, status,
release.TarURL, release.TarURL,
) )

View File

@ -18,7 +18,7 @@ func ReposList(repos []*gitea.Repository, output string, fields []string) {
for i, r := range repos { for i, r := range repos {
printables[i] = &printableRepo{r} printables[i] = &printableRepo{r}
} }
t := tableFromItems(fields, printables) t := tableFromItems(fields, printables, isMachineReadable(output))
t.print(output) t.print(output)
} }
@ -107,7 +107,7 @@ var RepoFields = []string{
type printableRepo struct{ *gitea.Repository } type printableRepo struct{ *gitea.Repository }
func (x printableRepo) FormatField(field string) string { func (x printableRepo) FormatField(field string, machineReadable bool) string {
switch field { switch field {
case "description": case "description":
return x.Description return x.Description
@ -124,7 +124,7 @@ func (x printableRepo) FormatField(field string) string {
case "ssh": case "ssh":
return x.SSHURL return x.SSHURL
case "updated": case "updated":
return FormatTime(x.Updated) return FormatTime(x.Updated, machineReadable)
case "url": case "url":
return x.HTMLURL return x.HTMLURL
case "permission": case "permission":

View File

@ -24,16 +24,16 @@ type table struct {
// printable can be implemented for structs to put fields dynamically into a table // printable can be implemented for structs to put fields dynamically into a table
type printable interface { type printable interface {
FormatField(field string) string FormatField(field string, machineReadable bool) string
} }
// high level api to print a table of items with dynamic fields // high level api to print a table of items with dynamic fields
func tableFromItems(fields []string, values []printable) table { func tableFromItems(fields []string, values []printable, machineReadable bool) table {
t := table{headers: fields} t := table{headers: fields}
for _, v := range values { for _, v := range values {
row := make([]string, len(fields)) row := make([]string, len(fields))
for i, f := range fields { for i, f := range fields {
row[i] = v.FormatField(f) row[i] = v.FormatField(f, machineReadable)
} }
t.addRowSlice(row) t.addRowSlice(row)
} }
@ -140,7 +140,7 @@ func outputyaml(headers []string, values [][]string) {
func isMachineReadable(outputFormat string) bool { func isMachineReadable(outputFormat string) bool {
switch outputFormat { switch outputFormat {
case "yml", "yaml", "csv": case "yml", "yaml", "csv", "tsv":
return true return true
} }
return false return false

View File

@ -18,7 +18,7 @@ func TrackedTimesList(times []*gitea.TrackedTime, outputType string, fields []st
totalDuration += t.Time totalDuration += t.Time
printables[i] = &printableTrackedTime{t, outputType} printables[i] = &printableTrackedTime{t, outputType}
} }
t := tableFromItems(fields, printables) t := tableFromItems(fields, printables, isMachineReadable(outputType))
if printTotal { if printTotal {
total := make([]string, len(fields)) total := make([]string, len(fields))
@ -45,12 +45,12 @@ type printableTrackedTime struct {
outputFormat string outputFormat string
} }
func (t printableTrackedTime) FormatField(field string) string { func (t printableTrackedTime) FormatField(field string, machineReadable bool) string {
switch field { switch field {
case "id": case "id":
return fmt.Sprintf("%d", t.ID) return fmt.Sprintf("%d", t.ID)
case "created": case "created":
return FormatTime(t.Created) return FormatTime(t.Created, machineReadable)
case "repo": case "repo":
return t.Issue.Repository.FullName return t.Issue.Repository.FullName
case "issue": case "issue":

135
modules/print/user.go Normal file
View File

@ -0,0 +1,135 @@
// 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 print
import (
"fmt"
"code.gitea.io/sdk/gitea"
)
// UserDetails print a formatted user to stdout
func UserDetails(user *gitea.User) {
title := "# " + user.UserName
if user.IsAdmin {
title += " (admin)"
}
if !user.IsActive {
title += " (disabled)"
}
if user.Restricted {
title += " (restricted)"
}
if user.ProhibitLogin {
title += " (login prohibited)"
}
title += "\n"
var desc string
if len(user.Description) != 0 {
desc = fmt.Sprintf("*%s*\n\n", user.Description)
}
var website string
if len(user.Website) != 0 {
website = fmt.Sprintf("%s\n\n", user.Website)
}
stats := fmt.Sprintf(
"Follower Count: %d, Following Count: %d, Starred Repos: %d\n",
user.FollowerCount,
user.FollowingCount,
user.StarredRepoCount,
)
outputMarkdown(fmt.Sprintf(
"%s%s\n%s\n%s",
title,
desc,
website,
stats,
), "")
}
// UserList prints a listing of the users
func UserList(user []*gitea.User, output string, fields []string) {
var printables = make([]printable, len(user))
for i, u := range user {
printables[i] = &printableUser{u}
}
t := tableFromItems(fields, printables, isMachineReadable(output))
t.print(output)
}
// UserFields are the available fields to print with UserList()
var UserFields = []string{
"id",
"login",
"full_name",
"email",
"avatar_url",
"language",
"is_admin",
"restricted",
"prohibit_login",
"location",
"website",
"description",
"visibility",
"activated",
"lastlogin_at",
"created_at",
}
type printableUser struct{ *gitea.User }
func (x printableUser) FormatField(field string, machineReadable bool) string {
switch field {
case "id":
return fmt.Sprintf("%d", x.ID)
case "login":
if x.IsAdmin {
return fmt.Sprintf("%s (admin)", x.UserName)
}
if !x.IsActive {
return fmt.Sprintf("%s (disabled)", x.UserName)
}
if x.Restricted {
return fmt.Sprintf("%s (restricted)", x.UserName)
}
if x.ProhibitLogin {
return fmt.Sprintf("%s (login prohibited)", x.UserName)
}
return x.UserName
case "full_name":
return x.FullName
case "email":
return x.Email
case "avatar_url":
return x.AvatarURL
case "language":
return x.Language
case "is_admin":
return formatBoolean(x.IsAdmin, !machineReadable)
case "restricted":
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":
return x.Website
case "description":
return x.Description
case "visibility":
return string(x.Visibility)
case "created_at":
return FormatTime(x.Created, machineReadable)
case "lastlogin_at":
return FormatTime(x.LastLogin, machineReadable)
}
return ""
}

View File

@ -25,7 +25,7 @@ func CreateIssue(login *config.Login, repoOwner, repoName string, opts gitea.Cre
return fmt.Errorf("could not create issue: %s", err) return fmt.Errorf("could not create issue: %s", err)
} }
print.IssueDetails(issue) print.IssueDetails(issue, nil)
fmt.Println(issue.HTMLURL) fmt.Println(issue.HTMLURL)

View File

@ -12,7 +12,9 @@ import (
// ResolveLabelNames returns a list of label IDs for a given list of label names // ResolveLabelNames returns a list of label IDs for a given list of label names
func ResolveLabelNames(client *gitea.Client, owner, repo string, labelNames []string) ([]int64, error) { func ResolveLabelNames(client *gitea.Client, owner, repo string, labelNames []string) ([]int64, error) {
labelIDs := make([]int64, len(labelNames)) labelIDs := make([]int64, len(labelNames))
labels, _, err := client.ListRepoLabels(owner, repo, gitea.ListLabelsOptions{}) labels, _, err := client.ListRepoLabels(owner, repo, gitea.ListLabelsOptions{
ListOptions: gitea.ListOptions{Page: -1},
})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -56,14 +56,14 @@ func CreateLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bo
Created: time.Now().Unix(), Created: time.Now().Unix(),
} }
client := login.Client()
if len(token) == 0 { if len(token) == 0 {
if login.Token, err = generateToken(client, user, passwd); err != nil { if login.Token, err = generateToken(login, user, passwd); err != nil {
return err return err
} }
} }
client := login.Client()
// Verify if authentication works and get user info // Verify if authentication works and get user info
u, _, err := client.GetMyUserInfo() u, _, err := client.GetMyUserInfo()
if err != nil { if err != nil {
@ -98,16 +98,19 @@ func CreateLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bo
} }
// generateToken creates a new token when given BasicAuth credentials // generateToken creates a new token when given BasicAuth credentials
func generateToken(client *gitea.Client, user, pass string) (string, error) { func generateToken(login config.Login, user, pass string) (string, error) {
gitea.SetBasicAuth(user, pass)(client) client := login.Client(gitea.SetBasicAuth(user, pass))
host, _ := os.Hostname() tl, _, err := client.ListAccessTokens(gitea.ListAccessTokensOptions{
tl, _, err := client.ListAccessTokens(gitea.ListAccessTokensOptions{}) ListOptions: gitea.ListOptions{Page: -1},
})
if err != nil { if err != nil {
return "", err return "", err
} }
host, _ := os.Hostname()
tokenName := host + "-tea" tokenName := host + "-tea"
// append timestamp, if a token with this hostname already exists
for i := range tl { for i := range tl {
if tl[i].Name == tokenName { if tl[i].Name == tokenName {
tokenName += time.Now().Format("2006-01-02_15-04-05") tokenName += time.Now().Format("2006-01-02_15-04-05")

View File

@ -20,7 +20,9 @@ import (
// a matching private key in ~/.ssh/. If no match is found, path is empty. // a matching private key in ~/.ssh/. If no match is found, path is empty.
func findSSHKey(client *gitea.Client) (string, error) { func findSSHKey(client *gitea.Client) (string, error) {
// get keys registered on gitea instance // get keys registered on gitea instance
keys, _, err := client.ListMyPublicKeys(gitea.ListPublicKeysOptions{}) keys, _, err := client.ListMyPublicKeys(gitea.ListPublicKeysOptions{
ListOptions: gitea.ListOptions{Page: -1},
})
if err != nil || len(keys) == 0 { if err != nil || len(keys) == 0 {
return "", err return "", err
} }

View File

@ -28,7 +28,7 @@ func PullCheckout(
client := login.Client() client := login.Client()
pr, _, err := client.GetPullRequest(repoOwner, repoName, index) pr, _, err := client.GetPullRequest(repoOwner, repoName, index)
if err != nil { if err != nil {
return err return fmt.Errorf("couldn't fetch PR: %s", err)
} }
if err := workaround.FixPullHeadSha(client, pr); err != nil { if err := workaround.FixPullHeadSha(client, pr); err != nil {
return err return err

View File

@ -10,22 +10,17 @@ import (
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/context"
local_git "code.gitea.io/tea/modules/git" local_git "code.gitea.io/tea/modules/git"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"code.gitea.io/tea/modules/utils" "code.gitea.io/tea/modules/utils"
) )
// CreatePull creates a PR in the given repo and prints the result // CreatePull creates a PR in the given repo and prints the result
func CreatePull(login *config.Login, repoOwner, repoName, base, head string, opts *gitea.CreateIssueOption) error { func CreatePull(ctx *context.TeaContext, base, head string, opts *gitea.CreateIssueOption) (err error) {
// open local git repo
localRepo, err := local_git.RepoForWorkdir()
if err != nil {
return fmt.Errorf("Could not open local repo: %s", err)
}
// default is default branch // default is default branch
if len(base) == 0 { if len(base) == 0 {
base, err = GetDefaultPRBase(login, repoOwner, repoName) base, err = GetDefaultPRBase(ctx.Login, ctx.Owner, ctx.Repo)
if err != nil { if err != nil {
return err return err
} }
@ -33,12 +28,15 @@ func CreatePull(login *config.Login, repoOwner, repoName, base, head string, opt
// default is current one // default is current one
if len(head) == 0 { if len(head) == 0 {
headOwner, headBranch, err := GetDefaultPRHead(localRepo) if ctx.LocalRepo == nil {
return fmt.Errorf("no local git repo detected, please specify head branch")
}
headOwner, headBranch, err := GetDefaultPRHead(ctx.LocalRepo)
if err != nil { if err != nil {
return err return err
} }
head = GetHeadSpec(headOwner, headBranch, repoOwner) head = GetHeadSpec(headOwner, headBranch, ctx.Owner)
} }
// head & base may not be the same // head & base may not be the same
@ -52,10 +50,10 @@ func CreatePull(login *config.Login, repoOwner, repoName, base, head string, opt
} }
// title is required // title is required
if len(opts.Title) == 0 { if len(opts.Title) == 0 {
return fmt.Errorf("Title is required") return fmt.Errorf("title is required")
} }
pr, _, err := login.Client().CreatePullRequest(repoOwner, repoName, gitea.CreatePullRequestOption{ pr, _, err := ctx.Login.Client().CreatePullRequest(ctx.Owner, ctx.Repo, gitea.CreatePullRequestOption{
Head: head, Head: head,
Base: base, Base: base,
Title: opts.Title, Title: opts.Title,
@ -67,7 +65,7 @@ func CreatePull(login *config.Login, repoOwner, repoName, base, head string, opt
}) })
if err != nil { if err != nil {
return fmt.Errorf("Could not create PR from %s to %s:%s: %s", head, repoOwner, base, err) return fmt.Errorf("could not create PR from %s to %s:%s: %s", head, ctx.Owner, base, err)
} }
print.PullDetails(pr, nil, nil) print.PullDetails(pr, nil, nil)
@ -110,7 +108,7 @@ func GetDefaultPRHead(localRepo *local_git.TeaRepo) (owner, branch string, err e
if err != nil { if err != nil {
return return
} }
owner, _ = utils.GetOwnerAndRepo(strings.TrimLeft(url.Path, "/"), "") owner, _ = utils.GetOwnerAndRepo(url.Path, "")
return return
} }

View File

@ -49,7 +49,7 @@ func CreatePullReview(ctx *context.TeaContext, idx int64, status gitea.ReviewSta
// SavePullDiff fetches the diff of a pull request and stores it as a temporary file. // SavePullDiff fetches the diff of a pull request and stores it as a temporary file.
// The path to the file is returned. // The path to the file is returned.
func SavePullDiff(ctx *context.TeaContext, idx int64) (string, error) { func SavePullDiff(ctx *context.TeaContext, idx int64) (string, error) {
diff, _, err := ctx.Login.Client().GetPullRequestDiff(ctx.Owner, ctx.Repo, idx) diff, _, err := ctx.Login.Client().GetPullRequestDiff(ctx.Owner, ctx.Repo, idx, gitea.PullRequestDiffOptions{})
if err != nil { if err != nil {
return "", err return "", err
} }
@ -107,10 +107,13 @@ func ParseDiffComments(diffFile string) ([]gitea.CreatePullReviewComment, error)
// OpenFileInEditor opens filename in a text editor, and blocks until the editor terminates. // OpenFileInEditor opens filename in a text editor, and blocks until the editor terminates.
func OpenFileInEditor(filename string) error { func OpenFileInEditor(filename string) error {
editor := os.Getenv("EDITOR") editor := os.Getenv("VISUAL")
if editor == "" { if editor == "" {
fmt.Println("No $EDITOR env is set, defaulting to vim") editor = os.Getenv("EDITOR")
editor = "vim" if editor == "" {
fmt.Println("No $VISUAL or $EDITOR env is set, defaulting to vim")
editor = "vi"
}
} }
// Get the full executable path for the editor. // Get the full executable path for the editor.

View File

@ -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)
}

View File

@ -33,7 +33,7 @@ func GetOwnerAndRepo(repoPath, user string) (string, string) {
if len(repoPath) == 0 { if len(repoPath) == 0 {
return "", "" return "", ""
} }
p := strings.Split(repoPath, "/") p := strings.Split(strings.TrimLeft(repoPath, "/"), "/")
if len(p) >= 2 { if len(p) >= 2 {
return p[0], p[1] return p[0], p[1]
} }

View File

@ -1,30 +0,0 @@
# The full repository name
repo: gitea/gitea-vet
# Service type (gitea or github)
service: gitea
# Base URL for Gitea instance if using gitea service type (optional)
base-url: https://gitea.com
# Changelog groups and which labeled PRs to add to each group
groups:
-
name: BREAKING
labels:
- breaking
-
name: FEATURES
labels:
- feature
-
name: BUGFIXES
labels:
- bug
-
name: ENHANCEMENTS
labels:
- enhancement
# regex indicating which labels to skip for the changelog
skip-labels: skip-changelog|backport\/.+

View File

@ -1,45 +0,0 @@
---
kind: pipeline
name: compliance
platform:
os: linux
arch: arm64
trigger:
event:
- pull_request
steps:
- name: check
pull: always
image: golang:1.14
environment:
GOPROXY: https://goproxy.cn
commands:
- make build
- make lint
- make vet
---
kind: pipeline
name: build-master
platform:
os: linux
arch: amd64
trigger:
branch:
- master
event:
- push
steps:
- name: build
pull: always
image: techknowlogick/xgo:latest
environment:
GOPROXY: https://goproxy.cn
commands:
- make build

Some files were not shown because too many files have changed in this diff Show More