mirror of
https://gitea.com/gitea/tea.git
synced 2025-09-19 02:02:55 +02:00
Compare commits
1 Commits
v0.8.0
...
release/v0
Author | SHA1 | Date | |
---|---|---|---|
8372dad929 |
@ -25,6 +25,8 @@ groups:
|
|||||||
name: ENHANCEMENTS
|
name: ENHANCEMENTS
|
||||||
labels:
|
labels:
|
||||||
- kind/enhancement
|
- kind/enhancement
|
||||||
|
- kind/refactor
|
||||||
|
- kind/ui
|
||||||
-
|
-
|
||||||
name: SECURITY
|
name: SECURITY
|
||||||
labels:
|
labels:
|
||||||
|
23
.drone.yml
23
.drone.yml
@ -6,10 +6,14 @@ platform:
|
|||||||
os: linux
|
os: linux
|
||||||
arch: amd64
|
arch: amd64
|
||||||
|
|
||||||
|
workspace:
|
||||||
|
base: /go
|
||||||
|
path: src/code.gitea.io/tea
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build
|
- name: build
|
||||||
pull: always
|
pull: always
|
||||||
image: golang:1.16
|
image: golang:1.13
|
||||||
environment:
|
environment:
|
||||||
GOPROXY: https://goproxy.cn
|
GOPROXY: https://goproxy.cn
|
||||||
commands:
|
commands:
|
||||||
@ -27,7 +31,8 @@ steps:
|
|||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: unit-test
|
- name: unit-test
|
||||||
image: golang:1.16
|
pull: always
|
||||||
|
image: golang:1.13
|
||||||
commands:
|
commands:
|
||||||
- make unit-test-coverage
|
- make unit-test-coverage
|
||||||
settings:
|
settings:
|
||||||
@ -40,7 +45,8 @@ steps:
|
|||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: release-test
|
- name: release-test
|
||||||
image: golang:1.16
|
pull: always
|
||||||
|
image: golang:1.13
|
||||||
commands:
|
commands:
|
||||||
- make test
|
- make test
|
||||||
settings:
|
settings:
|
||||||
@ -54,7 +60,7 @@ steps:
|
|||||||
|
|
||||||
- name: tag-test
|
- name: tag-test
|
||||||
pull: always
|
pull: always
|
||||||
image: golang:1.16
|
image: golang:1.13
|
||||||
commands:
|
commands:
|
||||||
- make test
|
- make test
|
||||||
settings:
|
settings:
|
||||||
@ -64,7 +70,8 @@ steps:
|
|||||||
- tag
|
- tag
|
||||||
|
|
||||||
- name: static
|
- name: static
|
||||||
image: golang:1.16
|
pull: always
|
||||||
|
image: techknowlogick/xgo:latest
|
||||||
environment:
|
environment:
|
||||||
GOPROXY: https://goproxy.cn
|
GOPROXY: https://goproxy.cn
|
||||||
commands:
|
commands:
|
||||||
@ -99,7 +106,7 @@ steps:
|
|||||||
image: plugins/s3:1
|
image: plugins/s3:1
|
||||||
settings:
|
settings:
|
||||||
acl: public-read
|
acl: public-read
|
||||||
bucket: gitea-artifacts
|
bucket: releases
|
||||||
endpoint: https://storage.gitea.io
|
endpoint: https://storage.gitea.io
|
||||||
path_style: true
|
path_style: true
|
||||||
source: "dist/release/*"
|
source: "dist/release/*"
|
||||||
@ -119,7 +126,7 @@ steps:
|
|||||||
image: plugins/s3:1
|
image: plugins/s3:1
|
||||||
settings:
|
settings:
|
||||||
acl: public-read
|
acl: public-read
|
||||||
bucket: gitea-artifacts
|
bucket: releases
|
||||||
endpoint: https://storage.gitea.io
|
endpoint: https://storage.gitea.io
|
||||||
path_style: true
|
path_style: true
|
||||||
source: "dist/release/*"
|
source: "dist/release/*"
|
||||||
@ -141,7 +148,7 @@ steps:
|
|||||||
image: plugins/s3:1
|
image: plugins/s3:1
|
||||||
settings:
|
settings:
|
||||||
acl: public-read
|
acl: public-read
|
||||||
bucket: gitea-artifacts
|
bucket: releases
|
||||||
endpoint: https://storage.gitea.io
|
endpoint: https://storage.gitea.io
|
||||||
path_style: true
|
path_style: true
|
||||||
source: "dist/release/*"
|
source: "dist/release/*"
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,5 +4,3 @@ tea
|
|||||||
.idea/
|
.idea/
|
||||||
.history/
|
.history/
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
.vscode/
|
|
||||||
|
123
CHANGELOG.md
123
CHANGELOG.md
@ -1,128 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [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
|
|
||||||
|
|
||||||
* BREAKING
|
|
||||||
* `tea issue create`: move `-b` flag to `-d` (#331)
|
|
||||||
* Drop `tea notif` shorthand in favor of `tea n` (#307)
|
|
||||||
* FEATURES
|
|
||||||
* Add commands for reviews (#315)
|
|
||||||
* Add `tea comment` and show comments of issues/pulls (#313)
|
|
||||||
* Add interactive mode for `tea milestone create` (#310)
|
|
||||||
* Add command to install shell completion (#309)
|
|
||||||
* Implement PR closing and reopening (#304)
|
|
||||||
* Add interactive mode for `tea issue create` (#302)
|
|
||||||
* BUGFIXES
|
|
||||||
* Introduce workaround for missing pull head sha (#340)
|
|
||||||
* Don't exit if we can't find a local repo with a remote matching to a login (#336)
|
|
||||||
* Don't push before creating a pull (#334)
|
|
||||||
* InitCommand() robustness (#327)
|
|
||||||
* `tea comment`: handle piped stdin (#322)
|
|
||||||
* ENHANCEMENTS
|
|
||||||
* Allow checking out PRs with deleted head branch (#341)
|
|
||||||
* Markdown renderer: detect terminal width, resolve relative URLs (#332)
|
|
||||||
* Add more issue / pr creation parameters (#331)
|
|
||||||
* Improve `tea time` (#319)
|
|
||||||
* `tea pr checkout`: dont create local branches (#314)
|
|
||||||
* Add `tea issues --fields`, allow printing labels (#312)
|
|
||||||
* Add more command shorthands (#307)
|
|
||||||
* Show PR CI status (#306)
|
|
||||||
* Make PR workflow helpers more robust (#300)
|
|
||||||
|
|
||||||
## [v0.6.0](https://gitea.com/gitea/tea/releases/tag/v0.6.0) - 2020-12-11
|
|
||||||
|
|
||||||
* BREAKING
|
|
||||||
* Add `tea repos search`, improve repo listing (#215)
|
|
||||||
* Add Detail View for Login (#212)
|
|
||||||
* FEATURES
|
|
||||||
* Add interactive mode for `tea pr create` (#279)
|
|
||||||
* Add organization delete command (#270)
|
|
||||||
* Add organization list command (#264)
|
|
||||||
* BUGFIXES
|
|
||||||
* Forces needed arguments to `tea ms issues` (#297)
|
|
||||||
* Subcommands work outside of git repos (#285)
|
|
||||||
* Fix repo flag ignores local repo for login detection (#285)
|
|
||||||
* Improve ssh handling (#277)
|
|
||||||
* Issue create return web url (#257)
|
|
||||||
* Support prerelease gitea instances (#252)
|
|
||||||
* Fix `tea pr create` within same repo (#248)
|
|
||||||
* Handle login name case-insensitive on all comands (#227)
|
|
||||||
* ENHANCEMENTS
|
|
||||||
* Add `tea login delete` (#296)
|
|
||||||
* Release delete: add --delete-tag & --confirm (#286)
|
|
||||||
* Sorted milestones list (#281)
|
|
||||||
* Pull clean & checkout use token for http(s) auth (#275)
|
|
||||||
* Show more infos in pull detail view (#271)
|
|
||||||
* Specify fields to print on `tea repos list` (#223)
|
|
||||||
* Print times in local timezone (#217)
|
|
||||||
* Issue create/edit print details (#214)
|
|
||||||
* Improve `tea logout` (#213)
|
|
||||||
* Added a shorthand for notifications (#209)
|
|
||||||
* Common subcommand naming scheme (#208)
|
|
||||||
* `tea pr checkout`: fetch via ssh if available (#192)
|
|
||||||
* Major refactor of codebase
|
|
||||||
* BUILD
|
|
||||||
* Use gox to cross-compile (#274)
|
|
||||||
* DOCS
|
|
||||||
* Update Docs to new code structure (#247)
|
|
||||||
|
|
||||||
## [v0.5.0](https://gitea.com/gitea/tea/releases/tag/v0.5.0) - 2020-09-27
|
|
||||||
|
|
||||||
* BREAKING
|
|
||||||
* Add Login Manage Functions (#182)
|
|
||||||
* FEATURES
|
|
||||||
* Add Release Subcomands (#195)
|
|
||||||
* Render Markdown and colorize labels table (#181)
|
|
||||||
* Add BasicAuth & Interactive for Login (#174)
|
|
||||||
* Add milestones subcomands (#149)
|
|
||||||
* BUGFIXES
|
|
||||||
* Fix Pulls Create (#202)
|
|
||||||
* Pulls create: detect head branch repo owner (#193)
|
|
||||||
* Fix Labels Delete (#180)
|
|
||||||
* ENHANCEMENTS
|
|
||||||
* Add Pagination Options for List Subcomands (#204)
|
|
||||||
* Issues/Pulls: Details show State (#196)
|
|
||||||
* Make issues & pulls subcommands consistent (#188)
|
|
||||||
* Update SDK to v0.13.0 (#179)
|
|
||||||
* More Options To Specify Repo (#178)
|
|
||||||
* Add Repo Create subcomand & enhancements (#173)
|
|
||||||
* Times: format duration as seconds for machine-readable outputs (#168)
|
|
||||||
* Add user message to login list view (#166)
|
|
||||||
|
|
||||||
## [v0.4.1](https://gitea.com/gitea/tea/releases/tag/v0.4.1) - 2020-09-13
|
## [v0.4.1](https://gitea.com/gitea/tea/releases/tag/v0.4.1) - 2020-09-13
|
||||||
|
|
||||||
* BUGFIXES
|
* BUGFIXES
|
||||||
|
132
CONTRIBUTING.md
132
CONTRIBUTING.md
@ -8,6 +8,7 @@
|
|||||||
- [Discuss your design](#discuss-your-design)
|
- [Discuss your design](#discuss-your-design)
|
||||||
- [Testing redux](#testing-redux)
|
- [Testing redux](#testing-redux)
|
||||||
- [Vendoring](#vendoring)
|
- [Vendoring](#vendoring)
|
||||||
|
- [Translation](#translation)
|
||||||
- [Code review](#code-review)
|
- [Code review](#code-review)
|
||||||
- [Styleguide](#styleguide)
|
- [Styleguide](#styleguide)
|
||||||
- [Sign-off your work](#sign-off-your-work)
|
- [Sign-off your work](#sign-off-your-work)
|
||||||
@ -19,31 +20,36 @@
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
This document explains how to contribute changes to TEA.
|
This document explains how to contribute changes to the Gitea project.
|
||||||
|
It assumes you have followed the
|
||||||
|
[installation instructions](https://docs.gitea.io/en-us/).
|
||||||
Sensitive security-related issues should be reported to
|
Sensitive security-related issues should be reported to
|
||||||
[security@gitea.io](mailto:security@gitea.io).
|
[security@gitea.io](mailto:security@gitea.io).
|
||||||
|
|
||||||
For configuring IDE or code editor to develop Gitea see [IDE and code editor configuration](https://github.com/go-gitea/gitea/tree/master/contrib/ide)
|
For configuring IDE or code editor to develop Gitea see [IDE and code editor configuration](contrib/ide/)
|
||||||
|
|
||||||
## Bug reports
|
## Bug reports
|
||||||
|
|
||||||
Please search the issues on the issue tracker with a variety of keywords
|
Please search the issues on the issue tracker with a variety of keywords
|
||||||
to ensure your bug is not already reported.
|
to ensure your bug is not already reported.
|
||||||
|
|
||||||
If unique, [open an issue](https://gitea.com/gitea/tea/issues/new).
|
If unique, [open an issue](https://github.com/go-gitea/gitea/issues/new)
|
||||||
|
and answer the questions so we can understand and reproduce the
|
||||||
|
problematic behavior.
|
||||||
|
|
||||||
Please write clear, concise instructions so we can reproduce the behavior—
|
To show us that the issue you are having is in Gitea itself, please
|
||||||
|
write clear, concise instructions so we can reproduce the behavior—
|
||||||
even if it seems obvious. The more detailed and specific you are,
|
even if it seems obvious. The more detailed and specific you are,
|
||||||
the faster we can fix the issue. Check out [How to Report Bugs
|
the faster we can fix the issue. Check out [How to Report Bugs
|
||||||
Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
|
Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
|
||||||
|
|
||||||
Please be kind, remember that TEA comes at no cost to you, and you're
|
Please be kind, remember that Gitea comes at no cost to you, and you're
|
||||||
getting free help.
|
getting free help.
|
||||||
|
|
||||||
## Discuss your design
|
## Discuss your design
|
||||||
|
|
||||||
The project welcomes submissions. If you want to change or add something,
|
The project welcomes submissions. If you want to change or add something,
|
||||||
please let everyone know what you're working on—[file an issue](https://gitea.com/gitea/tea/issues/new)!
|
please let everyone know what you're working on—[file an issue](https://github.com/go-gitea/gitea/issues/new)!
|
||||||
Significant changes must go through the change proposal process
|
Significant changes must go through the change proposal process
|
||||||
before they can be accepted. To create a proposal, file an issue with
|
before they can be accepted. To create a proposal, file an issue with
|
||||||
your proposed changes documented, and make sure to note in the title
|
your proposed changes documented, and make sure to note in the title
|
||||||
@ -57,8 +63,15 @@ 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 tests for the
|
||||||
Since TEA is an cli tool it should be obvious to test your feature localy first.
|
whole tree to make sure the changes don't break other usage
|
||||||
|
and keep the compatibility on upgrade. To make sure you are
|
||||||
|
running the test suite exactly like we do, you should install
|
||||||
|
the CLI for [Drone CI](https://github.com/drone/drone), as
|
||||||
|
we are using the server for continous testing, following [these
|
||||||
|
instructions](http://docs.drone.io/cli-installation/). After that,
|
||||||
|
you can simply call `drone exec --local --build-event "pull_request"` within
|
||||||
|
your working directory and it will try to run the test suite locally.
|
||||||
|
|
||||||
## Vendoring
|
## Vendoring
|
||||||
|
|
||||||
@ -74,11 +87,31 @@ an existing upstream commit.
|
|||||||
|
|
||||||
You can find more information on how to get started with it on the [dep project website](https://golang.github.io/dep/docs/introduction.html).
|
You can find more information on how to get started with it on the [dep project website](https://golang.github.io/dep/docs/introduction.html).
|
||||||
|
|
||||||
|
## Translation
|
||||||
|
|
||||||
|
We do all translation work inside [Crowdin](https://crowdin.com/project/gitea).
|
||||||
|
The only translation that is maintained in this git repository is
|
||||||
|
[`en_US.ini`](https://github.com/go-gitea/gitea/blob/master/options/locale/locale_en-US.ini)
|
||||||
|
and is synced regularily to Crowdin. Once a translation has reached
|
||||||
|
A SATISFACTORY PERCENTAGE it will be synced back into this repo and
|
||||||
|
included in the next released version.
|
||||||
|
|
||||||
|
## Building Gitea
|
||||||
|
|
||||||
|
Generally, the go build tools are installed as-needed in the `Makefile`.
|
||||||
|
An exception are the tools to build the CSS and images.
|
||||||
|
|
||||||
|
- To build CSS: Install [Node.js](https://nodejs.org/en/download/package-manager)
|
||||||
|
with `npm` and then run `npm install` and `make generate-stylesheets`.
|
||||||
|
- To build Images: ImageMagick, inkscape and zopflipng binaries must be
|
||||||
|
available in your `PATH` to run `make generate-images`.
|
||||||
|
|
||||||
## Code review
|
## Code review
|
||||||
|
|
||||||
Changes to TEA must be reviewed before they are accepted—no matter who
|
Changes to Gitea 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 GitHub's
|
||||||
pull request & review workflow to do that. Gitea ensure every PR is reviewed by at least 2 maintainers.
|
pull request workflow to do that. And, we also use [LGTM](http://lgtm.co)
|
||||||
|
to 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
|
||||||
the *[How to get faster PR reviews](https://github.com/kubernetes/community/blob/261cb0fd089b64002c91e8eddceebf032462ccd6/contributors/guide/pull-requests.md#best-practices-for-faster-reviews)* guide;
|
the *[How to get faster PR reviews](https://github.com/kubernetes/community/blob/261cb0fd089b64002c91e8eddceebf032462ccd6/contributors/guide/pull-requests.md#best-practices-for-faster-reviews)* guide;
|
||||||
@ -95,41 +128,6 @@ Some of the key points:
|
|||||||
|
|
||||||
## Styleguide
|
## Styleguide
|
||||||
|
|
||||||
### Commands
|
|
||||||
- Subcommands should follow the following structure:
|
|
||||||
```
|
|
||||||
tea <noun> <verb> [<noun>] [<flags>]
|
|
||||||
```
|
|
||||||
|
|
||||||
for example:
|
|
||||||
|
|
||||||
```
|
|
||||||
tea issues list
|
|
||||||
tea pulls create
|
|
||||||
tea teams add user --team x --user y
|
|
||||||
```
|
|
||||||
- Commands should accept nouns as singular & plural by making use of the `Aliases` field.
|
|
||||||
- The default action without a verb is `list`.
|
|
||||||
- There is a standard set of verbs: `list/ls`, `create`, `edit`, `delete`
|
|
||||||
- `ls` lists objects with filter options, and applies pagination where available.
|
|
||||||
- `delete` should show info what is deleted and ask user again, if force flag`-y` is not set
|
|
||||||
- Verbs that accept large numbers of flags provide an interactive mode when called without any arguments or flags.
|
|
||||||
- Try to reuse as many flag definitions as possible, see `cmd/flags/flags.go`.
|
|
||||||
- Always make sure that the help texts are properly set, and as concise as possible.
|
|
||||||
|
|
||||||
### Internal Module Structure
|
|
||||||
- `cmd`: only contains comand/flag options for `urfave/cli`
|
|
||||||
- subcomands are in a subpackage named after its parent comand
|
|
||||||
- `modules/task`: contain func for doing something with gitea
|
|
||||||
(e.g. create token by user/passwd)
|
|
||||||
- `modules/print`: contain all functions that print to stdout
|
|
||||||
- `modules/config`: config tea & login things
|
|
||||||
- `modules/interact`: contain functions to interact with user by prompts
|
|
||||||
- `modules/git`: do git related stuff (get info/push/pull/checkout/...)
|
|
||||||
- `modules/utils`: helper functions used by other functions
|
|
||||||
|
|
||||||
### Code Style
|
|
||||||
Use `make fmt`, check with `make lint`.
|
|
||||||
For imports you should use the following format (_without_ the comments)
|
For imports you should use the following format (_without_ the comments)
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
@ -166,7 +164,25 @@ commit automatically with `git commit -s`.
|
|||||||
|
|
||||||
## Release Cycle
|
## Release Cycle
|
||||||
|
|
||||||
Before we reach v1 there is no fixed release cycle.
|
We adopted a release schedule to streamline the process of working
|
||||||
|
on, finishing, and issuing releases. The overall goal is to make a
|
||||||
|
minor release every two months, which breaks down into one month of
|
||||||
|
general development followed by one month of testing and polishing
|
||||||
|
known as the release freeze. All the feature pull requests should be
|
||||||
|
merged in the first month of one release period. And, during the frozen
|
||||||
|
period, a corresponding release branch is open for fixes backported from
|
||||||
|
master. Release candidates are made during this period for user testing to
|
||||||
|
obtain a final version that is maintained in this branch. A release is
|
||||||
|
maintained by issuing patch releases to only correct critical problems
|
||||||
|
such as crashes or security issues.
|
||||||
|
|
||||||
|
Major release cycles are bimonthly. They always begin on the 25th and end on
|
||||||
|
the 24th (i.e., the 25th of December to February 24th).
|
||||||
|
|
||||||
|
During a development cycle, we may also publish any necessary minor releases
|
||||||
|
for the previous version. For example, if the latest, published release is
|
||||||
|
v1.2, then minor changes for the previous release—e.g., v1.1.0 -> v1.1.1—are
|
||||||
|
still possible.
|
||||||
|
|
||||||
## Maintainers
|
## Maintainers
|
||||||
|
|
||||||
@ -186,15 +202,12 @@ to the maintainers team. If a maintainer is inactive for more than 3
|
|||||||
months and forgets to leave the maintainers team, the owners may move
|
months and forgets to leave the maintainers team, the owners may move
|
||||||
him or her from the maintainers team to the advisors team.
|
him or her from the maintainers team to the advisors team.
|
||||||
For security reasons, Maintainers should use 2FA for their accounts and
|
For security reasons, Maintainers should use 2FA for their accounts and
|
||||||
if possible provide gpg signed commits.
|
if possible provide gpg signed commits.
|
||||||
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
|
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
|
||||||
https://help.github.com/articles/signing-commits-with-gpg/
|
https://help.github.com/articles/signing-commits-with-gpg/
|
||||||
|
|
||||||
## Owners
|
## Owners
|
||||||
|
|
||||||
This repo is part of the Gitea project and as such part of that project's
|
|
||||||
governance.
|
|
||||||
|
|
||||||
Since Gitea is a pure community organization without any company support,
|
Since Gitea is a pure community organization without any company support,
|
||||||
to keep the development healthy we will elect three owners every year. All
|
to keep the development healthy we will elect three owners every year. All
|
||||||
contributors may vote to elect up to three candidates, one of which will
|
contributors may vote to elect up to three candidates, one of which will
|
||||||
@ -217,9 +230,22 @@ I'm honored to having been elected an owner of Gitea, I agree with
|
|||||||
and lead the development of Gitea.
|
and lead the development of Gitea.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To honor the past owners, here's the history of the owners and the time
|
||||||
|
they served:
|
||||||
|
|
||||||
|
* 2016-11-04 ~ 2017-12-31
|
||||||
|
* [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
|
||||||
|
* [Thomas Boerger](https://github.com/tboerger) <thomas@webhippie.de>
|
||||||
|
* [Kim Carlbäcker](https://github.com/bkcsoft) <kim.carlbacker@gmail.com>
|
||||||
|
|
||||||
|
* 2018-01-01 ~ 2018-12-31
|
||||||
|
* [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
|
||||||
|
* [Lauris Bukšis-Haberkorns](https://github.com/lafriks) <lauris@nix.lv>
|
||||||
|
* [Kim Carlbäcker](https://github.com/bkcsoft) <kim.carlbacker@gmail.com>
|
||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
tea has the `master` branch as a tip branch and has version branches
|
Gitea has the `master` branch as a tip branch and has version branches
|
||||||
such as `release/v0.9`. `release/v0.9` is a release branch and we will
|
such as `release/v0.9`. `release/v0.9` is a release branch and we will
|
||||||
tag `v0.9.0` for binary download. If `v0.9.0` has bugs, we will accept
|
tag `v0.9.0` for binary download. If `v0.9.0` has bugs, we will accept
|
||||||
pull requests on the `release/v0.9` branch and publish a `v0.9.1` tag,
|
pull requests on the `release/v0.9` branch and publish a `v0.9.1` tag,
|
||||||
@ -235,7 +261,7 @@ be reviewed by two maintainers and must pass the automatic tests.
|
|||||||
Code that you contribute should use the standard copyright header:
|
Code that you contribute should use the standard copyright header:
|
||||||
|
|
||||||
```
|
```
|
||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
// Copyright 2018 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.
|
||||||
```
|
```
|
||||||
|
25
Dockerfile
25
Dockerfile
@ -1,25 +0,0 @@
|
|||||||
ARG GOVERSION="1.16.2"
|
|
||||||
|
|
||||||
FROM golang:${GOVERSION}-alpine AS buildenv
|
|
||||||
|
|
||||||
ARG CGO_ENABLED="0"
|
|
||||||
ARG GOOS="linux"
|
|
||||||
|
|
||||||
COPY . $GOPATH/src/
|
|
||||||
WORKDIR $GOPATH/src
|
|
||||||
|
|
||||||
RUN apk add --quiet --no-cache \
|
|
||||||
make \
|
|
||||||
git && \
|
|
||||||
make build
|
|
||||||
|
|
||||||
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"]
|
|
@ -1,63 +0,0 @@
|
|||||||
# comparing git forge commandline interfaces
|
|
||||||
|
|
||||||
[tea]: https://gitea.com/gitea/tea
|
|
||||||
[sip]: https://gitea.com/jolheiser/sip
|
|
||||||
[gitlab]: https://github.com/makkes/gitlab-cli
|
|
||||||
[glab]: https://github.com/profclems/glab
|
|
||||||
[gh]: https://cli.github.com
|
|
||||||
|
|
||||||
last update: 2020-12-11
|
|
||||||
|
|
||||||
## general
|
|
||||||
/ | [tea][tea] | [sip][sip] | [gitlab][gitlab] | [gh][gh]
|
|
||||||
-----------------------|:-----:|:-----:|:-----:|:-----:
|
|
||||||
forge|gitea|gitea|gitlab|github
|
|
||||||
official forge support|✓|✘|✘|✓
|
|
||||||
dev status|adding features|maintenance||
|
|
||||||
platform|any|any|any|any
|
|
||||||
|
|
||||||
## philosophy
|
|
||||||
/ | [tea][tea] | [sip][sip] | [gitlab][gitlab] | [gh][gh]
|
|
||||||
-----------------------|:-----:|:-----:|:-----:|:-----:
|
|
||||||
aims to replace git cli|✘|||✓
|
|
||||||
works with decentralization in mind|✓|✓|✓|✘
|
|
||||||
per-repo setup needed|✘||✓|✘
|
|
||||||
workflow helpers|✓|||
|
|
||||||
interactive mode |[(✓)](https://gitea.com/gitea/tea/issues?type=all&state=open&labels=&milestone=0&assignee=0&q=interactive)|✘| |✓
|
|
||||||
programmatic mode|✓|||✓
|
|
||||||
machine readable output|✓|||
|
|
||||||
follows XDG spec|✓|||
|
|
||||||
|
|
||||||
## features
|
|
||||||
/ | [tea][tea] | [sip][sip] | [gitlab][gitlab] | [gh][gh]
|
|
||||||
-----------------------|:-----:|:-----:|:-----:|:-----:
|
|
||||||
open web UI|✓|||
|
|
||||||
search repos|✓|||
|
|
||||||
search issues|✘|✓||
|
|
||||||
textual item search filter syntax|✘|✓||
|
|
||||||
CRUD repos|[(✓)](https://gitea.com/gitea/tea/issues/239)|||
|
|
||||||
CRUD issues|[(✓)](https://gitea.com/gitea/tea/issues/229)|||
|
|
||||||
CRUD milestones|[(✓)](https://gitea.com/gitea/tea/issues/246)|||
|
|
||||||
CRUD releases|✓|||
|
|
||||||
CRUD labels|✓|||
|
|
||||||
CRUD PRs|✓|||
|
|
||||||
CRUD time tracking|✓|||x
|
|
||||||
CRUD orgs|[(✓)](https://gitea.com/gitea/tea/issues/287)|||
|
|
||||||
create PRs from local repo|✓|||
|
|
||||||
create PRs from remote repo|✓|||
|
|
||||||
code review|[u](https://gitea.com/gitea/tea/issues/131)|||
|
|
||||||
merge PRs||||
|
|
||||||
read comments|[u](https://gitea.com/gitea/tea/issues/172)|||
|
|
||||||
post comments||||
|
|
||||||
manage CI|✘|✘|✓|
|
|
||||||
manage notifications|[(✓)]()|||
|
|
||||||
administration|[u](https://gitea.com/gitea/tea/issues/161)|✘||✘
|
|
||||||
markdown rendering|✓|||✓
|
|
||||||
issue import/export|[u](https://gitea.com/gitea/tea/issues/132)|||
|
|
||||||
checkout PRs|✓|||
|
|
||||||
|
|
||||||
- ✓: supported
|
|
||||||
- (✓): partial support
|
|
||||||
- u: upcoming
|
|
||||||
- ✘: not supported
|
|
||||||
- ?: unknown
|
|
49
Makefile
49
Makefile
@ -38,8 +38,6 @@ else
|
|||||||
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)"
|
LDFLAGS := -X "main.Version=$(TEA_VERSION)" -X "main.Tags=$(TAGS)"
|
||||||
|
|
||||||
PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/)
|
PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/)
|
||||||
@ -73,7 +71,7 @@ vet:
|
|||||||
# Default vet
|
# Default vet
|
||||||
$(GO) vet -mod=vendor $(PACKAGES)
|
$(GO) vet -mod=vendor $(PACKAGES)
|
||||||
# Custom vet
|
# Custom vet
|
||||||
$(GO) build -mod=vendor code.gitea.io/gitea-vet
|
$(GO) build -mod=vendor gitea.com/jolheiser/gitea-vet
|
||||||
$(GO) vet -vettool=gitea-vet $(PACKAGES)
|
$(GO) vet -vettool=gitea-vet $(PACKAGES)
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
@ -133,7 +131,7 @@ check: test
|
|||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
install: $(wildcard *.go)
|
install: $(wildcard *.go)
|
||||||
$(GO) install -mod=vendor -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
|
$(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: $(EXECUTABLE)
|
build: $(EXECUTABLE)
|
||||||
@ -141,23 +139,46 @@ build: $(EXECUTABLE)
|
|||||||
$(EXECUTABLE): $(SOURCES)
|
$(EXECUTABLE): $(SOURCES)
|
||||||
$(GO) build -mod=vendor $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
$(GO) build -mod=vendor $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -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 release-windows release-linux release-darwin release-copy release-compress release-check
|
||||||
|
|
||||||
.PHONY: release-dirs
|
.PHONY: release-dirs
|
||||||
release-dirs:
|
release-dirs:
|
||||||
mkdir -p $(DIST)/binaries $(DIST)/release
|
mkdir -p $(DIST)/binaries $(DIST)/release
|
||||||
|
|
||||||
.PHONY: release-os
|
.PHONY: release-windows
|
||||||
release-os:
|
release-windows:
|
||||||
@hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
cd /tmp && $(GO) get -u github.com/mitchellh/gox; \
|
cd /tmp && $(GO) get -u src.techknowlogick.com/xgo; \
|
||||||
fi
|
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}}"
|
GO111MODULE=off xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out tea-$(VERSION) .
|
||||||
|
ifeq ($(CI),drone)
|
||||||
|
cp /build/* $(DIST)/binaries
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: release-linux
|
||||||
|
release-linux:
|
||||||
|
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
cd /tmp && $(GO) get -u src.techknowlogick.com/xgo; \
|
||||||
|
fi
|
||||||
|
GO111MODULE=off xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/mips64le,linux/mips,linux/mipsle' -out tea-$(VERSION) .
|
||||||
|
ifeq ($(CI),drone)
|
||||||
|
cp /build/* $(DIST)/binaries
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: release-darwin
|
||||||
|
release-darwin:
|
||||||
|
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
cd /tmp && $(GO) get -u src.techknowlogick.com/xgo; \
|
||||||
|
fi
|
||||||
|
GO111MODULE=off xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out tea-$(VERSION) .
|
||||||
|
ifeq ($(CI),drone)
|
||||||
|
cp /build/* $(DIST)/binaries
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: release-copy
|
||||||
|
release-copy:
|
||||||
|
cd $(DIST); for file in `find /build -type f -name "*"`; do cp $${file} ./release/; done;
|
||||||
|
|
||||||
.PHONY: release-compress
|
.PHONY: release-compress
|
||||||
release-compress:
|
release-compress:
|
||||||
|
130
README.md
130
README.md
@ -2,95 +2,59 @@
|
|||||||
|
|
||||||
[](https://opensource.org/licenses/MIT) [](https://gitea.com/gitea/tea/releases) [](https://drone.gitea.com/gitea/tea) [](https://discord.gg/Gitea) [](https://goreportcard.com/report/code.gitea.io/tea) [](https://godoc.org/code.gitea.io/tea)
|
[](https://opensource.org/licenses/MIT) [](https://gitea.com/gitea/tea/releases) [](https://drone.gitea.com/gitea/tea) [](https://discord.gg/Gitea) [](https://goreportcard.com/report/code.gitea.io/tea) [](https://godoc.org/code.gitea.io/tea)
|
||||||
|
|
||||||
### The official CLI for Gitea
|
## The official CLI interface for gitea
|
||||||
|
|
||||||

|
Tea is a command line tool for interacting on one or more Gitea instances.
|
||||||
|
It uses [code.gitea.io/sdk](https://code.gitea.io/sdk) and interacts with the Gitea API
|
||||||
|
|
||||||
```
|

|
||||||
tea - command line tool to interact with Gitea
|
|
||||||
version 0.7.0-preview
|
|
||||||
|
|
||||||
USAGE
|
|
||||||
tea command [subcommand] [command options] [arguments...]
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
tea is a productivity helper for Gitea. It can be used to manage most entities on one
|
|
||||||
or multiple Gitea instances and provides local helpers like 'tea pull checkout'.
|
|
||||||
tea makes use of context provided by the repository in $PWD if available, but is still
|
|
||||||
usable independently of $PWD. Configuration is persisted in $XDG_CONFIG_HOME/tea.
|
|
||||||
|
|
||||||
COMMANDS
|
|
||||||
help, h Shows a list of commands or help for one command
|
|
||||||
ENTITIES:
|
|
||||||
issues, issue, i List, create and update issues
|
|
||||||
pulls, pull, pr Manage and checkout pull requests
|
|
||||||
labels, label Manage issue labels
|
|
||||||
milestones, milestone, ms List and create milestones
|
|
||||||
releases, release, r Manage releases
|
|
||||||
times, time, t Operate on tracked times of a repository's issues & pulls
|
|
||||||
organizations, organization, org List, create, delete organizations
|
|
||||||
repos, repo Show repository details
|
|
||||||
HELPERS:
|
|
||||||
open, o Open something of the repository in web browser
|
|
||||||
notifications, notification, n Show notifications
|
|
||||||
SETUP:
|
|
||||||
logins, login Log in to a Gitea server
|
|
||||||
logout Log out from a Gitea server
|
|
||||||
shellcompletion, autocomplete Install shell completion for tea
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
--help, -h show help (default: false)
|
|
||||||
--version, -v print the version (default: false)
|
|
||||||
|
|
||||||
EXAMPLES
|
|
||||||
tea login add # add a login once to get started
|
|
||||||
|
|
||||||
tea pulls # list open pulls for the repo in $PWD
|
|
||||||
tea pulls --repo $HOME/foo # list open pulls for the repo in $HOME/foo
|
|
||||||
tea pulls --remote upstream # list open pulls for the repo pointed at by
|
|
||||||
# your local "upstream" git remote
|
|
||||||
# list open pulls for any gitea repo at the given login instance
|
|
||||||
tea pulls --repo gitea/tea --login gitea.com
|
|
||||||
|
|
||||||
tea milestone issues 0.7.0 # view open issues for milestone '0.7.0'
|
|
||||||
tea issue 189 # view contents of issue 189
|
|
||||||
tea open 189 # open web ui for issue 189
|
|
||||||
tea open milestones # open web ui for milestones
|
|
||||||
|
|
||||||
# send gitea desktop notifications every 5 minutes (bash + libnotify)
|
|
||||||
while :; do tea notifications --all -o simple | xargs -i notify-send {}; sleep 300; done
|
|
||||||
|
|
||||||
ABOUT
|
|
||||||
Written & maintained by The Gitea Authors.
|
|
||||||
If you find a bug or want to contribute, we'll welcome you at https://gitea.com/gitea/tea.
|
|
||||||
More info about Gitea itself on https://gitea.io.
|
|
||||||
```
|
|
||||||
|
|
||||||
- [Compare features with other git forge CLIs](./FEATURE-COMPARISON.md)
|
|
||||||
- tea uses [code.gitea.io/sdk](https://code.gitea.io/sdk) and interacts with the Gitea API.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
There are different ways to get `tea`:
|
You can use the prebuilt binaries from [dl.gitea.io](https://dl.gitea.io/tea/)
|
||||||
|
|
||||||
1. Install via your system package manager:
|
|
||||||
- macOS via `brew` (gitea-maintained):
|
|
||||||
```sh
|
|
||||||
brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea
|
|
||||||
brew install tea
|
|
||||||
```
|
|
||||||
- arch linux ([gitea-tea](https://aur.archlinux.org/packages/gitea-tea), thirdparty)
|
|
||||||
- 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/)
|
To install from source, go 1.12 or newer is required:
|
||||||
|
```sh
|
||||||
|
go get code.gitea.io/tea
|
||||||
|
go install code.gitea.io/tea
|
||||||
|
```
|
||||||
|
|
||||||
3. Install from source (go 1.13 or newer is required):
|
|
||||||
```sh
|
|
||||||
go get code.gitea.io/tea
|
|
||||||
go install code.gitea.io/tea
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Docker (thirdparty): [tgerczei/tea](https://hub.docker.com/r/tgerczei/tea)
|
If you have `brew` installed, you can install `tea` via:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea
|
||||||
|
brew install tea
|
||||||
|
```
|
||||||
|
|
||||||
|
Distribution packages exist for: **alpinelinux ([tea](https://pkgs.alpinelinux.org/packages?name=tea&branch=edge))** and **archlinux ([gitea-tea](https://aur.archlinux.org/packages/gitea-tea))**
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
First of all, you have to create a token on your `personal settings -> application` page of your gitea instance.
|
||||||
|
Use this token to login with `tea`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
tea login add --name=try --url=https://try.gitea.io --token=xxxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can use the following `tea` subcommands.
|
||||||
|
Detailed usage information is available via `tea <command> --help`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
login Log in to a Gitea server
|
||||||
|
logout Log out from a Gitea server
|
||||||
|
issues List, create and update issues
|
||||||
|
pulls List, checkout and clean pull requests
|
||||||
|
releases Create releases
|
||||||
|
repos Operate with repositories
|
||||||
|
labels Manage issue labels
|
||||||
|
times Operate on tracked times of a repositorys issues and pulls
|
||||||
|
open Open something of the repository on web browser
|
||||||
|
```
|
||||||
|
|
||||||
|
To fetch issues from different repos, use the `--remote` flag (when inside a gitea repository directory) or `--login` & `--repo` flags.
|
||||||
|
|
||||||
## Compilation
|
## Compilation
|
||||||
|
|
||||||
@ -108,11 +72,13 @@ make
|
|||||||
Fork -> Patch -> Push -> Pull Request
|
Fork -> Patch -> Push -> Pull Request
|
||||||
|
|
||||||
- `make test` run testsuite
|
- `make test` run testsuite
|
||||||
- `make vet` run checks (check the order of imports; preventing failure on CI pipeline beforehand)
|
|
||||||
- `make vendor` when adding new dependencies
|
- `make vendor` when adding new dependencies
|
||||||
- ... (for other development tasks, check the `Makefile`)
|
- ... (for other development tasks, check the `Makefile`)
|
||||||
|
|
||||||
**Please** read the [CONTRIBUTING](CONTRIBUTING.md) documentation, it will tell you about internal structures and concepts.
|
## Authors
|
||||||
|
|
||||||
|
* [Maintainers](https://github.com/orgs/go-gitea/people)
|
||||||
|
* [Contributors](https://github.com/go-gitea/tea/graphs/contributors)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
2
build.go
2
build.go
@ -10,5 +10,5 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
// for vet
|
// for vet
|
||||||
_ "code.gitea.io/gitea-vet"
|
_ "gitea.com/jolheiser/gitea-vet"
|
||||||
)
|
)
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/adrg/xdg"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdAutocomplete manages autocompletion
|
|
||||||
var CmdAutocomplete = cli.Command{
|
|
||||||
Name: "shellcompletion",
|
|
||||||
Aliases: []string{"autocomplete"},
|
|
||||||
Category: catSetup,
|
|
||||||
Usage: "Install shell completion for tea",
|
|
||||||
Description: "Install shell completion for tea",
|
|
||||||
ArgsUsage: "<shell type> (bash, zsh, powershell, fish)",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "install",
|
|
||||||
Usage: "Persist in shell config instead of printing commands",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: runAutocompleteAdd,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runAutocompleteAdd(ctx *cli.Context) error {
|
|
||||||
var remoteFile, localFile, cmds string
|
|
||||||
shell := ctx.Args().First()
|
|
||||||
|
|
||||||
switch shell {
|
|
||||||
case "zsh":
|
|
||||||
remoteFile = "contrib/autocomplete.zsh"
|
|
||||||
localFile = "autocomplete.zsh"
|
|
||||||
cmds = "echo 'PROG=tea _CLI_ZSH_AUTOCOMPLETE_HACK=1 source \"%s\"' >> ~/.zshrc && source ~/.zshrc"
|
|
||||||
|
|
||||||
case "bash":
|
|
||||||
remoteFile = "contrib/autocomplete.sh"
|
|
||||||
localFile = "autocomplete.sh"
|
|
||||||
cmds = "echo 'PROG=tea source \"%s\"' >> ~/.bashrc && source ~/.bashrc"
|
|
||||||
|
|
||||||
case "powershell":
|
|
||||||
remoteFile = "contrib/autocomplete.ps1"
|
|
||||||
localFile = "tea.ps1"
|
|
||||||
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:
|
|
||||||
return fmt.Errorf("Must specify valid %s", ctx.Command.ArgsUsage)
|
|
||||||
}
|
|
||||||
|
|
||||||
localPath, err := xdg.ConfigFile("tea/" + localFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds = fmt.Sprintf(cmds, localPath)
|
|
||||||
if err = writeRemoteAutoCompleteFile(remoteFile, localPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Bool("install") {
|
|
||||||
fmt.Println("Installing in your shellrc")
|
|
||||||
installer := exec.Command(shell, "-c", cmds)
|
|
||||||
if shell == "powershell" {
|
|
||||||
installer = exec.Command("powershell.exe", "-Command", cmds)
|
|
||||||
}
|
|
||||||
out, err := installer.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Couldn't run the commands: %s %s", err, out)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Println("\n# Run the following commands to install autocompletion (or use --install)")
|
|
||||||
fmt.Println(cmds)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeRemoteAutoCompleteFile(file, destPath string) error {
|
|
||||||
url := fmt.Sprintf("https://gitea.com/gitea/tea/raw/branch/master/%s", file)
|
|
||||||
fmt.Println("Fetching " + url)
|
|
||||||
|
|
||||||
res, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
writer, err := os.Create(destPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer writer.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(writer, res.Body)
|
|
||||||
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
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
var (
|
|
||||||
catSetup = "SETUP"
|
|
||||||
catEntities = "ENTITIES"
|
|
||||||
catHelpers = "HELPERS"
|
|
||||||
)
|
|
@ -1,77 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/interact"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/print"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdAddComment is the main command to operate with notifications
|
|
||||||
var CmdAddComment = cli.Command{
|
|
||||||
Name: "comment",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Category: catEntities,
|
|
||||||
Usage: "Add a comment to an issue / pr",
|
|
||||||
Description: "Add a comment to an issue / pr",
|
|
||||||
ArgsUsage: "<issue / pr index> [<comment body>]",
|
|
||||||
Action: runAddComment,
|
|
||||||
Flags: flags.AllDefaultFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runAddComment(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
args := ctx.Args()
|
|
||||||
if args.Len() == 0 {
|
|
||||||
return fmt.Errorf("Please specify issue / pr index")
|
|
||||||
}
|
|
||||||
|
|
||||||
idx, err := utils.ArgToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
body := strings.Join(ctx.Args().Tail(), " ")
|
|
||||||
if interact.IsStdinPiped() {
|
|
||||||
// custom solution until https://github.com/AlecAivazis/survey/issues/328 is fixed
|
|
||||||
if bodyStdin, err := ioutil.ReadAll(ctx.App.Reader); err != nil {
|
|
||||||
return err
|
|
||||||
} else if len(bodyStdin) != 0 {
|
|
||||||
body = strings.Join([]string{body, string(bodyStdin)}, "\n\n")
|
|
||||||
}
|
|
||||||
} else if len(body) == 0 {
|
|
||||||
if body, err = interact.PromptMultiline("Content"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(body) == 0 {
|
|
||||||
return fmt.Errorf("No comment body provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
comment, _, err := client.CreateIssueComment(ctx.Owner, ctx.Repo, idx, gitea.CreateIssueCommentOption{
|
|
||||||
Body: body,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
print.Comment(comment)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
255
cmd/config.go
Normal file
255
cmd/config.go
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
// Copyright 2018 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 (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"code.gitea.io/tea/modules/git"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Login represents a login to a gitea server, you even could add multiple logins for one gitea server
|
||||||
|
type Login struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
Token string `yaml:"token"`
|
||||||
|
Active bool `yaml:"active"`
|
||||||
|
SSHHost string `yaml:"ssh_host"`
|
||||||
|
// optional path to the private key
|
||||||
|
SSHKey string `yaml:"ssh_key"`
|
||||||
|
Insecure bool `yaml:"insecure"`
|
||||||
|
// optional gitea username
|
||||||
|
User string `yaml:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns a client to operate Gitea API
|
||||||
|
func (l *Login) Client() *gitea.Client {
|
||||||
|
client := gitea.NewClient(l.URL, l.Token)
|
||||||
|
if l.Insecure {
|
||||||
|
cookieJar, _ := cookiejar.New(nil)
|
||||||
|
|
||||||
|
client.SetHTTPClient(&http.Client{
|
||||||
|
Jar: cookieJar,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSSHHost returns SSH host name
|
||||||
|
func (l *Login) GetSSHHost() string {
|
||||||
|
if l.SSHHost != "" {
|
||||||
|
return l.SSHHost
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(l.URL)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config reprensents local configurations
|
||||||
|
type Config struct {
|
||||||
|
Logins []Login `yaml:"logins"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
config Config
|
||||||
|
yamlConfigPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
homeDir, err := utils.Home()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Retrieve home dir failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Join(homeDir, ".tea")
|
||||||
|
err = os.MkdirAll(dir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Init tea config dir " + dir + " failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
yamlConfigPath = filepath.Join(dir, "tea.yml")
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitRepo(repoPath string) (string, string) {
|
||||||
|
p := strings.Split(repoPath, "/")
|
||||||
|
if len(p) >= 2 {
|
||||||
|
return p[0], p[1]
|
||||||
|
}
|
||||||
|
return repoPath, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActiveLogin() (*Login, error) {
|
||||||
|
if len(config.Logins) == 0 {
|
||||||
|
return nil, errors.New("No available login")
|
||||||
|
}
|
||||||
|
for _, l := range config.Logins {
|
||||||
|
if l.Active {
|
||||||
|
return &l, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config.Logins[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLoginByName(name string) *Login {
|
||||||
|
for _, l := range config.Logins {
|
||||||
|
if l.Name == name {
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addLogin(login Login) error {
|
||||||
|
for _, l := range config.Logins {
|
||||||
|
if l.Name == login.Name {
|
||||||
|
if l.URL == login.URL && l.Token == login.Token {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("Login name has already been used")
|
||||||
|
}
|
||||||
|
if l.URL == login.URL && l.Token == login.Token {
|
||||||
|
return errors.New("URL has been added")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(login.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if login.SSHHost == "" {
|
||||||
|
login.SSHHost = u.Hostname()
|
||||||
|
}
|
||||||
|
config.Logins = append(config.Logins, login)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFileExist(fileName string) (bool, error) {
|
||||||
|
f, err := os.Stat(fileName)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if f.IsDir() {
|
||||||
|
return false, errors.New("A directory with the same name exists")
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig(ymlPath string) error {
|
||||||
|
exist, _ := isFileExist(ymlPath)
|
||||||
|
if exist {
|
||||||
|
Println("Found config file", ymlPath)
|
||||||
|
bs, err := ioutil.ReadFile(ymlPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = yaml.Unmarshal(bs, &config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveConfig(ymlPath string) error {
|
||||||
|
bs, err := yaml.Marshal(&config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(ymlPath, bs, 0660)
|
||||||
|
}
|
||||||
|
|
||||||
|
func curGitRepoPath(path string) (*Login, string, error) {
|
||||||
|
var err error
|
||||||
|
var repo *git.TeaRepo
|
||||||
|
if len(path) == 0 {
|
||||||
|
repo, err = git.RepoForWorkdir()
|
||||||
|
} else {
|
||||||
|
repo, err = git.RepoFromPath(path)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
gitConfig, err := repo.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no remote
|
||||||
|
if len(gitConfig.Remotes) == 0 {
|
||||||
|
return nil, "", errors.New("No remote(s) found in this Git repository")
|
||||||
|
}
|
||||||
|
|
||||||
|
// if only one remote exists
|
||||||
|
if len(gitConfig.Remotes) >= 1 && len(remoteValue) == 0 {
|
||||||
|
for remote := range gitConfig.Remotes {
|
||||||
|
remoteValue = remote
|
||||||
|
}
|
||||||
|
if len(gitConfig.Remotes) > 1 {
|
||||||
|
// if master branch is present, use it as the default remote
|
||||||
|
masterBranch, ok := gitConfig.Branches["master"]
|
||||||
|
if ok {
|
||||||
|
if len(masterBranch.Remote) > 0 {
|
||||||
|
remoteValue = masterBranch.Remote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteConfig, ok := gitConfig.Remotes[remoteValue]
|
||||||
|
if !ok || remoteConfig == nil {
|
||||||
|
return nil, "", errors.New("Remote " + remoteValue + " not found in this Git repository")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range config.Logins {
|
||||||
|
for _, u := range remoteConfig.URLs {
|
||||||
|
p, err := git.ParseURL(strings.TrimSpace(u))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("Git remote URL parse failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
if strings.EqualFold(p.Scheme, "http") || strings.EqualFold(p.Scheme, "https") {
|
||||||
|
if strings.HasPrefix(u, l.URL) {
|
||||||
|
ps := strings.Split(p.Path, "/")
|
||||||
|
path := strings.Join(ps[len(ps)-2:], "/")
|
||||||
|
return &l, strings.TrimSuffix(path, ".git"), nil
|
||||||
|
}
|
||||||
|
} else if strings.EqualFold(p.Scheme, "ssh") {
|
||||||
|
if l.GetSSHHost() == strings.Split(p.Host, ":")[0] {
|
||||||
|
return &l, strings.TrimLeft(strings.TrimSuffix(p.Path, ".git"), "/"), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, "", errors.New("No Gitea login found. You might want to specify --repo (and --login) to work outside of a repository")
|
||||||
|
}
|
126
cmd/flags.go
Normal file
126
cmd/flags.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// create global variables for global Flags to simplify
|
||||||
|
// access to the options without requiring cli.Context
|
||||||
|
var (
|
||||||
|
loginValue string
|
||||||
|
repoValue string
|
||||||
|
outputValue string
|
||||||
|
remoteValue string
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoginFlag provides flag to specify tea login profile
|
||||||
|
var LoginFlag = cli.StringFlag{
|
||||||
|
Name: "login",
|
||||||
|
Aliases: []string{"l"},
|
||||||
|
Usage: "Use a different Gitea login. Optional",
|
||||||
|
Destination: &loginValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepoFlag provides flag to specify repository
|
||||||
|
var RepoFlag = cli.StringFlag{
|
||||||
|
Name: "repo",
|
||||||
|
Aliases: []string{"r"},
|
||||||
|
Usage: "Repository to interact with. Optional",
|
||||||
|
Destination: &repoValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteFlag provides flag to specify remote repository
|
||||||
|
var RemoteFlag = cli.StringFlag{
|
||||||
|
Name: "remote",
|
||||||
|
Aliases: []string{"R"},
|
||||||
|
Usage: "Discover Gitea login from remote. Optional",
|
||||||
|
Destination: &remoteValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputFlag provides flag to specify output type
|
||||||
|
var OutputFlag = cli.StringFlag{
|
||||||
|
Name: "output",
|
||||||
|
Aliases: []string{"o"},
|
||||||
|
Usage: "Output format. (csv, simple, table, tsv, yaml)",
|
||||||
|
Destination: &outputValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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...)
|
||||||
|
|
||||||
|
// initCommand returns repository and *Login based on flags
|
||||||
|
func initCommand() (*Login, string, string) {
|
||||||
|
err := loadConfig(yamlConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("load config file failed ", yamlConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
login, repoPath, err := curGitRepoPath(repoValue)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if loginValue != "" {
|
||||||
|
login = getLoginByName(loginValue)
|
||||||
|
if login == nil {
|
||||||
|
log.Fatal("Login name " + loginValue + " does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, repo := splitRepo(repoPath)
|
||||||
|
return login, owner, repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// initCommandLoginOnly return *Login based on flags
|
||||||
|
func initCommandLoginOnly() *Login {
|
||||||
|
err := loadConfig(yamlConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("load config file failed ", yamlConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var login *Login
|
||||||
|
if loginValue == "" {
|
||||||
|
login, err = getActiveLogin()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
login = getLoginByName(loginValue)
|
||||||
|
if login == nil {
|
||||||
|
log.Fatal("Login name " + loginValue + " does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return login
|
||||||
|
}
|
@ -1,49 +0,0 @@
|
|||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package 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 {
|
|
||||||
return &CsvFlag{
|
|
||||||
AvailableFields: availableValues,
|
|
||||||
StringFlag: cli.StringFlag{
|
|
||||||
Name: name,
|
|
||||||
Aliases: aliases,
|
|
||||||
Value: strings.Join(defaults, ","),
|
|
||||||
Usage: fmt.Sprintf(`Comma-separated list of %s. Available values:
|
|
||||||
%s
|
|
||||||
`, usage, 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
|
|
||||||
}
|
|
@ -1,204 +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"
|
|
||||||
|
|
||||||
"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...)
|
|
||||||
|
|
||||||
// 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"},
|
|
||||||
)
|
|
||||||
|
|
||||||
// IssuePREditFlags defines flags for properties of issues and PRs
|
|
||||||
var IssuePREditFlags = append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "title",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "description",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "assignees",
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "Comma-separated list of usernames to assign",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "labels",
|
|
||||||
Aliases: []string{"L"},
|
|
||||||
Usage: "Comma-separated list of labels to assign",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "deadline",
|
|
||||||
Aliases: []string{"D"},
|
|
||||||
Usage: "Deadline timestamp to assign",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "milestone",
|
|
||||||
Aliases: []string{"m"},
|
|
||||||
Usage: "Milestone to assign",
|
|
||||||
},
|
|
||||||
}, LoginRepoFlags...)
|
|
||||||
|
|
||||||
// GetIssuePREditFlags parses all IssuePREditFlags
|
|
||||||
func GetIssuePREditFlags(ctx *context.TeaContext) (*gitea.CreateIssueOption, error) {
|
|
||||||
opts := gitea.CreateIssueOption{
|
|
||||||
Title: ctx.String("title"),
|
|
||||||
Body: ctx.String("description"),
|
|
||||||
Assignees: strings.Split(ctx.String("assignees"), ","),
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
|
|
||||||
date := ctx.String("deadline")
|
|
||||||
if date != "" {
|
|
||||||
t, err := dateparse.ParseAny(date)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
opts.Deadline = &t
|
|
||||||
}
|
|
||||||
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
labelNames := strings.Split(ctx.String("labels"), ",")
|
|
||||||
if len(labelNames) != 0 {
|
|
||||||
if client == nil {
|
|
||||||
client = ctx.Login.Client()
|
|
||||||
}
|
|
||||||
if opts.Labels, err = task.ResolveLabelNames(client, ctx.Owner, ctx.Repo, labelNames); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if milestoneName := ctx.String("milestone"); len(milestoneName) != 0 {
|
|
||||||
if client == nil {
|
|
||||||
client = ctx.Login.Client()
|
|
||||||
}
|
|
||||||
ms, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, milestoneName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Milestone '%s' not found", milestoneName)
|
|
||||||
}
|
|
||||||
opts.Milestone = ms.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
return &opts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FieldsFlag generates a flag selecting printable fields.
|
|
||||||
// To retrieve the value, use f.GetValues()
|
|
||||||
func FieldsFlag(availableFields, defaultFields []string) *CsvFlag {
|
|
||||||
return NewCsvFlag("fields", "fields to print", []string{"f"}, availableFields, defaultFields)
|
|
||||||
}
|
|
202
cmd/issues.go
202
cmd/issues.go
@ -6,12 +6,10 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/issues"
|
"code.gitea.io/sdk/gitea"
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/interact"
|
|
||||||
"code.gitea.io/tea/modules/print"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@ -19,53 +17,201 @@ import (
|
|||||||
// CmdIssues represents to login a gitea server.
|
// CmdIssues represents to login a gitea server.
|
||||||
var CmdIssues = cli.Command{
|
var CmdIssues = cli.Command{
|
||||||
Name: "issues",
|
Name: "issues",
|
||||||
Aliases: []string{"issue", "i"},
|
Usage: "List and create issues",
|
||||||
Category: catEntities,
|
Description: `List and create issues`,
|
||||||
Usage: "List, create and update issues",
|
|
||||||
Description: `Lists issues when called without argument. If issue index is provided, will show it in detail.`,
|
|
||||||
ArgsUsage: "[<issue index>]",
|
ArgsUsage: "[<issue index>]",
|
||||||
Action: runIssues,
|
Action: runIssues,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&issues.CmdIssuesList,
|
&CmdIssuesList,
|
||||||
&issues.CmdIssuesCreate,
|
&CmdIssuesCreate,
|
||||||
&issues.CmdIssuesReopen,
|
&CmdIssuesReopen,
|
||||||
&issues.CmdIssuesClose,
|
&CmdIssuesClose,
|
||||||
},
|
},
|
||||||
|
Flags: AllDefaultFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdIssuesList represents a sub command of issues to list issues
|
||||||
|
var CmdIssuesList = cli.Command{
|
||||||
|
Name: "ls",
|
||||||
|
Usage: "List issues of the repository",
|
||||||
|
Description: `List issues of the repository`,
|
||||||
|
Action: runIssuesList,
|
||||||
Flags: append([]cli.Flag{
|
Flags: append([]cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.StringFlag{
|
||||||
Name: "comments",
|
Name: "state",
|
||||||
Usage: "Wether to display comments (will prompt if not provided & run interactively)",
|
Usage: "Filter by issue state (all|open|closed)",
|
||||||
|
DefaultText: "open",
|
||||||
},
|
},
|
||||||
}, issues.CmdIssuesList.Flags...),
|
}, AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIssues(ctx *cli.Context) error {
|
func runIssues(ctx *cli.Context) error {
|
||||||
if ctx.Args().Len() == 1 {
|
if ctx.Args().Len() == 1 {
|
||||||
return runIssueDetail(ctx, ctx.Args().First())
|
return runIssueDetail(ctx, ctx.Args().First())
|
||||||
}
|
}
|
||||||
return issues.RunIssuesList(ctx)
|
return runIssuesList(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIssueDetail(cmd *cli.Context, index string) error {
|
func runIssueDetail(ctx *cli.Context, index string) error {
|
||||||
ctx := context.InitCommand(cmd)
|
login, owner, repo := initCommand()
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
idx, err := utils.ArgToIndex(index)
|
idx, err := argToIndex(index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
issue, _, err := ctx.Login.Client().GetIssue(ctx.Owner, ctx.Repo, idx)
|
issue, err := login.Client().GetIssue(owner, repo, idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
print.IssueDetails(issue)
|
|
||||||
|
|
||||||
if issue.Comments > 0 {
|
fmt.Printf("#%d %s\n%s created %s\n\n%s\n", issue.Index,
|
||||||
err = interact.ShowCommentsMaybeInteractive(ctx, idx, issue.Comments)
|
issue.Title,
|
||||||
if err != nil {
|
issue.Poster.UserName,
|
||||||
return fmt.Errorf("error loading comments: %v", err)
|
issue.Created.Format("2006-01-02 15:04:05"),
|
||||||
|
issue.Body,
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runIssuesList(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
|
||||||
|
state := gitea.StateOpen
|
||||||
|
switch ctx.String("state") {
|
||||||
|
case "all":
|
||||||
|
state = gitea.StateAll
|
||||||
|
case "open":
|
||||||
|
state = gitea.StateOpen
|
||||||
|
case "closed":
|
||||||
|
state = gitea.StateClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
issues, err := login.Client().ListRepoIssues(owner, repo, gitea.ListIssueOption{
|
||||||
|
State: state,
|
||||||
|
Type: gitea.IssueTypeIssue,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Index",
|
||||||
|
"State",
|
||||||
|
"Author",
|
||||||
|
"Updated",
|
||||||
|
"Title",
|
||||||
|
}
|
||||||
|
|
||||||
|
var values [][]string
|
||||||
|
|
||||||
|
if len(issues) == 0 {
|
||||||
|
Output(outputValue, headers, values)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, issue := range issues {
|
||||||
|
name := issue.Poster.FullName
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = issue.Poster.UserName
|
||||||
}
|
}
|
||||||
|
values = append(
|
||||||
|
values,
|
||||||
|
[]string{
|
||||||
|
strconv.FormatInt(issue.Index, 10),
|
||||||
|
string(issue.State),
|
||||||
|
name,
|
||||||
|
issue.Updated.Format("2006-01-02 15:04:05"),
|
||||||
|
issue.Title,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Output(outputValue, headers, values)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdIssuesCreate represents a sub command of issues to create issue
|
||||||
|
var CmdIssuesCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create an issue on repository",
|
||||||
|
Description: `Create an issue on repository`,
|
||||||
|
Action: runIssuesCreate,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "title",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "issue title to create",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "body",
|
||||||
|
Aliases: []string{"b"},
|
||||||
|
Usage: "issue body to create",
|
||||||
|
},
|
||||||
|
}, LoginRepoFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runIssuesCreate(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
|
||||||
|
_, err := login.Client().CreateIssue(owner, repo, gitea.CreateIssueOption{
|
||||||
|
Title: ctx.String("title"),
|
||||||
|
Body: ctx.String("body"),
|
||||||
|
// TODO:
|
||||||
|
//Assignee string `json:"assignee"`
|
||||||
|
//Assignees []string `json:"assignees"`
|
||||||
|
//Deadline *time.Time `json:"due_date"`
|
||||||
|
//Milestone int64 `json:"milestone"`
|
||||||
|
//Labels []int64 `json:"labels"`
|
||||||
|
//Closed bool `json:"closed"`
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CmdIssuesReopen represents a sub command of issues to open an issue
|
||||||
|
var CmdIssuesReopen = cli.Command{
|
||||||
|
Name: "reopen",
|
||||||
|
Aliases: []string{"open"},
|
||||||
|
Usage: "Change state of an issue to 'open'",
|
||||||
|
Description: `Change state of an issue to 'open'`,
|
||||||
|
ArgsUsage: "<issue index>",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
var s = gitea.StateOpen
|
||||||
|
return editIssueState(ctx, gitea.EditIssueOption{State: &s})
|
||||||
|
},
|
||||||
|
Flags: AllDefaultFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdIssuesClose represents a sub command of issues to close an issue
|
||||||
|
var CmdIssuesClose = cli.Command{
|
||||||
|
Name: "close",
|
||||||
|
Usage: "Change state of an issue to 'closed'",
|
||||||
|
Description: `Change state of an issue to 'closed'`,
|
||||||
|
ArgsUsage: "<issue index>",
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
var s = gitea.StateClosed
|
||||||
|
return editIssueState(ctx, gitea.EditIssueOption{State: &s})
|
||||||
|
},
|
||||||
|
Flags: AllDefaultFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
// editIssueState abstracts the arg parsing to edit the given issue
|
||||||
|
func editIssueState(ctx *cli.Context, opts gitea.EditIssueOption) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
if ctx.Args().Len() == 0 {
|
||||||
|
log.Fatal(ctx.Command.ArgsUsage)
|
||||||
|
}
|
||||||
|
|
||||||
|
index, err := argToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = login.Client().EditIssue(owner, repo, index, opts)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
// Copyright 2018 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 issues
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdIssuesClose represents a sub command of issues to close an issue
|
|
||||||
var CmdIssuesClose = cli.Command{
|
|
||||||
Name: "close",
|
|
||||||
Usage: "Change state of an issue to 'closed'",
|
|
||||||
Description: `Change state of an issue to 'closed'`,
|
|
||||||
ArgsUsage: "<issue index>",
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
var s = gitea.StateClosed
|
|
||||||
return editIssueState(ctx, gitea.EditIssueOption{State: &s})
|
|
||||||
},
|
|
||||||
Flags: flags.AllDefaultFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
// editIssueState abstracts the arg parsing to edit the given issue
|
|
||||||
func editIssueState(cmd *cli.Context, opts gitea.EditIssueOption) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
if ctx.Args().Len() == 0 {
|
|
||||||
return fmt.Errorf(ctx.Command.ArgsUsage)
|
|
||||||
}
|
|
||||||
|
|
||||||
index, err := utils.ArgToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
issue, _, err := ctx.Login.Client().EditIssue(ctx.Owner, ctx.Repo, index, opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
print.IssueDetails(issue)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package issues
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/interact"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdIssuesCreate represents a sub command of issues to create issue
|
|
||||||
var CmdIssuesCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Usage: "Create an issue on repository",
|
|
||||||
Description: `Create an issue on repository`,
|
|
||||||
Action: runIssuesCreate,
|
|
||||||
Flags: flags.IssuePREditFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runIssuesCreate(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
if ctx.NumFlags() == 0 {
|
|
||||||
return interact.CreateIssue(ctx.Login, ctx.Owner, ctx.Repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts, err := flags.GetIssuePREditFlags(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.CreateIssue(
|
|
||||||
ctx.Login,
|
|
||||||
ctx.Owner,
|
|
||||||
ctx.Repo,
|
|
||||||
*opts,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package issues
|
|
||||||
|
|
||||||
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 issueFieldsFlag = flags.FieldsFlag(print.IssueFields, []string{
|
|
||||||
"index", "title", "state", "author", "milestone", "labels",
|
|
||||||
})
|
|
||||||
|
|
||||||
// CmdIssuesList represents a sub command of issues to list issues
|
|
||||||
var CmdIssuesList = cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Usage: "List issues of the repository",
|
|
||||||
Description: `List issues of the repository`,
|
|
||||||
Action: RunIssuesList,
|
|
||||||
Flags: append([]cli.Flag{issueFieldsFlag}, flags.IssuePRFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunIssuesList list issues
|
|
||||||
func RunIssuesList(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
state := gitea.StateOpen
|
|
||||||
switch ctx.String("state") {
|
|
||||||
case "all":
|
|
||||||
state = gitea.StateAll
|
|
||||||
case "open":
|
|
||||||
state = gitea.StateOpen
|
|
||||||
case "closed":
|
|
||||||
state = gitea.StateClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
issues, _, err := ctx.Login.Client().ListRepoIssues(ctx.Owner, ctx.Repo, gitea.ListIssueOption{
|
|
||||||
ListOptions: ctx.GetListOptions(),
|
|
||||||
State: state,
|
|
||||||
Type: gitea.IssueTypeIssue,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fields, err := issueFieldsFlag.GetValues(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
print.IssuesPullsList(issues, ctx.Output, fields)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package issues
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdIssuesReopen represents a sub command of issues to open an issue
|
|
||||||
var CmdIssuesReopen = cli.Command{
|
|
||||||
Name: "reopen",
|
|
||||||
Aliases: []string{"open"},
|
|
||||||
Usage: "Change state of an issue to 'open'",
|
|
||||||
Description: `Change state of an issue to 'open'`,
|
|
||||||
ArgsUsage: "<issue index>",
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
var s = gitea.StateOpen
|
|
||||||
return editIssueState(ctx, gitea.EditIssueOption{State: &s})
|
|
||||||
},
|
|
||||||
Flags: flags.AllDefaultFlags,
|
|
||||||
}
|
|
252
cmd/labels.go
252
cmd/labels.go
@ -5,36 +5,262 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/labels"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdLabels represents to operate repositories' labels.
|
// CmdLabels represents to operate repositories' labels.
|
||||||
var CmdLabels = cli.Command{
|
var CmdLabels = cli.Command{
|
||||||
Name: "labels",
|
Name: "labels",
|
||||||
Aliases: []string{"label"},
|
|
||||||
Category: catEntities,
|
|
||||||
Usage: "Manage issue labels",
|
Usage: "Manage issue labels",
|
||||||
Description: `Manage issue labels`,
|
Description: `Manage issue labels`,
|
||||||
Action: runLabels,
|
Action: runLabels,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&labels.CmdLabelsList,
|
&CmdLabelCreate,
|
||||||
&labels.CmdLabelCreate,
|
&CmdLabelUpdate,
|
||||||
&labels.CmdLabelUpdate,
|
&CmdLabelDelete,
|
||||||
&labels.CmdLabelDelete,
|
|
||||||
},
|
},
|
||||||
Flags: labels.CmdLabelsList.Flags,
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "save",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "Save all the labels as a file",
|
||||||
|
},
|
||||||
|
}, AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLabels(ctx *cli.Context) error {
|
func runLabels(ctx *cli.Context) error {
|
||||||
if ctx.Args().Len() == 1 {
|
login, owner, repo := initCommand()
|
||||||
return runLabelsDetails(ctx)
|
|
||||||
|
headers := []string{
|
||||||
|
"Index",
|
||||||
|
"Color",
|
||||||
|
"Name",
|
||||||
|
"Description",
|
||||||
}
|
}
|
||||||
return labels.RunLabelsList(ctx)
|
|
||||||
|
var values [][]string
|
||||||
|
|
||||||
|
labels, err := login.Client().ListRepoLabels(owner, repo, gitea.ListLabelsOptions{})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(labels) == 0 {
|
||||||
|
Output(outputValue, headers, values)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fPath := ctx.String("save")
|
||||||
|
if len(fPath) > 0 {
|
||||||
|
f, err := os.Create(fPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
for _, label := range labels {
|
||||||
|
fmt.Fprintf(f, "#%s %s\n", label.Color, label.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, label := range labels {
|
||||||
|
values = append(
|
||||||
|
values,
|
||||||
|
[]string{
|
||||||
|
strconv.FormatInt(label.ID, 10),
|
||||||
|
label.Color,
|
||||||
|
label.Name,
|
||||||
|
label.Description,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Output(outputValue, headers, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLabelsDetails(ctx *cli.Context) error {
|
// CmdLabelCreate represents a sub command of labels to create label.
|
||||||
return fmt.Errorf("Not yet implemented")
|
var CmdLabelCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create a label",
|
||||||
|
Description: `Create a label`,
|
||||||
|
Action: runLabelCreate,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "label name",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "color",
|
||||||
|
Usage: "label color value",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "description",
|
||||||
|
Usage: "label description",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "file",
|
||||||
|
Usage: "indicate a label file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitLabelLine(line string) (string, string, string) {
|
||||||
|
fields := strings.SplitN(line, ";", 2)
|
||||||
|
var color, name, description string
|
||||||
|
if len(fields) < 1 {
|
||||||
|
return "", "", ""
|
||||||
|
} else if len(fields) >= 2 {
|
||||||
|
description = strings.TrimSpace(fields[1])
|
||||||
|
}
|
||||||
|
fields = strings.Fields(fields[0])
|
||||||
|
if len(fields) <= 0 {
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
color = fields[0]
|
||||||
|
if len(fields) == 2 {
|
||||||
|
name = fields[1]
|
||||||
|
} else if len(fields) > 2 {
|
||||||
|
name = strings.Join(fields[1:], " ")
|
||||||
|
}
|
||||||
|
return color, name, description
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLabelCreate(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
|
||||||
|
labelFile := ctx.String("file")
|
||||||
|
var err error
|
||||||
|
if len(labelFile) == 0 {
|
||||||
|
_, err = login.Client().CreateLabel(owner, repo, gitea.CreateLabelOption{
|
||||||
|
Name: ctx.String("name"),
|
||||||
|
Color: ctx.String("color"),
|
||||||
|
Description: ctx.String("description"),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
f, err := os.Open(labelFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
var i = 1
|
||||||
|
// FIXME: if Gitea's API support create multiple labels once, we should move to that API.
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
color, name, description := splitLabelLine(line)
|
||||||
|
if color == "" || name == "" {
|
||||||
|
log.Printf("Line %d ignored because lack of enough fields: %s\n", i, line)
|
||||||
|
} else {
|
||||||
|
_, err = login.Client().CreateLabel(owner, repo, gitea.CreateLabelOption{
|
||||||
|
Name: name,
|
||||||
|
Color: color,
|
||||||
|
Description: description,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdLabelUpdate represents a sub command of labels to update label.
|
||||||
|
var CmdLabelUpdate = cli.Command{
|
||||||
|
Name: "update",
|
||||||
|
Usage: "Update a label",
|
||||||
|
Description: `Update a label`,
|
||||||
|
Action: runLabelUpdate,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "id",
|
||||||
|
Usage: "label id",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "label name",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "color",
|
||||||
|
Usage: "label color value",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "description",
|
||||||
|
Usage: "label description",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLabelUpdate(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
|
||||||
|
id := ctx.Int64("id")
|
||||||
|
var pName, pColor, pDescription *string
|
||||||
|
name := ctx.String("name")
|
||||||
|
if name != "" {
|
||||||
|
pName = &name
|
||||||
|
}
|
||||||
|
|
||||||
|
color := ctx.String("color")
|
||||||
|
if color != "" {
|
||||||
|
pColor = &color
|
||||||
|
}
|
||||||
|
|
||||||
|
description := ctx.String("description")
|
||||||
|
if description != "" {
|
||||||
|
pDescription = &description
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
_, err = login.Client().EditLabel(owner, repo, id, gitea.EditLabelOption{
|
||||||
|
Name: pName,
|
||||||
|
Color: pColor,
|
||||||
|
Description: pDescription,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdLabelDelete represents a sub command of labels to delete label.
|
||||||
|
var CmdLabelDelete = cli.Command{
|
||||||
|
Name: "delete",
|
||||||
|
Usage: "Delete a label",
|
||||||
|
Description: `Delete a label`,
|
||||||
|
Action: runLabelCreate,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "id",
|
||||||
|
Usage: "label id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLabelDelete(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
|
||||||
|
err := login.Client().DeleteLabel(owner, repo, ctx.Int64("id"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package labels
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdLabelCreate represents a sub command of labels to create label.
|
|
||||||
var CmdLabelCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Usage: "Create a label",
|
|
||||||
Description: `Create a label`,
|
|
||||||
Action: runLabelCreate,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Usage: "label name",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "color",
|
|
||||||
Usage: "label color value",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "description",
|
|
||||||
Usage: "label description",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "file",
|
|
||||||
Usage: "indicate a label file",
|
|
||||||
},
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLabelCreate(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
labelFile := ctx.String("file")
|
|
||||||
var err error
|
|
||||||
if len(labelFile) == 0 {
|
|
||||||
_, _, err = ctx.Login.Client().CreateLabel(ctx.Owner, ctx.Repo, gitea.CreateLabelOption{
|
|
||||||
Name: ctx.String("name"),
|
|
||||||
Color: ctx.String("color"),
|
|
||||||
Description: ctx.String("description"),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
f, err := os.Open(labelFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(f)
|
|
||||||
var i = 1
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
color, name, description := splitLabelLine(line)
|
|
||||||
if color == "" || name == "" {
|
|
||||||
log.Printf("Line %d ignored because lack of enough fields: %s\n", i, line)
|
|
||||||
} else {
|
|
||||||
_, _, err = ctx.Login.Client().CreateLabel(ctx.Owner, ctx.Repo, gitea.CreateLabelOption{
|
|
||||||
Name: name,
|
|
||||||
Color: color,
|
|
||||||
Description: description,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitLabelLine(line string) (string, string, string) {
|
|
||||||
fields := strings.SplitN(line, ";", 2)
|
|
||||||
var color, name, description string
|
|
||||||
if len(fields) < 1 {
|
|
||||||
return "", "", ""
|
|
||||||
} else if len(fields) >= 2 {
|
|
||||||
description = strings.TrimSpace(fields[1])
|
|
||||||
}
|
|
||||||
fields = strings.Fields(fields[0])
|
|
||||||
if len(fields) <= 0 {
|
|
||||||
return "", "", ""
|
|
||||||
}
|
|
||||||
color = fields[0]
|
|
||||||
if len(fields) == 2 {
|
|
||||||
name = fields[1]
|
|
||||||
} else if len(fields) > 2 {
|
|
||||||
name = strings.Join(fields[1:], " ")
|
|
||||||
}
|
|
||||||
return color, name, description
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package labels
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdLabelDelete represents a sub command of labels to delete label.
|
|
||||||
var CmdLabelDelete = cli.Command{
|
|
||||||
Name: "delete",
|
|
||||||
Aliases: []string{"rm"},
|
|
||||||
Usage: "Delete a label",
|
|
||||||
Description: `Delete a label`,
|
|
||||||
Action: runLabelDelete,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "id",
|
|
||||||
Usage: "label id",
|
|
||||||
},
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLabelDelete(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
_, err := ctx.Login.Client().DeleteLabel(ctx.Owner, ctx.Repo, ctx.Int64("id"))
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package labels
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/print"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdLabelsList represents a sub command of labels to list labels
|
|
||||||
var CmdLabelsList = cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Usage: "List labels",
|
|
||||||
Description: "List labels",
|
|
||||||
Action: RunLabelsList,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "save",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "Save all the labels as a file",
|
|
||||||
},
|
|
||||||
&flags.PaginationPageFlag,
|
|
||||||
&flags.PaginationLimitFlag,
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunLabelsList list labels.
|
|
||||||
func RunLabelsList(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
labels, _, err := client.ListRepoLabels(ctx.Owner, ctx.Repo, gitea.ListLabelsOptions{
|
|
||||||
ListOptions: ctx.GetListOptions(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.IsSet("save") {
|
|
||||||
return task.LabelsExport(labels, ctx.String("save"))
|
|
||||||
}
|
|
||||||
|
|
||||||
print.LabelsList(labels, ctx.Output)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package labels
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdLabelUpdate represents a sub command of labels to update label.
|
|
||||||
var CmdLabelUpdate = cli.Command{
|
|
||||||
Name: "update",
|
|
||||||
Usage: "Update a label",
|
|
||||||
Description: `Update a label`,
|
|
||||||
Action: runLabelUpdate,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "id",
|
|
||||||
Usage: "label id",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Usage: "label name",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "color",
|
|
||||||
Usage: "label color value",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "description",
|
|
||||||
Usage: "label description",
|
|
||||||
},
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLabelUpdate(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
id := ctx.Int64("id")
|
|
||||||
var pName, pColor, pDescription *string
|
|
||||||
name := ctx.String("name")
|
|
||||||
if name != "" {
|
|
||||||
pName = &name
|
|
||||||
}
|
|
||||||
|
|
||||||
color := ctx.String("color")
|
|
||||||
if color != "" {
|
|
||||||
pColor = &color
|
|
||||||
}
|
|
||||||
|
|
||||||
description := ctx.String("description")
|
|
||||||
if description != "" {
|
|
||||||
pDescription = &description
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
_, _, err = ctx.Login.Client().EditLabel(ctx.Owner, ctx.Repo, id, gitea.EditLabelOption{
|
|
||||||
Name: pName,
|
|
||||||
Color: pColor,
|
|
||||||
Description: pDescription,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -2,7 +2,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
package labels
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
111
cmd/log.go
Normal file
111
cmd/log.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// Copyright 2018 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"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/olekukonko/tablewriter"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
showLog bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// Println println content according the flag
|
||||||
|
func Println(a ...interface{}) {
|
||||||
|
if showLog {
|
||||||
|
fmt.Println(a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf printf content according the flag
|
||||||
|
func Printf(format string, a ...interface{}) {
|
||||||
|
if showLog {
|
||||||
|
fmt.Printf(format, a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error println content as an error information
|
||||||
|
func Error(a ...interface{}) {
|
||||||
|
fmt.Println(a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf printf content as an error information
|
||||||
|
func Errorf(format string, a ...interface{}) {
|
||||||
|
fmt.Printf(format, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputtable prints structured data as table
|
||||||
|
func outputtable(headers []string, values [][]string) {
|
||||||
|
table := tablewriter.NewWriter(os.Stdout)
|
||||||
|
if len(headers) > 0 {
|
||||||
|
table.SetHeader(headers)
|
||||||
|
}
|
||||||
|
for _, value := range values {
|
||||||
|
table.Append(value)
|
||||||
|
}
|
||||||
|
table.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputsimple prints structured data as space delimited value
|
||||||
|
func outputsimple(headers []string, values [][]string) {
|
||||||
|
for _, value := range values {
|
||||||
|
fmt.Printf(strings.Join(value, " "))
|
||||||
|
fmt.Printf("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputdsv prints structured data as delimiter separated value format
|
||||||
|
func outputdsv(headers []string, values [][]string, delimiterOpt ...string) {
|
||||||
|
delimiter := ","
|
||||||
|
if len(delimiterOpt) > 0 {
|
||||||
|
delimiter = delimiterOpt[0]
|
||||||
|
}
|
||||||
|
fmt.Println("\"" + strings.Join(headers, "\""+delimiter+"\"") + "\"")
|
||||||
|
for _, value := range values {
|
||||||
|
fmt.Printf("\"")
|
||||||
|
fmt.Printf(strings.Join(value, "\""+delimiter+"\""))
|
||||||
|
fmt.Printf("\"")
|
||||||
|
fmt.Printf("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputyaml prints structured data as yaml
|
||||||
|
func outputyaml(headers []string, values [][]string) {
|
||||||
|
for _, value := range values {
|
||||||
|
fmt.Println("-")
|
||||||
|
for j, val := range value {
|
||||||
|
intVal, _ := strconv.Atoi(val)
|
||||||
|
if strconv.Itoa(intVal) == val {
|
||||||
|
fmt.Printf(" %s: %s\n", headers[j], val)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" %s: '%s'\n", headers[j], val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output provides general function to convert given information
|
||||||
|
// into several outputs
|
||||||
|
func Output(output string, headers []string, values [][]string) {
|
||||||
|
switch {
|
||||||
|
case output == "" || output == "table":
|
||||||
|
outputtable(headers, values)
|
||||||
|
case output == "csv":
|
||||||
|
outputdsv(headers, values, ",")
|
||||||
|
case output == "simple":
|
||||||
|
outputsimple(headers, values)
|
||||||
|
case output == "tsv":
|
||||||
|
outputdsv(headers, values, "\t")
|
||||||
|
case output == "yaml":
|
||||||
|
outputyaml(headers, values)
|
||||||
|
default:
|
||||||
|
Errorf("unknown output type '" + output + "', available types are:\n- csv: comma-separated values\n- simple: space-separated values\n- table: auto-aligned table format (default)\n- tsv: tab-separated values\n- yaml: YAML format\n")
|
||||||
|
}
|
||||||
|
}
|
160
cmd/login.go
160
cmd/login.go
@ -1,51 +1,161 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
// Copyright 2018 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.
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/login"
|
"code.gitea.io/sdk/gitea"
|
||||||
"code.gitea.io/tea/modules/config"
|
|
||||||
"code.gitea.io/tea/modules/print"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdLogin represents to login a gitea server.
|
// CmdLogin represents to login a gitea server.
|
||||||
var CmdLogin = cli.Command{
|
var CmdLogin = cli.Command{
|
||||||
Name: "logins",
|
Name: "login",
|
||||||
Aliases: []string{"login"},
|
|
||||||
Category: catSetup,
|
|
||||||
Usage: "Log in to a Gitea server",
|
Usage: "Log in to a Gitea server",
|
||||||
Description: `Log in to a Gitea server`,
|
Description: `Log in to a Gitea server`,
|
||||||
ArgsUsage: "[<login name>]",
|
Action: runLoginList,
|
||||||
Action: runLogins,
|
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&login.CmdLoginList,
|
&cmdLoginList,
|
||||||
&login.CmdLoginAdd,
|
&cmdLoginAdd,
|
||||||
&login.CmdLoginEdit,
|
|
||||||
&login.CmdLoginDelete,
|
|
||||||
&login.CmdLoginSetDefault,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLogins(ctx *cli.Context) error {
|
// CmdLogin represents to login a gitea server.
|
||||||
if ctx.Args().Len() == 1 {
|
var cmdLoginAdd = cli.Command{
|
||||||
return runLoginDetail(ctx.Args().First())
|
Name: "add",
|
||||||
}
|
Usage: "Add a Gitea login",
|
||||||
return login.RunLoginList(ctx)
|
Description: `Add a Gitea login`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Usage: "Login name",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "url",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Value: "https://try.gitea.io",
|
||||||
|
EnvVars: []string{"GITEA_SERVER_URL"},
|
||||||
|
Usage: "Server URL",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "token",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Value: "",
|
||||||
|
EnvVars: []string{"GITEA_SERVER_TOKEN"},
|
||||||
|
Usage: "Access token. Can be obtained from Settings > Applications",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "ssh-key",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "Path to a SSH key to use for pull/push operations",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "insecure",
|
||||||
|
Aliases: []string{"i"},
|
||||||
|
Usage: "Disable TLS verification",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: runLoginAdd,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLoginDetail(name string) error {
|
func runLoginAdd(ctx *cli.Context) error {
|
||||||
l := config.GetLoginByName(name)
|
if !ctx.IsSet("url") {
|
||||||
if l == nil {
|
log.Fatal("You have to input Gitea server URL")
|
||||||
fmt.Printf("Login '%s' do not exist\n\n", name)
|
}
|
||||||
return nil
|
if !ctx.IsSet("token") {
|
||||||
|
log.Fatal("No token found")
|
||||||
|
}
|
||||||
|
if !ctx.IsSet("name") {
|
||||||
|
log.Fatal("You have to set a name for the login")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := loadConfig(yamlConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to load config file " + yamlConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := gitea.NewClient(ctx.String("url"), ctx.String("token"))
|
||||||
|
if ctx.Bool("insecure") {
|
||||||
|
cookieJar, _ := cookiejar.New(nil)
|
||||||
|
|
||||||
|
client.SetHTTPClient(&http.Client{
|
||||||
|
Jar: cookieJar,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
u, err := client.GetMyUserInfo()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Login successful! Login name " + u.UserName)
|
||||||
|
|
||||||
|
err = addLogin(Login{
|
||||||
|
Name: ctx.String("name"),
|
||||||
|
URL: ctx.String("url"),
|
||||||
|
Token: ctx.String("token"),
|
||||||
|
Insecure: ctx.Bool("insecure"),
|
||||||
|
SSHKey: ctx.String("ssh-key"),
|
||||||
|
User: u.UserName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveConfig(yamlConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
print.LoginDetails(l)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdLogin represents to login a gitea server.
|
||||||
|
var cmdLoginList = cli.Command{
|
||||||
|
Name: "ls",
|
||||||
|
Usage: "List Gitea logins",
|
||||||
|
Description: `List Gitea logins`,
|
||||||
|
Action: runLoginList,
|
||||||
|
Flags: []cli.Flag{&OutputFlag},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLoginList(ctx *cli.Context) error {
|
||||||
|
err := loadConfig(yamlConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to load config file " + yamlConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Name",
|
||||||
|
"URL",
|
||||||
|
"SSHHost",
|
||||||
|
}
|
||||||
|
|
||||||
|
var values [][]string
|
||||||
|
|
||||||
|
for _, l := range config.Logins {
|
||||||
|
values = append(values, []string{
|
||||||
|
l.Name,
|
||||||
|
l.URL,
|
||||||
|
l.GetSSHHost(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Output(outputValue, headers, values)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package login
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/modules/interact"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdLoginAdd represents to login a gitea server.
|
|
||||||
var CmdLoginAdd = cli.Command{
|
|
||||||
Name: "add",
|
|
||||||
Usage: "Add a Gitea login",
|
|
||||||
Description: `Add a Gitea login, without args it will create one interactively`,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Aliases: []string{"n"},
|
|
||||||
Usage: "Login name",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "url",
|
|
||||||
Aliases: []string{"u"},
|
|
||||||
Value: "https://gitea.com",
|
|
||||||
EnvVars: []string{"GITEA_SERVER_URL"},
|
|
||||||
Usage: "Server URL",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "token",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Value: "",
|
|
||||||
EnvVars: []string{"GITEA_SERVER_TOKEN"},
|
|
||||||
Usage: "Access token. Can be obtained from Settings > Applications",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "user",
|
|
||||||
Value: "",
|
|
||||||
EnvVars: []string{"GITEA_SERVER_USER"},
|
|
||||||
Usage: "User for basic auth (will create token)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "password",
|
|
||||||
Aliases: []string{"pwd"},
|
|
||||||
Value: "",
|
|
||||||
EnvVars: []string{"GITEA_SERVER_PASSWORD"},
|
|
||||||
Usage: "Password for basic auth (will create token)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "ssh-key",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "Path to a SSH key to use, overrides auto-discovery",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "insecure",
|
|
||||||
Aliases: []string{"i"},
|
|
||||||
Usage: "Disable TLS verification",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: runLoginAdd,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLoginAdd(ctx *cli.Context) error {
|
|
||||||
// if no args create login interactive
|
|
||||||
if ctx.NumFlags() == 0 {
|
|
||||||
return interact.CreateLogin()
|
|
||||||
}
|
|
||||||
|
|
||||||
// else use args to add login
|
|
||||||
return task.CreateLogin(
|
|
||||||
ctx.String("name"),
|
|
||||||
ctx.String("token"),
|
|
||||||
ctx.String("user"),
|
|
||||||
ctx.String("password"),
|
|
||||||
ctx.String("ssh-key"),
|
|
||||||
ctx.String("url"),
|
|
||||||
ctx.Bool("insecure"))
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package login
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/config"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdLoginSetDefault represents to login a gitea server.
|
|
||||||
var CmdLoginSetDefault = cli.Command{
|
|
||||||
Name: "default",
|
|
||||||
Usage: "Get or Set Default Login",
|
|
||||||
Description: `Get or Set Default Login`,
|
|
||||||
ArgsUsage: "<Login>",
|
|
||||||
Action: runLoginSetDefault,
|
|
||||||
Flags: []cli.Flag{&flags.OutputFlag},
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLoginSetDefault(ctx *cli.Context) error {
|
|
||||||
if ctx.Args().Len() == 0 {
|
|
||||||
l, err := config.GetDefaultLogin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("Default Login: %s\n", l.Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
name := ctx.Args().First()
|
|
||||||
return config.SetDefaultLogin(name)
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package login
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/config"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdLoginDelete is a command to delete a login
|
|
||||||
var CmdLoginDelete = cli.Command{
|
|
||||||
Name: "delete",
|
|
||||||
Aliases: []string{"rm"},
|
|
||||||
Usage: "Remove a Gitea login",
|
|
||||||
Description: `Remove a Gitea login`,
|
|
||||||
ArgsUsage: "<login name>",
|
|
||||||
Action: RunLoginDelete,
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunLoginDelete runs the action of a login delete command
|
|
||||||
func RunLoginDelete(ctx *cli.Context) error {
|
|
||||||
logins, err := config.GetLogins()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var name string
|
|
||||||
|
|
||||||
if len(ctx.Args().First()) != 0 {
|
|
||||||
name = ctx.Args().First()
|
|
||||||
} else if len(logins) == 1 {
|
|
||||||
name = logins[0].Name
|
|
||||||
} else {
|
|
||||||
return errors.New("Please specify a login name")
|
|
||||||
}
|
|
||||||
|
|
||||||
return config.DeleteLogin(name)
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package login
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/config"
|
|
||||||
|
|
||||||
"github.com/skratchdot/open-golang/open"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdLoginEdit represents to login a gitea server.
|
|
||||||
var CmdLoginEdit = cli.Command{
|
|
||||||
Name: "edit",
|
|
||||||
Aliases: []string{"e"},
|
|
||||||
Usage: "Edit Gitea logins",
|
|
||||||
Description: `Edit Gitea logins`,
|
|
||||||
Action: runLoginEdit,
|
|
||||||
Flags: []cli.Flag{&flags.OutputFlag},
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLoginEdit(_ *cli.Context) error {
|
|
||||||
return open.Start(config.GetConfigPath())
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package login
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/config"
|
|
||||||
"code.gitea.io/tea/modules/print"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdLoginList represents to login a gitea server.
|
|
||||||
var CmdLoginList = cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Usage: "List Gitea logins",
|
|
||||||
Description: `List Gitea logins`,
|
|
||||||
Action: RunLoginList,
|
|
||||||
Flags: []cli.Flag{&flags.OutputFlag},
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunLoginList list all logins
|
|
||||||
func RunLoginList(cmd *cli.Context) error {
|
|
||||||
logins, err := config.GetLogins()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
print.LoginsList(logins, cmd.String("output"))
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,11 +1,13 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
// Copyright 2018 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.
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/tea/cmd/login"
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@ -13,9 +15,47 @@ import (
|
|||||||
// CmdLogout represents to logout a gitea server.
|
// CmdLogout represents to logout a gitea server.
|
||||||
var CmdLogout = cli.Command{
|
var CmdLogout = cli.Command{
|
||||||
Name: "logout",
|
Name: "logout",
|
||||||
Category: catSetup,
|
|
||||||
Usage: "Log out from a Gitea server",
|
Usage: "Log out from a Gitea server",
|
||||||
Description: `Log out from a Gitea server`,
|
Description: `Log out from a Gitea server`,
|
||||||
ArgsUsage: "<login name>",
|
Action: runLogout,
|
||||||
Action: login.RunLoginDelete,
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Usage: "Login name to remove",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLogout(ctx *cli.Context) error {
|
||||||
|
var name string
|
||||||
|
if len(os.Args) == 3 {
|
||||||
|
name = os.Args[2]
|
||||||
|
} else if ctx.IsSet("name") {
|
||||||
|
name = ctx.String("name")
|
||||||
|
} else {
|
||||||
|
return errors.New("Please specify a login name")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := loadConfig(yamlConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to load config file " + yamlConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx = -1
|
||||||
|
for i, l := range config.Logins {
|
||||||
|
if l.Name == name {
|
||||||
|
idx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idx > -1 {
|
||||||
|
config.Logins = append(config.Logins[:idx], config.Logins[idx+1:]...)
|
||||||
|
err = saveConfig(yamlConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to save config file " + yamlConfigPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/milestones"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/print"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdMilestones represents to operate repositories milestones.
|
|
||||||
var CmdMilestones = cli.Command{
|
|
||||||
Name: "milestones",
|
|
||||||
Aliases: []string{"milestone", "ms"},
|
|
||||||
Category: catEntities,
|
|
||||||
Usage: "List and create milestones",
|
|
||||||
Description: `List and create milestones`,
|
|
||||||
ArgsUsage: "[<milestone name>]",
|
|
||||||
Action: runMilestones,
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
&milestones.CmdMilestonesList,
|
|
||||||
&milestones.CmdMilestonesCreate,
|
|
||||||
&milestones.CmdMilestonesClose,
|
|
||||||
&milestones.CmdMilestonesDelete,
|
|
||||||
&milestones.CmdMilestonesReopen,
|
|
||||||
&milestones.CmdMilestonesIssues,
|
|
||||||
},
|
|
||||||
Flags: milestones.CmdMilestonesList.Flags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runMilestones(ctx *cli.Context) error {
|
|
||||||
if ctx.Args().Len() == 1 {
|
|
||||||
return runMilestoneDetail(ctx, ctx.Args().First())
|
|
||||||
}
|
|
||||||
return milestones.RunMilestonesList(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runMilestoneDetail(cmd *cli.Context, name string) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
milestone, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
print.MilestoneDetails(milestone)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package milestones
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdMilestonesClose represents a sub command of milestones to close an milestone
|
|
||||||
var CmdMilestonesClose = cli.Command{
|
|
||||||
Name: "close",
|
|
||||||
Usage: "Change state of an milestone to 'closed'",
|
|
||||||
Description: `Change state of an milestone to 'closed'`,
|
|
||||||
ArgsUsage: "<milestone name>",
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
if ctx.Bool("force") {
|
|
||||||
return deleteMilestone(ctx)
|
|
||||||
}
|
|
||||||
return editMilestoneStatus(ctx, true)
|
|
||||||
},
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "force",
|
|
||||||
Aliases: []string{"f"},
|
|
||||||
Usage: "delete milestone",
|
|
||||||
},
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package milestones
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/interact"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/araddon/dateparse"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdMilestonesCreate represents a sub command of milestones to create milestone
|
|
||||||
var CmdMilestonesCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Usage: "Create an milestone on repository",
|
|
||||||
Description: `Create an milestone on repository`,
|
|
||||||
Action: runMilestonesCreate,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "title",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "milestone title to create",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "description",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "milestone description to create",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "deadline",
|
|
||||||
Aliases: []string{"expires", "x"},
|
|
||||||
Usage: "set milestone deadline (default is no due date)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "state",
|
|
||||||
Usage: "set milestone state (default is open)",
|
|
||||||
DefaultText: "open",
|
|
||||||
},
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runMilestonesCreate(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
|
|
||||||
date := ctx.String("deadline")
|
|
||||||
deadline := &time.Time{}
|
|
||||||
if date != "" {
|
|
||||||
t, err := dateparse.ParseAny(date)
|
|
||||||
if err == nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
deadline = &t
|
|
||||||
}
|
|
||||||
|
|
||||||
state := gitea.StateOpen
|
|
||||||
if ctx.String("state") == "closed" {
|
|
||||||
state = gitea.StateClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.NumFlags() == 0 {
|
|
||||||
return interact.CreateMilestone(ctx.Login, ctx.Owner, ctx.Repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.CreateMilestone(
|
|
||||||
ctx.Login,
|
|
||||||
ctx.Owner,
|
|
||||||
ctx.Repo,
|
|
||||||
ctx.String("title"),
|
|
||||||
ctx.String("description"),
|
|
||||||
deadline,
|
|
||||||
state,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package milestones
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdMilestonesDelete represents a sub command of milestones to delete an milestone
|
|
||||||
var CmdMilestonesDelete = cli.Command{
|
|
||||||
Name: "delete",
|
|
||||||
Aliases: []string{"rm"},
|
|
||||||
Usage: "delete a milestone",
|
|
||||||
Description: "delete a milestone",
|
|
||||||
ArgsUsage: "<milestone name>",
|
|
||||||
Action: deleteMilestone,
|
|
||||||
Flags: flags.AllDefaultFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteMilestone(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
_, err := client.DeleteMilestoneByName(ctx.Owner, ctx.Repo, ctx.Args().First())
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,183 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package milestones
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
var CmdMilestonesIssues = cli.Command{
|
|
||||||
Name: "issues",
|
|
||||||
Aliases: []string{"i"},
|
|
||||||
Usage: "manage issue/pull of an milestone",
|
|
||||||
Description: "manage issue/pull of an milestone",
|
|
||||||
ArgsUsage: "<milestone name>",
|
|
||||||
Action: runMilestoneIssueList,
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
&CmdMilestoneAddIssue,
|
|
||||||
&CmdMilestoneRemoveIssue,
|
|
||||||
},
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "state",
|
|
||||||
Usage: "Filter by issue state (all|open|closed)",
|
|
||||||
DefaultText: "open",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "kind",
|
|
||||||
Usage: "Filter by kind (issue|pull)",
|
|
||||||
},
|
|
||||||
&flags.PaginationPageFlag,
|
|
||||||
&flags.PaginationLimitFlag,
|
|
||||||
msIssuesFieldsFlag,
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdMilestoneAddIssue represents a sub command of milestone issues to add an issue/pull to an milestone
|
|
||||||
var CmdMilestoneAddIssue = cli.Command{
|
|
||||||
Name: "add",
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "Add an issue/pull to an milestone",
|
|
||||||
Description: "Add an issue/pull to an milestone",
|
|
||||||
ArgsUsage: "<milestone name> <issue/pull index>",
|
|
||||||
Action: runMilestoneIssueAdd,
|
|
||||||
Flags: flags.AllDefaultFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdMilestoneRemoveIssue represents a sub command of milestones to remove an issue/pull from an milestone
|
|
||||||
var CmdMilestoneRemoveIssue = cli.Command{
|
|
||||||
Name: "remove",
|
|
||||||
Aliases: []string{"r"},
|
|
||||||
Usage: "Remove an issue/pull to an milestone",
|
|
||||||
Description: "Remove an issue/pull to an milestone",
|
|
||||||
ArgsUsage: "<milestone name> <issue/pull index>",
|
|
||||||
Action: runMilestoneIssueRemove,
|
|
||||||
Flags: flags.AllDefaultFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runMilestoneIssueList(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
state := gitea.StateOpen
|
|
||||||
switch ctx.String("state") {
|
|
||||||
case "all":
|
|
||||||
state = gitea.StateAll
|
|
||||||
case "closed":
|
|
||||||
state = gitea.StateClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
kind := gitea.IssueTypeAll
|
|
||||||
switch ctx.String("kind") {
|
|
||||||
case "issue":
|
|
||||||
kind = gitea.IssueTypeIssue
|
|
||||||
case "pull":
|
|
||||||
kind = gitea.IssueTypePull
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Args().Len() != 1 {
|
|
||||||
return fmt.Errorf("Must specify milestone name")
|
|
||||||
}
|
|
||||||
|
|
||||||
milestone := ctx.Args().First()
|
|
||||||
// make sure milestone exist
|
|
||||||
_, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, milestone)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
issues, _, err := client.ListRepoIssues(ctx.Owner, ctx.Repo, gitea.ListIssueOption{
|
|
||||||
ListOptions: ctx.GetListOptions(),
|
|
||||||
Milestones: []string{milestone},
|
|
||||||
Type: kind,
|
|
||||||
State: state,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fields, err := msIssuesFieldsFlag.GetValues(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
print.IssuesPullsList(issues, ctx.Output, fields)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runMilestoneIssueAdd(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
if ctx.Args().Len() != 2 {
|
|
||||||
return fmt.Errorf("need two arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
mileName := ctx.Args().Get(0)
|
|
||||||
issueIndex := ctx.Args().Get(1)
|
|
||||||
idx, err := utils.ArgToIndex(issueIndex)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure milestone exist
|
|
||||||
mile, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, mileName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = client.EditIssue(ctx.Owner, ctx.Repo, idx, gitea.EditIssueOption{
|
|
||||||
Milestone: &mile.ID,
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func runMilestoneIssueRemove(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
if ctx.Args().Len() != 2 {
|
|
||||||
return fmt.Errorf("need two arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
mileName := ctx.Args().Get(0)
|
|
||||||
issueIndex := ctx.Args().Get(1)
|
|
||||||
idx, err := utils.ArgToIndex(issueIndex)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
issue, _, err := client.GetIssue(ctx.Owner, ctx.Repo, idx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if issue.Milestone == nil {
|
|
||||||
return fmt.Errorf("issue is not assigned to a milestone")
|
|
||||||
}
|
|
||||||
|
|
||||||
if issue.Milestone.Title != mileName {
|
|
||||||
return fmt.Errorf("issue is not assigned to this milestone")
|
|
||||||
}
|
|
||||||
|
|
||||||
zero := int64(0)
|
|
||||||
_, _, err = client.EditIssue(ctx.Owner, ctx.Repo, idx, gitea.EditIssueOption{
|
|
||||||
Milestone: &zero,
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package milestones
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdMilestonesList represents a sub command of milestones to list milestones
|
|
||||||
var CmdMilestonesList = cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Usage: "List milestones of the repository",
|
|
||||||
Description: `List milestones of the repository`,
|
|
||||||
Action: RunMilestonesList,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "state",
|
|
||||||
Usage: "Filter by milestone state (all|open|closed)",
|
|
||||||
DefaultText: "open",
|
|
||||||
},
|
|
||||||
&flags.PaginationPageFlag,
|
|
||||||
&flags.PaginationLimitFlag,
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunMilestonesList list milestones
|
|
||||||
func RunMilestonesList(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
state := gitea.StateOpen
|
|
||||||
switch ctx.String("state") {
|
|
||||||
case "all":
|
|
||||||
state = gitea.StateAll
|
|
||||||
case "closed":
|
|
||||||
state = gitea.StateClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
milestones, _, err := client.ListRepoMilestones(ctx.Owner, ctx.Repo, gitea.ListMilestoneOption{
|
|
||||||
ListOptions: ctx.GetListOptions(),
|
|
||||||
State: state,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
print.MilestonesList(milestones, ctx.Output, state)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package milestones
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdMilestonesReopen represents a sub command of milestones to open an milestone
|
|
||||||
var CmdMilestonesReopen = cli.Command{
|
|
||||||
Name: "reopen",
|
|
||||||
Aliases: []string{"open"},
|
|
||||||
Usage: "Change state of an milestone to 'open'",
|
|
||||||
Description: `Change state of an milestone to 'open'`,
|
|
||||||
ArgsUsage: "<milestone name>",
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
return editMilestoneStatus(ctx, false)
|
|
||||||
},
|
|
||||||
Flags: flags.AllDefaultFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func editMilestoneStatus(cmd *cli.Context, close bool) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
state := gitea.StateOpen
|
|
||||||
if close {
|
|
||||||
state = gitea.StateClosed
|
|
||||||
}
|
|
||||||
_, _, err := client.EditMilestoneByName(ctx.Owner, ctx.Repo, ctx.Args().First(), gitea.EditMilestoneOption{
|
|
||||||
State: &state,
|
|
||||||
Title: ctx.Args().First(),
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
@ -5,25 +5,105 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/tea/cmd/notifications"
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdNotifications is the main command to operate with notifications
|
// CmdNotifications is the main command to operate with notifications
|
||||||
var CmdNotifications = cli.Command{
|
var CmdNotifications = cli.Command{
|
||||||
Name: "notifications",
|
Name: "notifications",
|
||||||
Aliases: []string{"notification", "n"},
|
Usage: "show notifications",
|
||||||
Category: catHelpers,
|
Description: "show notifications, by default based of the current repo and unread one",
|
||||||
Usage: "Show notifications",
|
Action: runNotifications,
|
||||||
Description: "Show notifications, by default based on the current repo if available",
|
Flags: append([]cli.Flag{
|
||||||
Action: notifications.RunNotificationsList,
|
&cli.BoolFlag{
|
||||||
Subcommands: []*cli.Command{
|
Name: "all",
|
||||||
¬ifications.CmdNotificationsList,
|
Aliases: []string{"a"},
|
||||||
¬ifications.CmdNotificationsMarkRead,
|
Usage: "show all notifications of related gitea instance",
|
||||||
¬ifications.CmdNotificationsMarkUnread,
|
},
|
||||||
¬ifications.CmdNotificationsMarkPinned,
|
/* // not supported jet
|
||||||
¬ifications.CmdNotificationsUnpin,
|
&cli.BoolFlag{
|
||||||
},
|
Name: "read",
|
||||||
Flags: notifications.CmdNotificationsList.Flags,
|
Aliases: []string{"rd"},
|
||||||
|
Usage: "show read notifications instead unread",
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "page",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Usage: "specify page, default is 1",
|
||||||
|
Value: 1,
|
||||||
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "limit",
|
||||||
|
Aliases: []string{"lm"},
|
||||||
|
Usage: "specify limit of items per page",
|
||||||
|
},
|
||||||
|
}, AllDefaultFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runNotifications(ctx *cli.Context) error {
|
||||||
|
var news []*gitea.NotificationThread
|
||||||
|
var err error
|
||||||
|
|
||||||
|
listOpts := gitea.ListOptions{
|
||||||
|
Page: ctx.Int("page"),
|
||||||
|
PageSize: ctx.Int("limit"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Bool("all") {
|
||||||
|
login := initCommandLoginOnly()
|
||||||
|
news, err = login.Client().ListNotifications(gitea.ListNotificationOptions{
|
||||||
|
ListOptions: listOpts,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
news, err = login.Client().ListRepoNotifications(owner, repo, gitea.ListNotificationOptions{
|
||||||
|
ListOptions: listOpts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Type",
|
||||||
|
"Index",
|
||||||
|
"Title",
|
||||||
|
}
|
||||||
|
if ctx.Bool("all") {
|
||||||
|
headers = append(headers, "Repository")
|
||||||
|
}
|
||||||
|
|
||||||
|
var values [][]string
|
||||||
|
|
||||||
|
for _, n := range news {
|
||||||
|
if n.Subject == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// if pull or Issue get Index
|
||||||
|
var index string
|
||||||
|
if n.Subject.Type == "Issue" || n.Subject.Type == "Pull" {
|
||||||
|
index = n.Subject.URL
|
||||||
|
urlParts := strings.Split(n.Subject.URL, "/")
|
||||||
|
if len(urlParts) != 0 {
|
||||||
|
index = urlParts[len(urlParts)-1]
|
||||||
|
}
|
||||||
|
index = "#" + index
|
||||||
|
}
|
||||||
|
|
||||||
|
item := []string{n.Subject.Type, index, n.Subject.Title}
|
||||||
|
if ctx.Bool("all") {
|
||||||
|
item = append(item, n.Repository.FullName)
|
||||||
|
}
|
||||||
|
values = append(values, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(values) != 0 {
|
||||||
|
Output(outputValue, headers, values)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package 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 notifTypeFlag = 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`,
|
|
||||||
Action: RunNotificationsList,
|
|
||||||
Flags: append([]cli.Flag{notifTypeFlag}, 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 := notifTypeFlag.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
|
|
||||||
}
|
|
||||||
|
|
||||||
if all {
|
|
||||||
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, all)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,139 +0,0 @@
|
|||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package 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 !flags.NotificationStateFlag.IsSet() {
|
|
||||||
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 !flags.NotificationStateFlag.IsSet() {
|
|
||||||
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 !flags.NotificationStateFlag.IsSet() {
|
|
||||||
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
|
|
||||||
}
|
|
30
cmd/open.go
30
cmd/open.go
@ -5,11 +5,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
local_git "code.gitea.io/tea/modules/git"
|
local_git "code.gitea.io/tea/modules/git"
|
||||||
|
|
||||||
"github.com/skratchdot/open-golang/open"
|
"github.com/skratchdot/open-golang/open"
|
||||||
@ -19,17 +18,14 @@ import (
|
|||||||
// CmdOpen represents a sub command of issues to open issue on the web browser
|
// CmdOpen represents a sub command of issues to open issue on the web browser
|
||||||
var CmdOpen = cli.Command{
|
var CmdOpen = cli.Command{
|
||||||
Name: "open",
|
Name: "open",
|
||||||
Aliases: []string{"o"},
|
Usage: "Open something of the repository on web browser",
|
||||||
Category: catHelpers,
|
Description: `Open something of the repository on web browser`,
|
||||||
Usage: "Open something of the repository in web browser",
|
|
||||||
Description: `Open something of the repository in web browser`,
|
|
||||||
Action: runOpen,
|
Action: runOpen,
|
||||||
Flags: append([]cli.Flag{}, flags.LoginRepoFlags...),
|
Flags: append([]cli.Flag{}, LoginRepoFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runOpen(cmd *cli.Context) error {
|
func runOpen(ctx *cli.Context) error {
|
||||||
ctx := context.InitCommand(cmd)
|
login, owner, repo := initCommand()
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
var suffix string
|
var suffix string
|
||||||
number := ctx.Args().Get(0)
|
number := ctx.Args().Get(0)
|
||||||
@ -43,11 +39,12 @@ func runOpen(cmd *cli.Context) error {
|
|||||||
case strings.EqualFold(number, "commits"):
|
case strings.EqualFold(number, "commits"):
|
||||||
repo, err := local_git.RepoForWorkdir()
|
repo, err := local_git.RepoForWorkdir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
b, err := repo.Head()
|
b, err := repo.Head()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Fatal(err)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
name := b.Name()
|
name := b.Name()
|
||||||
switch {
|
switch {
|
||||||
@ -74,6 +71,11 @@ func runOpen(cmd *cli.Context) error {
|
|||||||
suffix = number
|
suffix = number
|
||||||
}
|
}
|
||||||
|
|
||||||
u := path.Join(ctx.Login.URL, ctx.Owner, ctx.Repo, suffix)
|
u := path.Join(login.URL, owner, repo, suffix)
|
||||||
return open.Run(u)
|
err := open.Run(u)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,40 +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 cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/organizations"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdOrgs represents handle organization
|
|
||||||
var CmdOrgs = cli.Command{
|
|
||||||
Name: "organizations",
|
|
||||||
Aliases: []string{"organization", "org"},
|
|
||||||
Category: catEntities,
|
|
||||||
Usage: "List, create, delete organizations",
|
|
||||||
Description: "Show organization details",
|
|
||||||
ArgsUsage: "[<organization>]",
|
|
||||||
Action: runOrganizations,
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
&organizations.CmdOrganizationList,
|
|
||||||
&organizations.CmdOrganizationDelete,
|
|
||||||
},
|
|
||||||
Flags: organizations.CmdOrganizationList.Flags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runOrganizations(ctx *cli.Context) error {
|
|
||||||
if ctx.Args().Len() == 1 {
|
|
||||||
return runOrganizationDetail(ctx.Args().First())
|
|
||||||
}
|
|
||||||
return organizations.RunOrganizationList(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runOrganizationDetail(path string) error {
|
|
||||||
return fmt.Errorf("Not yet implemented")
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package organizations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdOrganizationDelete represents a sub command of organizations to delete a given user organization
|
|
||||||
var CmdOrganizationDelete = cli.Command{
|
|
||||||
Name: "delete",
|
|
||||||
Aliases: []string{"rm"},
|
|
||||||
Usage: "Delete users Organizations",
|
|
||||||
Description: "Delete users organizations",
|
|
||||||
ArgsUsage: "<organization name>",
|
|
||||||
Action: RunOrganizationDelete,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&flags.LoginFlag,
|
|
||||||
&flags.RemoteFlag,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunOrganizationDelete delete user organization
|
|
||||||
func RunOrganizationDelete(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
if ctx.Args().Len() < 1 {
|
|
||||||
return fmt.Errorf("You have to specify the organization name you want to delete")
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := client.DeleteOrg(ctx.Args().First())
|
|
||||||
if response != nil && response.StatusCode == 404 {
|
|
||||||
return fmt.Errorf("The given organization does not exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package organizations
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdOrganizationList represents a sub command of organizations to list users organizations
|
|
||||||
var CmdOrganizationList = cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Usage: "List Organizations",
|
|
||||||
Description: "List users organizations",
|
|
||||||
Action: RunOrganizationList,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&flags.PaginationPageFlag,
|
|
||||||
&flags.PaginationLimitFlag,
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunOrganizationList list user organizations
|
|
||||||
func RunOrganizationList(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
userOrganizations, _, err := client.ListUserOrgs(ctx.Login.User, gitea.ListOrgsOptions{
|
|
||||||
ListOptions: ctx.GetListOptions(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
print.OrganizationsList(userOrganizations, ctx.Output)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
394
cmd/pulls.go
394
cmd/pulls.go
@ -6,15 +6,15 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/pulls"
|
local_git "code.gitea.io/tea/modules/git"
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/interact"
|
|
||||||
"code.gitea.io/tea/modules/print"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
"code.gitea.io/tea/modules/workaround"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
git_config "github.com/go-git/go-git/v5/config"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,73 +22,357 @@ import (
|
|||||||
var CmdPulls = cli.Command{
|
var CmdPulls = cli.Command{
|
||||||
Name: "pulls",
|
Name: "pulls",
|
||||||
Aliases: []string{"pull", "pr"},
|
Aliases: []string{"pull", "pr"},
|
||||||
Category: catEntities,
|
Usage: "List open pull requests",
|
||||||
Usage: "Manage and checkout pull requests",
|
Description: `List open pull requests`,
|
||||||
Description: `Lists PRs when called without argument. If PR index is provided, will show it in detail.`,
|
|
||||||
ArgsUsage: "[<pull index>]",
|
|
||||||
Action: runPulls,
|
Action: runPulls,
|
||||||
Flags: append([]cli.Flag{
|
Flags: append([]cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.StringFlag{
|
||||||
Name: "comments",
|
Name: "state",
|
||||||
Usage: "Wether to display comments (will prompt if not provided & run interactively)",
|
Usage: "Filter by PR state (all|open|closed)",
|
||||||
|
DefaultText: "open",
|
||||||
},
|
},
|
||||||
}, pulls.CmdPullsList.Flags...),
|
}, AllDefaultFlags...),
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&pulls.CmdPullsList,
|
&CmdPullsCheckout,
|
||||||
&pulls.CmdPullsCheckout,
|
&CmdPullsClean,
|
||||||
&pulls.CmdPullsClean,
|
&CmdPullsCreate,
|
||||||
&pulls.CmdPullsCreate,
|
|
||||||
&pulls.CmdPullsClose,
|
|
||||||
&pulls.CmdPullsReopen,
|
|
||||||
&pulls.CmdPullsReview,
|
|
||||||
&pulls.CmdPullsApprove,
|
|
||||||
&pulls.CmdPullsReject,
|
|
||||||
&pulls.CmdPullsMerge,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPulls(ctx *cli.Context) error {
|
func runPulls(ctx *cli.Context) error {
|
||||||
if ctx.Args().Len() == 1 {
|
login, owner, repo := initCommand()
|
||||||
return runPullDetail(ctx, ctx.Args().First())
|
|
||||||
}
|
state := gitea.StateOpen
|
||||||
return pulls.RunPullsList(ctx)
|
switch ctx.String("state") {
|
||||||
}
|
case "all":
|
||||||
|
state = gitea.StateAll
|
||||||
|
case "open":
|
||||||
|
state = gitea.StateOpen
|
||||||
|
case "closed":
|
||||||
|
state = gitea.StateClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
prs, err := login.Client().ListRepoPullRequests(owner, repo, gitea.ListPullRequestsOptions{
|
||||||
|
State: state,
|
||||||
|
})
|
||||||
|
|
||||||
func runPullDetail(cmd *cli.Context, index string) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
idx, err := utils.ArgToIndex(index)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := ctx.Login.Client()
|
headers := []string{
|
||||||
pr, _, err := client.GetPullRequest(ctx.Owner, ctx.Repo, idx)
|
"Index",
|
||||||
if err != nil {
|
"State",
|
||||||
return err
|
"Author",
|
||||||
}
|
"Updated",
|
||||||
if err := workaround.FixPullHeadSha(client, pr); err != nil {
|
"Title",
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reviews, _, err := client.ListPullReviews(ctx.Owner, ctx.Repo, idx, gitea.ListPullReviewsOptions{})
|
var values [][]string
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error while loading reviews: %v\n", err)
|
if len(prs) == 0 {
|
||||||
|
Output(outputValue, headers, values)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, _, err := client.GetCombinedStatus(ctx.Owner, ctx.Repo, pr.Head.Sha)
|
for _, pr := range prs {
|
||||||
if err != nil {
|
if pr == nil {
|
||||||
fmt.Printf("error while loading CI: %v\n", err)
|
continue
|
||||||
}
|
|
||||||
|
|
||||||
print.PullDetails(pr, reviews, ci)
|
|
||||||
|
|
||||||
if pr.Comments > 0 {
|
|
||||||
err = interact.ShowCommentsMaybeInteractive(ctx, idx, pr.Comments)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error loading comments: %v\n", err)
|
|
||||||
}
|
}
|
||||||
|
name := pr.Poster.FullName
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = pr.Poster.UserName
|
||||||
|
}
|
||||||
|
values = append(
|
||||||
|
values,
|
||||||
|
[]string{
|
||||||
|
strconv.FormatInt(pr.Index, 10),
|
||||||
|
string(pr.State),
|
||||||
|
name,
|
||||||
|
pr.Updated.Format("2006-01-02 15:04:05"),
|
||||||
|
pr.Title,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
Output(outputValue, headers, values)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CmdPullsCheckout is a command to locally checkout the given PR
|
||||||
|
var CmdPullsCheckout = cli.Command{
|
||||||
|
Name: "checkout",
|
||||||
|
Usage: "Locally check out the given PR",
|
||||||
|
Description: `Locally check out the given PR`,
|
||||||
|
Action: runPullsCheckout,
|
||||||
|
ArgsUsage: "<pull index>",
|
||||||
|
Flags: AllDefaultFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPullsCheckout(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
if ctx.Args().Len() != 1 {
|
||||||
|
log.Fatal("Must specify a PR index")
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch PR source-repo & -branch from gitea
|
||||||
|
idx, err := argToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pr, err := login.Client().GetPullRequest(owner, repo, idx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
remoteURL := pr.Head.Repository.CloneURL
|
||||||
|
remoteBranchName := pr.Head.Ref
|
||||||
|
|
||||||
|
// open local git repo
|
||||||
|
localRepo, err := local_git.RepoForWorkdir()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify related remote is in local repo, otherwise add it
|
||||||
|
newRemoteName := fmt.Sprintf("pulls/%v", pr.Head.Repository.Owner.UserName)
|
||||||
|
localRemote, err := localRepo.GetOrCreateRemote(remoteURL, newRemoteName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
localRemoteName := localRemote.Config().Name
|
||||||
|
localBranchName := fmt.Sprintf("pulls/%v-%v", idx, remoteBranchName)
|
||||||
|
|
||||||
|
// fetch remote
|
||||||
|
fmt.Printf("Fetching PR %v (head %s:%s) from remote '%s'\n",
|
||||||
|
idx, remoteURL, remoteBranchName, localRemoteName)
|
||||||
|
|
||||||
|
url, err := local_git.ParseURL(localRemote.Config().URLs[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
auth, err := local_git.GetAuthForURL(url, login.User, login.SSHKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = localRemote.Fetch(&git.FetchOptions{Auth: auth})
|
||||||
|
if err == git.NoErrAlreadyUpToDate {
|
||||||
|
fmt.Println(err)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkout local branch
|
||||||
|
fmt.Printf("Creating branch '%s'\n", localBranchName)
|
||||||
|
err = localRepo.TeaCreateBranch(localBranchName, remoteBranchName, localRemoteName)
|
||||||
|
if err == git.ErrBranchExists {
|
||||||
|
fmt.Println(err)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Checking out PR %v\n", idx)
|
||||||
|
err = localRepo.TeaCheckout(localBranchName)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdPullsClean removes the remote and local feature branches, if a PR is merged.
|
||||||
|
var CmdPullsClean = cli.Command{
|
||||||
|
Name: "clean",
|
||||||
|
Usage: "Deletes local & remote feature-branches for a closed pull request",
|
||||||
|
Description: `Deletes local & remote feature-branches for a closed pull request`,
|
||||||
|
ArgsUsage: "<pull index>",
|
||||||
|
Action: runPullsClean,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "ignore-sha",
|
||||||
|
Usage: "Find the local branch by name instead of commit hash (less precise)",
|
||||||
|
},
|
||||||
|
}, AllDefaultFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPullsClean(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
if ctx.Args().Len() != 1 {
|
||||||
|
return fmt.Errorf("Must specify a PR index")
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch PR source-repo & -branch from gitea
|
||||||
|
idx, err := argToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pr, err := login.Client().GetPullRequest(owner, repo, idx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pr.State == gitea.StateOpen {
|
||||||
|
return fmt.Errorf("PR is still open, won't delete branches")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDEA: abort if PR.Head.Repository.CloneURL does not match login.URL?
|
||||||
|
|
||||||
|
r, err := local_git.RepoForWorkdir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// find a branch with matching sha or name, that has a remote matching the repo url
|
||||||
|
var branch *git_config.Branch
|
||||||
|
if ctx.Bool("ignore-sha") {
|
||||||
|
branch, err = r.TeaFindBranchByName(pr.Head.Ref, pr.Head.Repository.CloneURL)
|
||||||
|
} else {
|
||||||
|
branch, err = r.TeaFindBranchBySha(pr.Head.Sha, pr.Head.Repository.CloneURL)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if branch == nil {
|
||||||
|
if ctx.Bool("ignore-sha") {
|
||||||
|
return fmt.Errorf("Remote branch %s not found in local repo", pr.Head.Ref)
|
||||||
|
}
|
||||||
|
return fmt.Errorf(`Remote branch %s not found in local repo.
|
||||||
|
Either you don't track this PR, or the local branch has diverged from the remote.
|
||||||
|
If you still want to continue & are sure you don't loose any important commits,
|
||||||
|
call me again with the --ignore-sha flag`, pr.Head.Ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare deletion of local branch:
|
||||||
|
headRef, err := r.Head()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if headRef.Name().Short() == branch.Name {
|
||||||
|
fmt.Printf("Checking out 'master' to delete local branch '%s'\n", branch.Name)
|
||||||
|
err = r.TeaCheckout("master")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove local & remote branch
|
||||||
|
fmt.Printf("Deleting local branch %s and remote branch %s\n", branch.Name, pr.Head.Ref)
|
||||||
|
url, err := r.TeaRemoteURL(branch.Remote)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
auth, err := local_git.GetAuthForURL(url, login.User, login.SSHKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.TeaDeleteBranch(branch, pr.Head.Ref, auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdPullsCreate creates a pull request
|
||||||
|
var CmdPullsCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create a pull-request",
|
||||||
|
Description: "Create a pull-request",
|
||||||
|
Action: runPullsCreate,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "head",
|
||||||
|
Usage: "Set head branch (default is current one)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "base",
|
||||||
|
Aliases: []string{"b"},
|
||||||
|
Usage: "Set base branch (default is default branch)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "title",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "Set title of pull (default is head branch name)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "description",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "Set body of new pull",
|
||||||
|
},
|
||||||
|
}, AllDefaultFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPullsCreate(ctx *cli.Context) error {
|
||||||
|
login, ownerArg, repoArg := initCommand()
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
|
repo, err := login.Client().GetRepo(ownerArg, repoArg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// open local git repo
|
||||||
|
localRepo, err := local_git.RepoForWorkdir()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
base := ctx.String("base")
|
||||||
|
// default is default branch
|
||||||
|
if len(base) == 0 {
|
||||||
|
base = repo.DefaultBranch
|
||||||
|
}
|
||||||
|
|
||||||
|
head := ctx.String("head")
|
||||||
|
// default is current one
|
||||||
|
if len(head) == 0 {
|
||||||
|
head, err = localRepo.TeaGetCurrentBranchName()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
title := ctx.String("title")
|
||||||
|
// default is head branch name
|
||||||
|
if len(title) == 0 {
|
||||||
|
title = head
|
||||||
|
if strings.Contains(title, ":") {
|
||||||
|
title = strings.SplitN(title, ":", 2)[1]
|
||||||
|
}
|
||||||
|
title = strings.Replace(title, "-", " ", -1)
|
||||||
|
title = strings.Replace(title, "_", " ", -1)
|
||||||
|
title = strings.Title(strings.ToLower(title))
|
||||||
|
}
|
||||||
|
// title is required
|
||||||
|
if len(title) == 0 {
|
||||||
|
fmt.Printf("Title is required")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// push if possible
|
||||||
|
err = localRepo.Push(&git.PushOptions{})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error occurred during 'git push':\n%s\n", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
pr, err := client.CreatePullRequest(ownerArg, repoArg, gitea.CreatePullRequestOption{
|
||||||
|
Head: head,
|
||||||
|
Base: base,
|
||||||
|
Title: title,
|
||||||
|
Body: ctx.String("description"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("#%d %s\n%s created %s\n", pr.Index,
|
||||||
|
pr.Title,
|
||||||
|
pr.Poster.UserName,
|
||||||
|
pr.Created.Format("2006-01-02 15:04:05"),
|
||||||
|
)
|
||||||
|
if len(pr.Body) != 0 {
|
||||||
|
fmt.Printf("\n%s\n", pr.Body)
|
||||||
|
}
|
||||||
|
fmt.Println(pr.HTMLURL)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func argToIndex(arg string) (int64, error) {
|
||||||
|
if strings.HasPrefix(arg, "#") {
|
||||||
|
arg = arg[1:]
|
||||||
|
}
|
||||||
|
return strconv.ParseInt(arg, 10, 64)
|
||||||
|
}
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package pulls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdPullsApprove approves a PR
|
|
||||||
var CmdPullsApprove = cli.Command{
|
|
||||||
Name: "approve",
|
|
||||||
Aliases: []string{"lgtm", "a"},
|
|
||||||
Usage: "Approve a pull request",
|
|
||||||
Description: "Approve a pull request",
|
|
||||||
ArgsUsage: "<pull index> [<comment>]",
|
|
||||||
Action: func(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
if ctx.Args().Len() == 0 {
|
|
||||||
return fmt.Errorf("Must specify a PR index")
|
|
||||||
}
|
|
||||||
|
|
||||||
idx, err := utils.ArgToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
comment := strings.Join(ctx.Args().Tail(), " ")
|
|
||||||
|
|
||||||
return task.CreatePullReview(ctx, idx, gitea.ReviewStateApproved, comment, nil)
|
|
||||||
},
|
|
||||||
Flags: flags.AllDefaultFlags,
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package pulls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/interact"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdPullsCheckout is a command to locally checkout the given PR
|
|
||||||
var CmdPullsCheckout = cli.Command{
|
|
||||||
Name: "checkout",
|
|
||||||
Aliases: []string{"co"},
|
|
||||||
Usage: "Locally check out the given PR",
|
|
||||||
Description: `Locally check out the given PR`,
|
|
||||||
Action: runPullsCheckout,
|
|
||||||
ArgsUsage: "<pull index>",
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "branch",
|
|
||||||
Aliases: []string{"b"},
|
|
||||||
Usage: "Create a local branch if it doesn't exist yet",
|
|
||||||
},
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPullsCheckout(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{LocalRepo: 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
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.PullCheckout(ctx.Login, ctx.Owner, ctx.Repo, ctx.Bool("branch"), idx, interact.PromptPassword)
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package pulls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/interact"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdPullsClean removes the remote and local feature branches, if a PR is merged.
|
|
||||||
var CmdPullsClean = cli.Command{
|
|
||||||
Name: "clean",
|
|
||||||
Usage: "Deletes local & remote feature-branches for a closed pull request",
|
|
||||||
Description: `Deletes local & remote feature-branches for a closed pull request`,
|
|
||||||
ArgsUsage: "<pull index>",
|
|
||||||
Action: runPullsClean,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "ignore-sha",
|
|
||||||
Usage: "Find the local branch by name instead of commit hash (less precise)",
|
|
||||||
},
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPullsClean(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{LocalRepo: 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
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.PullClean(ctx.Login, ctx.Owner, ctx.Repo, idx, ctx.Bool("ignore-sha"), interact.PromptPassword)
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package pulls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdPullsClose closes a given open pull request
|
|
||||||
var CmdPullsClose = cli.Command{
|
|
||||||
Name: "close",
|
|
||||||
Usage: "Change state of a pull request to 'closed'",
|
|
||||||
Description: `Change state of a pull request to 'closed'`,
|
|
||||||
ArgsUsage: "<pull index>",
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
var s = gitea.StateClosed
|
|
||||||
return editPullState(ctx, gitea.EditPullRequestOption{State: &s})
|
|
||||||
},
|
|
||||||
Flags: flags.AllDefaultFlags,
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package pulls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/interact"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdPullsCreate creates a pull request
|
|
||||||
var CmdPullsCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Usage: "Create a pull-request",
|
|
||||||
Description: "Create a pull-request",
|
|
||||||
Action: runPullsCreate,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "head",
|
|
||||||
Usage: "Set head branch (default is current one)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "base",
|
|
||||||
Aliases: []string{"b"},
|
|
||||||
Usage: "Set base branch (default is default branch)",
|
|
||||||
},
|
|
||||||
}, flags.IssuePREditFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPullsCreate(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
|
|
||||||
// no args -> interactive mode
|
|
||||||
if ctx.NumFlags() == 0 {
|
|
||||||
return interact.CreatePull(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// else use args to create PR
|
|
||||||
opts, err := flags.GetIssuePREditFlags(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.CreatePull(
|
|
||||||
ctx,
|
|
||||||
ctx.String("base"),
|
|
||||||
ctx.String("head"),
|
|
||||||
opts,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package pulls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// editPullState abstracts the arg parsing to edit the given pull request
|
|
||||||
func editPullState(cmd *cli.Context, opts gitea.EditPullRequestOption) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
if ctx.Args().Len() == 0 {
|
|
||||||
return fmt.Errorf("Please provide a Pull Request index")
|
|
||||||
}
|
|
||||||
|
|
||||||
index, err := utils.ArgToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pr, _, err := ctx.Login.Client().EditPullRequest(ctx.Owner, ctx.Repo, index, opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
print.PullDetails(pr, nil, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package pulls
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdPullsList represents a sub command of issues to list pulls
|
|
||||||
var CmdPullsList = cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Usage: "List pull requests of the repository",
|
|
||||||
Description: `List pull requests of the repository`,
|
|
||||||
Action: RunPullsList,
|
|
||||||
Flags: flags.IssuePRFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunPullsList return list of pulls
|
|
||||||
func RunPullsList(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
state := gitea.StateOpen
|
|
||||||
switch ctx.String("state") {
|
|
||||||
case "all":
|
|
||||||
state = gitea.StateAll
|
|
||||||
case "open":
|
|
||||||
state = gitea.StateOpen
|
|
||||||
case "closed":
|
|
||||||
state = gitea.StateClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
prs, _, err := ctx.Login.Client().ListRepoPullRequests(ctx.Owner, ctx.Repo, gitea.ListPullRequestsOptions{
|
|
||||||
State: state,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
print.PullsList(prs, ctx.Output)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package 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
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package pulls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdPullsReject requests changes to a PR
|
|
||||||
var CmdPullsReject = cli.Command{
|
|
||||||
Name: "reject",
|
|
||||||
Usage: "Request changes to a pull request",
|
|
||||||
Description: "Request changes to a pull request",
|
|
||||||
ArgsUsage: "<pull index> <reason>",
|
|
||||||
Action: func(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
if ctx.Args().Len() < 2 {
|
|
||||||
return fmt.Errorf("Must specify a PR index and comment")
|
|
||||||
}
|
|
||||||
|
|
||||||
idx, err := utils.ArgToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
comment := strings.Join(ctx.Args().Tail(), " ")
|
|
||||||
|
|
||||||
return task.CreatePullReview(ctx, idx, gitea.ReviewStateRequestChanges, comment, nil)
|
|
||||||
},
|
|
||||||
Flags: flags.AllDefaultFlags,
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package pulls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdPullsReopen reopens a given closed pull request
|
|
||||||
var CmdPullsReopen = cli.Command{
|
|
||||||
Name: "reopen",
|
|
||||||
Aliases: []string{"open"},
|
|
||||||
Usage: "Change state of a pull request to 'open'",
|
|
||||||
Description: `Change state of a pull request to 'open'`,
|
|
||||||
ArgsUsage: "<pull index>",
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
var s = gitea.StateOpen
|
|
||||||
return editPullState(ctx, gitea.EditPullRequestOption{State: &s})
|
|
||||||
},
|
|
||||||
Flags: flags.AllDefaultFlags,
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package pulls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/interact"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdPullsReview starts an interactive review session
|
|
||||||
var CmdPullsReview = cli.Command{
|
|
||||||
Name: "review",
|
|
||||||
Usage: "Interactively review a pull request",
|
|
||||||
Description: "Interactively review a pull request",
|
|
||||||
ArgsUsage: "<pull index>",
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
return interact.ReviewPull(ctx, idx)
|
|
||||||
},
|
|
||||||
Flags: flags.AllDefaultFlags,
|
|
||||||
}
|
|
144
cmd/releases.go
144
cmd/releases.go
@ -5,26 +5,144 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"log"
|
||||||
"code.gitea.io/tea/cmd/releases"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdReleases represents to login a gitea server.
|
// CmdReleases represents to login a gitea server.
|
||||||
// ToDo: ReleaseDetails
|
|
||||||
var CmdReleases = cli.Command{
|
var CmdReleases = cli.Command{
|
||||||
Name: "releases",
|
Name: "releases",
|
||||||
Aliases: []string{"release", "r"},
|
Usage: "Create releases",
|
||||||
Category: catEntities,
|
Description: `Create releases`,
|
||||||
Usage: "Manage releases",
|
Action: runReleases,
|
||||||
Description: "Manage releases",
|
|
||||||
Action: releases.RunReleasesList,
|
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&releases.CmdReleaseList,
|
&CmdReleaseCreate,
|
||||||
&releases.CmdReleaseCreate,
|
|
||||||
&releases.CmdReleaseDelete,
|
|
||||||
&releases.CmdReleaseEdit,
|
|
||||||
},
|
},
|
||||||
Flags: flags.AllDefaultFlags,
|
Flags: AllDefaultFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runReleases(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
|
||||||
|
releases, err := login.Client().ListReleases(owner, repo, gitea.ListReleasesOptions{})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Tag-Name",
|
||||||
|
"Title",
|
||||||
|
"Published At",
|
||||||
|
"Tar URL",
|
||||||
|
}
|
||||||
|
|
||||||
|
var values [][]string
|
||||||
|
|
||||||
|
if len(releases) == 0 {
|
||||||
|
Output(outputValue, headers, values)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, release := range releases {
|
||||||
|
values = append(
|
||||||
|
values,
|
||||||
|
[]string{
|
||||||
|
release.TagName,
|
||||||
|
release.Title,
|
||||||
|
release.PublishedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
release.TarURL,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Output(outputValue, headers, values)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdReleaseCreate represents a sub command of Release to create release.
|
||||||
|
var CmdReleaseCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create a release",
|
||||||
|
Description: `Create a release`,
|
||||||
|
Action: runReleaseCreate,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "tag",
|
||||||
|
Usage: "Tag name",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "target",
|
||||||
|
Usage: "Target refs, branch name or commit id",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "title",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "Release title",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "note",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Usage: "Release notes",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "draft",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "Is a draft",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "prerelease",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Usage: "Is a pre-release",
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "asset",
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Usage: "List of files to attach",
|
||||||
|
},
|
||||||
|
}, LoginRepoFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runReleaseCreate(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
|
||||||
|
release, err := login.Client().CreateRelease(owner, repo, gitea.CreateReleaseOption{
|
||||||
|
TagName: ctx.String("tag"),
|
||||||
|
Target: ctx.String("target"),
|
||||||
|
Title: ctx.String("title"),
|
||||||
|
Note: ctx.String("note"),
|
||||||
|
IsDraft: ctx.Bool("draft"),
|
||||||
|
IsPrerelease: ctx.Bool("prerelease"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "409 Conflict" {
|
||||||
|
log.Fatal("error: There already is a release for this tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, asset := range ctx.StringSlice("asset") {
|
||||||
|
var file *os.File
|
||||||
|
|
||||||
|
if file, err = os.Open(asset); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Base(asset)
|
||||||
|
|
||||||
|
if _, err = login.Client().CreateReleaseAttachment(owner, repo, release.ID, file, filePath); err != nil {
|
||||||
|
file.Close()
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,102 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package releases
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdReleaseCreate represents a sub command of Release to create release
|
|
||||||
var CmdReleaseCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Usage: "Create a release",
|
|
||||||
Description: `Create a release`,
|
|
||||||
Action: runReleaseCreate,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "tag",
|
|
||||||
Usage: "Tag name",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "target",
|
|
||||||
Usage: "Target refs, branch name or commit id",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "title",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "Release title",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "note",
|
|
||||||
Aliases: []string{"n"},
|
|
||||||
Usage: "Release notes",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "draft",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "Is a draft",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "prerelease",
|
|
||||||
Aliases: []string{"p"},
|
|
||||||
Usage: "Is a pre-release",
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "asset",
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "List of files to attach",
|
|
||||||
},
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runReleaseCreate(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
release, resp, err := ctx.Login.Client().CreateRelease(ctx.Owner, ctx.Repo, gitea.CreateReleaseOption{
|
|
||||||
TagName: ctx.String("tag"),
|
|
||||||
Target: ctx.String("target"),
|
|
||||||
Title: ctx.String("title"),
|
|
||||||
Note: ctx.String("note"),
|
|
||||||
IsDraft: ctx.Bool("draft"),
|
|
||||||
IsPrerelease: ctx.Bool("prerelease"),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if resp != nil && resp.StatusCode == http.StatusConflict {
|
|
||||||
return fmt.Errorf("There already is a release for this tag")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, asset := range ctx.StringSlice("asset") {
|
|
||||||
var file *os.File
|
|
||||||
|
|
||||||
if file, err = os.Open(asset); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := filepath.Base(asset)
|
|
||||||
|
|
||||||
if _, _, err = ctx.Login.Client().CreateReleaseAttachment(ctx.Owner, ctx.Repo, release.ID, file, filePath); err != nil {
|
|
||||||
file.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package releases
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdReleaseDelete represents a sub command of Release to delete a release
|
|
||||||
var CmdReleaseDelete = cli.Command{
|
|
||||||
Name: "delete",
|
|
||||||
Aliases: []string{"rm"},
|
|
||||||
Usage: "Delete a release",
|
|
||||||
Description: `Delete a release`,
|
|
||||||
ArgsUsage: "<release tag>",
|
|
||||||
Action: runReleaseDelete,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "confirm",
|
|
||||||
Aliases: []string{"y"},
|
|
||||||
Usage: "Confirm deletion (required)",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "delete-tag",
|
|
||||||
Usage: "Also delete the git tag for this release",
|
|
||||||
},
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runReleaseDelete(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
tag := ctx.Args().First()
|
|
||||||
if len(tag) == 0 {
|
|
||||||
fmt.Println("Release tag needed to delete")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ctx.Bool("confirm") {
|
|
||||||
fmt.Println("Are you sure? Please confirm with -y or --confirm.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
release, err := getReleaseByTag(ctx.Owner, ctx.Repo, tag, client)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if release == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.DeleteRelease(ctx.Owner, ctx.Repo, release.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Bool("delete-tag") {
|
|
||||||
_, err = client.DeleteTag(ctx.Owner, ctx.Repo, tag)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package releases
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdReleaseEdit represents a sub command of Release to edit releases
|
|
||||||
var CmdReleaseEdit = cli.Command{
|
|
||||||
Name: "edit",
|
|
||||||
Aliases: []string{"e"},
|
|
||||||
Usage: "Edit a release",
|
|
||||||
Description: `Edit a release`,
|
|
||||||
ArgsUsage: "<release tag>",
|
|
||||||
Action: runReleaseEdit,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "tag",
|
|
||||||
Usage: "Change Tag",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "target",
|
|
||||||
Usage: "Change Target",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "title",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "Change Title",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "note",
|
|
||||||
Aliases: []string{"n"},
|
|
||||||
Usage: "Change Notes",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "draft",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "Mark as Draft [True/false]",
|
|
||||||
DefaultText: "true",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "prerelease",
|
|
||||||
Aliases: []string{"p"},
|
|
||||||
Usage: "Mark as Pre-Release [True/false]",
|
|
||||||
DefaultText: "true",
|
|
||||||
},
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runReleaseEdit(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
tag := ctx.Args().First()
|
|
||||||
if len(tag) == 0 {
|
|
||||||
fmt.Println("Release tag needed to edit")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
release, err := getReleaseByTag(ctx.Owner, ctx.Repo, tag, client)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if release == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var isDraft, isPre *bool
|
|
||||||
if ctx.IsSet("draft") {
|
|
||||||
isDraft = gitea.OptionalBool(strings.ToLower(ctx.String("draft"))[:1] == "t")
|
|
||||||
}
|
|
||||||
if ctx.IsSet("prerelease") {
|
|
||||||
isPre = gitea.OptionalBool(strings.ToLower(ctx.String("prerelease"))[:1] == "t")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = client.EditRelease(ctx.Owner, ctx.Repo, release.ID, gitea.EditReleaseOption{
|
|
||||||
TagName: ctx.String("tag"),
|
|
||||||
Target: ctx.String("target"),
|
|
||||||
Title: ctx.String("title"),
|
|
||||||
Note: ctx.String("note"),
|
|
||||||
IsDraft: isDraft,
|
|
||||||
IsPrerelease: isPre,
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package releases
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdReleaseList represents a sub command of Release to list releases
|
|
||||||
var CmdReleaseList = cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Usage: "List Releases",
|
|
||||||
Description: "List Releases",
|
|
||||||
Action: RunReleasesList,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&flags.PaginationPageFlag,
|
|
||||||
&flags.PaginationLimitFlag,
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunReleasesList list releases
|
|
||||||
func RunReleasesList(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
releases, _, err := ctx.Login.Client().ListReleases(ctx.Owner, ctx.Repo, gitea.ListReleasesOptions{
|
|
||||||
ListOptions: ctx.GetListOptions(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
print.ReleasesList(releases, ctx.Output)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getReleaseByTag(owner, repo, tag string, client *gitea.Client) (*gitea.Release, error) {
|
|
||||||
rl, _, err := client.ListReleases(owner, repo, gitea.ListReleasesOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(rl) == 0 {
|
|
||||||
fmt.Println("Repo does not have any release")
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
for _, r := range rl {
|
|
||||||
if r.TagName == tag {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println("Release tag does not exist")
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
134
cmd/repos.go
134
cmd/repos.go
@ -5,52 +5,128 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/tea/cmd/repos"
|
"log"
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/print"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdRepos represents to login a gitea server.
|
// CmdRepos represents to login a gitea server.
|
||||||
var CmdRepos = cli.Command{
|
var CmdRepos = cli.Command{
|
||||||
Name: "repos",
|
Name: "repos",
|
||||||
Aliases: []string{"repo"},
|
Usage: "Operate with repositories",
|
||||||
Category: catEntities,
|
Description: `Operate with repositories`,
|
||||||
Usage: "Show repository details",
|
Action: runReposList,
|
||||||
Description: "Show repository details",
|
|
||||||
ArgsUsage: "[<repo owner>/<repo name>]",
|
|
||||||
Action: runRepos,
|
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&repos.CmdReposList,
|
&CmdReposList,
|
||||||
&repos.CmdReposSearch,
|
|
||||||
&repos.CmdRepoCreate,
|
|
||||||
},
|
},
|
||||||
Flags: repos.CmdReposListFlags,
|
Flags: LoginOutputFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRepos(ctx *cli.Context) error {
|
// CmdReposList represents a sub command of issues to list issues
|
||||||
if ctx.Args().Len() == 1 {
|
var CmdReposList = cli.Command{
|
||||||
return runRepoDetail(ctx, ctx.Args().First())
|
Name: "ls",
|
||||||
}
|
Usage: "List available repositories",
|
||||||
return repos.RunReposList(ctx)
|
Description: `List available repositories`,
|
||||||
|
Action: runReposList,
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "mode",
|
||||||
|
Usage: "Filter listed repositories based on mode, optional - fork, mirror, source",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "org",
|
||||||
|
Usage: "Filter listed repositories based on organization, optional",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "user",
|
||||||
|
Usage: "Filter listed repositories absed on user, optional",
|
||||||
|
},
|
||||||
|
}, LoginOutputFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRepoDetail(cmd *cli.Context, path string) error {
|
// runReposList list repositories
|
||||||
ctx := context.InitCommand(cmd)
|
func runReposList(ctx *cli.Context) error {
|
||||||
client := ctx.Login.Client()
|
login := initCommandLoginOnly()
|
||||||
repoOwner, repoName := utils.GetOwnerAndRepo(path, ctx.Owner)
|
|
||||||
repo, _, err := client.GetRepo(repoOwner, repoName)
|
mode := ctx.String("mode")
|
||||||
if err != nil {
|
org := ctx.String("org")
|
||||||
return err
|
user := ctx.String("user")
|
||||||
|
|
||||||
|
var rps []*gitea.Repository
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if org != "" {
|
||||||
|
rps, err = login.Client().ListOrgRepos(org, gitea.ListOrgReposOptions{})
|
||||||
|
} else if user != "" {
|
||||||
|
rps, err = login.Client().ListUserRepos(user, gitea.ListReposOptions{})
|
||||||
|
} else {
|
||||||
|
rps, err = login.Client().ListMyRepos(gitea.ListReposOptions{})
|
||||||
}
|
}
|
||||||
topics, _, err := client.ListRepoTopics(repoOwner, repoName, gitea.ListRepoTopicsOptions{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
print.RepoDetails(repo, topics)
|
var repos []*gitea.Repository
|
||||||
|
if mode == "" {
|
||||||
|
repos = rps
|
||||||
|
} else if mode == "fork" {
|
||||||
|
for _, rp := range rps {
|
||||||
|
if rp.Fork == true {
|
||||||
|
repos = append(repos, rp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if mode == "mirror" {
|
||||||
|
for _, rp := range rps {
|
||||||
|
if rp.Mirror == true {
|
||||||
|
repos = append(repos, rp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if mode == "source" {
|
||||||
|
for _, rp := range rps {
|
||||||
|
if rp.Mirror != true && rp.Fork != true {
|
||||||
|
repos = append(repos, rp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Fatal("Unknown mode: ", mode, "\nUse one of the following:\n- fork\n- mirror\n- source\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rps) == 0 {
|
||||||
|
log.Fatal("No repositories found", rps)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Name",
|
||||||
|
"Type",
|
||||||
|
"SSH",
|
||||||
|
"Owner",
|
||||||
|
}
|
||||||
|
var values [][]string
|
||||||
|
|
||||||
|
for _, rp := range repos {
|
||||||
|
var mode = "source"
|
||||||
|
if rp.Fork {
|
||||||
|
mode = "fork"
|
||||||
|
}
|
||||||
|
if rp.Mirror {
|
||||||
|
mode = "mirror"
|
||||||
|
}
|
||||||
|
|
||||||
|
values = append(
|
||||||
|
values,
|
||||||
|
[]string{
|
||||||
|
rp.FullName,
|
||||||
|
mode,
|
||||||
|
rp.SSHURL,
|
||||||
|
rp.Owner.UserName,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Output(outputValue, headers, values)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,120 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdRepoCreate represents a sub command of repos to create one
|
|
||||||
var CmdRepoCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Aliases: []string{"c"},
|
|
||||||
Usage: "Create a repository",
|
|
||||||
Description: "Create a repository",
|
|
||||||
Action: runRepoCreate,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Aliases: []string{""},
|
|
||||||
Required: true,
|
|
||||||
Usage: "name of new repo",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "owner",
|
|
||||||
Aliases: []string{"O"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "name of repo owner",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "private",
|
|
||||||
Required: false,
|
|
||||||
Value: false,
|
|
||||||
Usage: "make repo private",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "description",
|
|
||||||
Aliases: []string{"desc"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "add description to repo",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "init",
|
|
||||||
Required: false,
|
|
||||||
Value: false,
|
|
||||||
Usage: "initialize repo",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "labels",
|
|
||||||
Required: false,
|
|
||||||
Usage: "name of label set to add",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "gitignores",
|
|
||||||
Aliases: []string{"git"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "list of gitignore templates (need --init)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "license",
|
|
||||||
Required: false,
|
|
||||||
Usage: "add license (need --init)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "readme",
|
|
||||||
Required: false,
|
|
||||||
Usage: "use readme template (need --init)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "branch",
|
|
||||||
Required: false,
|
|
||||||
Usage: "use custom default branch (need --init)",
|
|
||||||
},
|
|
||||||
}, flags.LoginOutputFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRepoCreate(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
var (
|
|
||||||
repo *gitea.Repository
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
opts := gitea.CreateRepoOption{
|
|
||||||
Name: ctx.String("name"),
|
|
||||||
Description: ctx.String("description"),
|
|
||||||
Private: ctx.Bool("private"),
|
|
||||||
AutoInit: ctx.Bool("init"),
|
|
||||||
IssueLabels: ctx.String("labels"),
|
|
||||||
Gitignores: ctx.String("gitignores"),
|
|
||||||
License: ctx.String("license"),
|
|
||||||
Readme: ctx.String("readme"),
|
|
||||||
DefaultBranch: ctx.String("branch"),
|
|
||||||
}
|
|
||||||
if len(ctx.String("owner")) != 0 {
|
|
||||||
repo, _, err = client.CreateOrgRepo(ctx.String("owner"), opts)
|
|
||||||
} else {
|
|
||||||
repo, _, err = client.CreateRepo(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
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package repos
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var typeFilterFlag = cli.StringFlag{
|
|
||||||
Name: "type",
|
|
||||||
Aliases: []string{"T"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "Filter by type: fork, mirror, source",
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTypeFilter(ctx *cli.Context) (filter gitea.RepoType, err error) {
|
|
||||||
t := ctx.String("type")
|
|
||||||
filter = gitea.RepoTypeNone
|
|
||||||
switch t {
|
|
||||||
case "":
|
|
||||||
filter = gitea.RepoTypeNone
|
|
||||||
case "fork":
|
|
||||||
filter = gitea.RepoTypeFork
|
|
||||||
case "mirror":
|
|
||||||
filter = gitea.RepoTypeMirror
|
|
||||||
case "source":
|
|
||||||
filter = gitea.RepoTypeSource
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("invalid repo type '%s'. valid: fork, mirror, source", t)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,117 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package repos
|
|
||||||
|
|
||||||
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 repoFieldsFlag = flags.FieldsFlag(print.RepoFields, []string{
|
|
||||||
"owner", "name", "type", "ssh",
|
|
||||||
})
|
|
||||||
|
|
||||||
// CmdReposListFlags contains all flags needed for repo listing
|
|
||||||
var CmdReposListFlags = append([]cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "watched",
|
|
||||||
Aliases: []string{"w"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "List your watched repos instead",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "starred",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "List your starred repos instead",
|
|
||||||
},
|
|
||||||
repoFieldsFlag,
|
|
||||||
&typeFilterFlag,
|
|
||||||
&flags.PaginationPageFlag,
|
|
||||||
&flags.PaginationLimitFlag,
|
|
||||||
}, flags.LoginOutputFlags...)
|
|
||||||
|
|
||||||
// CmdReposList represents a sub command of repos to list them
|
|
||||||
var CmdReposList = cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Usage: "List repositories you have access to",
|
|
||||||
Description: "List repositories you have access to",
|
|
||||||
Action: RunReposList,
|
|
||||||
Flags: CmdReposListFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunReposList list repositories
|
|
||||||
func RunReposList(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
typeFilter, err := getTypeFilter(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var rps []*gitea.Repository
|
|
||||||
if ctx.Bool("starred") {
|
|
||||||
user, _, err := client.GetMyUserInfo()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rps, _, err = client.SearchRepos(gitea.SearchRepoOptions{
|
|
||||||
ListOptions: ctx.GetListOptions(),
|
|
||||||
StarredByUserID: user.ID,
|
|
||||||
})
|
|
||||||
} else if ctx.Bool("watched") {
|
|
||||||
rps, _, err = client.GetMyWatchedRepos() // TODO: this does not expose pagination..
|
|
||||||
} else {
|
|
||||||
rps, _, err = client.ListMyRepos(gitea.ListReposOptions{
|
|
||||||
ListOptions: ctx.GetListOptions(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reposFiltered := rps
|
|
||||||
if typeFilter != gitea.RepoTypeNone {
|
|
||||||
reposFiltered = filterReposByType(rps, typeFilter)
|
|
||||||
}
|
|
||||||
|
|
||||||
fields, err := repoFieldsFlag.GetValues(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
print.ReposList(reposFiltered, ctx.Output, fields)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterReposByType(repos []*gitea.Repository, t gitea.RepoType) []*gitea.Repository {
|
|
||||||
var filtered []*gitea.Repository
|
|
||||||
for _, r := range repos {
|
|
||||||
switch t {
|
|
||||||
case gitea.RepoTypeFork:
|
|
||||||
if !r.Fork {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case gitea.RepoTypeMirror:
|
|
||||||
if !r.Mirror {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case gitea.RepoTypeSource:
|
|
||||||
if r.Fork || r.Mirror {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filtered = append(filtered, r)
|
|
||||||
}
|
|
||||||
return filtered
|
|
||||||
}
|
|
@ -1,132 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package repos
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdReposSearch represents a sub command of repos to find them
|
|
||||||
var CmdReposSearch = cli.Command{
|
|
||||||
Name: "search",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "Find any repo on an Gitea instance",
|
|
||||||
Description: "Find any repo on an Gitea instance",
|
|
||||||
ArgsUsage: "[<search term>]",
|
|
||||||
Action: runReposSearch,
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
// TODO: it might be nice to search for topics as an ADDITIONAL filter.
|
|
||||||
// for that, we'd probably need to make multiple queries and UNION the results.
|
|
||||||
Name: "topic",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "Search for term in repo topics instead of name",
|
|
||||||
},
|
|
||||||
&typeFilterFlag,
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "owner",
|
|
||||||
Aliases: []string{"O"},
|
|
||||||
Required: false,
|
|
||||||
Usage: "Filter by owner",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "private",
|
|
||||||
Required: false,
|
|
||||||
Usage: "Filter private repos (true|false)",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "archived",
|
|
||||||
Required: false,
|
|
||||||
Usage: "Filter archived repos (true|false)",
|
|
||||||
},
|
|
||||||
repoFieldsFlag,
|
|
||||||
&flags.PaginationPageFlag,
|
|
||||||
&flags.PaginationLimitFlag,
|
|
||||||
}, flags.LoginOutputFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runReposSearch(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
var ownerID int64
|
|
||||||
if ctx.IsSet("owner") {
|
|
||||||
// test if owner is a organisation
|
|
||||||
org, _, err := client.GetOrg(ctx.String("owner"))
|
|
||||||
if err != nil {
|
|
||||||
// HACK: the client does not return a response on 404, so we can't check res.StatusCode
|
|
||||||
if err.Error() != "404 Not Found" {
|
|
||||||
return fmt.Errorf("Could not find owner: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if owner is no org, its a user
|
|
||||||
user, _, err := client.GetUserInfo(ctx.String("owner"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ownerID = user.ID
|
|
||||||
} else {
|
|
||||||
ownerID = org.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isArchived *bool
|
|
||||||
if ctx.IsSet("archived") {
|
|
||||||
archived := strings.ToLower(ctx.String("archived"))[:1] == "t"
|
|
||||||
isArchived = &archived
|
|
||||||
}
|
|
||||||
|
|
||||||
var isPrivate *bool
|
|
||||||
if ctx.IsSet("private") {
|
|
||||||
private := strings.ToLower(ctx.String("private"))[:1] == "t"
|
|
||||||
isPrivate = &private
|
|
||||||
}
|
|
||||||
|
|
||||||
mode, err := getTypeFilter(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyword string
|
|
||||||
if ctx.Args().Present() {
|
|
||||||
keyword = strings.Join(ctx.Args().Slice(), " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
user, _, err := client.GetMyUserInfo()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rps, _, err := client.SearchRepos(gitea.SearchRepoOptions{
|
|
||||||
ListOptions: ctx.GetListOptions(),
|
|
||||||
OwnerID: ownerID,
|
|
||||||
IsPrivate: isPrivate,
|
|
||||||
IsArchived: isArchived,
|
|
||||||
Type: mode,
|
|
||||||
Keyword: keyword,
|
|
||||||
KeywordInDescription: true,
|
|
||||||
KeywordIsTopic: ctx.Bool("topic"),
|
|
||||||
PrioritizedByOwnerID: user.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fields, err := repoFieldsFlag.GetValues(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
print.ReposList(rps, ctx.Output, fields)
|
|
||||||
return nil
|
|
||||||
}
|
|
254
cmd/times.go
254
cmd/times.go
@ -5,26 +5,258 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/tea/cmd/times"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
|
"github.com/araddon/dateparse"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdTrackedTimes represents the command to operate repositories' times.
|
// CmdTrackedTimes represents the command to operate repositories' times.
|
||||||
var CmdTrackedTimes = cli.Command{
|
var CmdTrackedTimes = cli.Command{
|
||||||
Name: "times",
|
Name: "times",
|
||||||
Aliases: []string{"time", "t"},
|
Aliases: []string{"time"},
|
||||||
Category: catEntities,
|
Usage: "Operate on tracked times of a repository's issues & pulls",
|
||||||
Usage: "Operate on tracked times of a repository's issues & pulls",
|
|
||||||
Description: `Operate on tracked times of a repository's issues & pulls.
|
Description: `Operate on tracked times of a repository's issues & pulls.
|
||||||
Depending on your permissions on the repository, only your own tracked
|
Depending on your permissions on the repository, only your own tracked
|
||||||
times might be listed.`,
|
times might be listed.`,
|
||||||
ArgsUsage: "[username | #issue]",
|
ArgsUsage: "[username | #issue]",
|
||||||
Action: times.RunTimesList,
|
Action: runTrackedTimes,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
×.CmdTrackedTimesAdd,
|
&CmdTrackedTimesAdd,
|
||||||
×.CmdTrackedTimesDelete,
|
&CmdTrackedTimesDelete,
|
||||||
×.CmdTrackedTimesReset,
|
&CmdTrackedTimesReset,
|
||||||
×.CmdTrackedTimesList,
|
|
||||||
},
|
},
|
||||||
Flags: times.CmdTrackedTimesList.Flags,
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "from",
|
||||||
|
Aliases: []string{"f"},
|
||||||
|
Usage: "Show only times tracked after this date",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "until",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Usage: "Show only times tracked before this date",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "total",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "Print the total duration at the end",
|
||||||
|
},
|
||||||
|
}, AllDefaultFlags...),
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTrackedTimes(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
|
if err := client.CheckServerVersionConstraint(">= 1.11"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var times []*gitea.TrackedTime
|
||||||
|
var err error
|
||||||
|
|
||||||
|
user := ctx.Args().First()
|
||||||
|
fmt.Println(ctx.Command.ArgsUsage)
|
||||||
|
if user == "" {
|
||||||
|
// get all tracked times on the repo
|
||||||
|
times, err = client.GetRepoTrackedTimes(owner, repo)
|
||||||
|
} else if strings.HasPrefix(user, "#") {
|
||||||
|
// get all tracked times on the specified issue
|
||||||
|
issue, err := argToIndex(user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
times, err = client.ListTrackedTimes(owner, repo, issue, gitea.ListTrackedTimesOptions{})
|
||||||
|
} else {
|
||||||
|
// get all tracked times by the specified user
|
||||||
|
times, err = client.GetUserTrackedTimes(owner, repo, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var from, until time.Time
|
||||||
|
if ctx.String("from") != "" {
|
||||||
|
from, err = dateparse.ParseLocal(ctx.String("from"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ctx.String("until") != "" {
|
||||||
|
until, err = dateparse.ParseLocal(ctx.String("until"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printTrackedTimes(times, outputValue, from, until, ctx.Bool("total"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTrackedTimes(times []*gitea.TrackedTime, outputType string, from, until time.Time, printTotal bool) {
|
||||||
|
var outputValues [][]string
|
||||||
|
var totalDuration int64
|
||||||
|
|
||||||
|
localLoc, err := time.LoadLocation("Local") // local timezone for time formatting
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range times {
|
||||||
|
if !from.IsZero() && from.After(t.Created) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !until.IsZero() && until.Before(t.Created) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
totalDuration += t.Time
|
||||||
|
|
||||||
|
outputValues = append(
|
||||||
|
outputValues,
|
||||||
|
[]string{
|
||||||
|
t.Created.In(localLoc).Format("2006-01-02 15:04:05"),
|
||||||
|
"#" + strconv.FormatInt(t.Issue.Index, 10),
|
||||||
|
t.UserName,
|
||||||
|
time.Duration(1e9 * t.Time).String(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if printTotal {
|
||||||
|
outputValues = append(outputValues, []string{
|
||||||
|
"TOTAL", "", "", time.Duration(1e9 * totalDuration).String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Created",
|
||||||
|
"Issue",
|
||||||
|
"User",
|
||||||
|
"Duration",
|
||||||
|
}
|
||||||
|
Output(outputType, headers, outputValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdTrackedTimesAdd represents a sub command of times to add time to an issue
|
||||||
|
var CmdTrackedTimesAdd = cli.Command{
|
||||||
|
Name: "add",
|
||||||
|
Usage: "Track spent time on an issue",
|
||||||
|
UsageText: "tea times add <issue> <duration>",
|
||||||
|
Description: `Track spent time on an issue
|
||||||
|
Example:
|
||||||
|
tea times add 1 1h25m
|
||||||
|
`,
|
||||||
|
Action: runTrackedTimesAdd,
|
||||||
|
Flags: LoginRepoFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTrackedTimesAdd(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
|
||||||
|
if ctx.Args().Len() < 2 {
|
||||||
|
return fmt.Errorf("No issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := argToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
duration, err := time.ParseDuration(strings.Join(ctx.Args().Tail(), ""))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = login.Client().AddTime(owner, repo, issue, gitea.AddTimeOption{
|
||||||
|
Time: int64(duration.Seconds()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdTrackedTimesDelete is a sub command of CmdTrackedTimes, and removes time from an issue
|
||||||
|
var CmdTrackedTimesDelete = cli.Command{
|
||||||
|
Name: "delete",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
|
Usage: "Delete a single tracked time on an issue",
|
||||||
|
UsageText: "tea times delete <issue> <time ID>",
|
||||||
|
Action: runTrackedTimesDelete,
|
||||||
|
Flags: LoginRepoFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTrackedTimesDelete(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
|
if err := client.CheckServerVersionConstraint(">= 1.11"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Args().Len() < 2 {
|
||||||
|
return fmt.Errorf("No issue or time ID specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := argToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeID, err := strconv.ParseInt(ctx.Args().Get(1), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.DeleteTime(owner, repo, issue, timeID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdTrackedTimesReset is a subcommand of CmdTrackedTimes, and
|
||||||
|
// clears all tracked times on an issue.
|
||||||
|
var CmdTrackedTimesReset = cli.Command{
|
||||||
|
Name: "reset",
|
||||||
|
Usage: "Reset tracked time on an issue",
|
||||||
|
UsageText: "tea times reset <issue>",
|
||||||
|
Action: runTrackedTimesReset,
|
||||||
|
Flags: LoginRepoFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTrackedTimesReset(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand()
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
|
if err := client.CheckServerVersionConstraint(">= 1.11"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Args().Len() != 1 {
|
||||||
|
return fmt.Errorf("No issue specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := argToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.ResetIssueTime(owner, repo, issue)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package times
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdTrackedTimesAdd represents a sub command of times to add time to an issue
|
|
||||||
var CmdTrackedTimesAdd = cli.Command{
|
|
||||||
Name: "add",
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "Track spent time on an issue",
|
|
||||||
UsageText: "tea times add <issue> <duration>",
|
|
||||||
Description: `Track spent time on an issue
|
|
||||||
Example:
|
|
||||||
tea times add 1 1h25m
|
|
||||||
`,
|
|
||||||
Action: runTrackedTimesAdd,
|
|
||||||
Flags: flags.LoginRepoFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTrackedTimesAdd(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
|
|
||||||
if ctx.Args().Len() < 2 {
|
|
||||||
return fmt.Errorf("No issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText)
|
|
||||||
}
|
|
||||||
|
|
||||||
issue, err := utils.ArgToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
duration, err := time.ParseDuration(strings.Join(ctx.Args().Tail(), ""))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = ctx.Login.Client().AddTime(ctx.Owner, ctx.Repo, issue, gitea.AddTimeOption{
|
|
||||||
Time: int64(duration.Seconds()),
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package times
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdTrackedTimesDelete is a sub command of CmdTrackedTimes, and removes time from an issue
|
|
||||||
var CmdTrackedTimesDelete = cli.Command{
|
|
||||||
Name: "delete",
|
|
||||||
Aliases: []string{"rm"},
|
|
||||||
Usage: "Delete a single tracked time on an issue",
|
|
||||||
UsageText: "tea times delete <issue> <time ID>",
|
|
||||||
Action: runTrackedTimesDelete,
|
|
||||||
Flags: flags.LoginRepoFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTrackedTimesDelete(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
if ctx.Args().Len() < 2 {
|
|
||||||
return fmt.Errorf("No issue or time ID specified.\nUsage:\t%s", ctx.Command.UsageText)
|
|
||||||
}
|
|
||||||
|
|
||||||
issue, err := utils.ArgToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
timeID, err := strconv.ParseInt(ctx.Args().Get(1), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.DeleteTime(ctx.Owner, ctx.Repo, issue, timeID)
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,132 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package times
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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/araddon/dateparse"
|
|
||||||
"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
|
|
||||||
var CmdTrackedTimesList = cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Action: RunTimesList,
|
|
||||||
Usage: "List tracked times on issues & pulls",
|
|
||||||
Description: `List tracked times, across repos, or on a single repo or issue:
|
|
||||||
- given a username all times on a repo by that user are shown,
|
|
||||||
- given a issue index with '#' prefix, all times on that issue are listed,
|
|
||||||
- given --mine, your times are listed across all repositories.
|
|
||||||
Depending on your permissions on the repository, only your own tracked times might be listed.`,
|
|
||||||
ArgsUsage: "[username | #issue]",
|
|
||||||
|
|
||||||
Flags: append([]cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "from",
|
|
||||||
Aliases: []string{"f"},
|
|
||||||
Usage: "Show only times tracked after this date",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "until",
|
|
||||||
Aliases: []string{"u"},
|
|
||||||
Usage: "Show only times tracked before this date",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "total",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "Print the total duration at the end",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "mine",
|
|
||||||
Aliases: []string{"m"},
|
|
||||||
Usage: "Show all times tracked by you across all repositories (overrides command arguments)",
|
|
||||||
},
|
|
||||||
timeFieldsFlag,
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunTimesList list repositories
|
|
||||||
func RunTimesList(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
var times []*gitea.TrackedTime
|
|
||||||
var err error
|
|
||||||
var from, until time.Time
|
|
||||||
var fields []string
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := gitea.ListTrackedTimesOptions{Since: from, Before: until}
|
|
||||||
|
|
||||||
user := ctx.Args().First()
|
|
||||||
if ctx.Bool("mine") {
|
|
||||||
times, _, err = client.GetMyTrackedTimes()
|
|
||||||
fields = []string{"created", "repo", "issue", "duration"}
|
|
||||||
} else if user == "" {
|
|
||||||
// get all tracked times on the repo
|
|
||||||
times, _, err = client.ListRepoTrackedTimes(ctx.Owner, ctx.Repo, opts)
|
|
||||||
fields = []string{"created", "issue", "user", "duration"}
|
|
||||||
} else if strings.HasPrefix(user, "#") {
|
|
||||||
// get all tracked times on the specified issue
|
|
||||||
issue, err := utils.ArgToIndex(user)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
times, _, err = client.ListIssueTrackedTimes(ctx.Owner, ctx.Repo, issue, opts)
|
|
||||||
fields = []string{"created", "user", "duration"}
|
|
||||||
} else {
|
|
||||||
// get all tracked times by the specified user
|
|
||||||
opts.User = user
|
|
||||||
times, _, err = client.ListRepoTrackedTimes(ctx.Owner, ctx.Repo, opts)
|
|
||||||
fields = []string{"created", "issue", "duration"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.IsSet("fields") {
|
|
||||||
if fields, err = timeFieldsFlag.GetValues(cmd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print.TrackedTimesList(times, ctx.Output, fields, ctx.Bool("total"))
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package times
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdTrackedTimesReset is a subcommand of CmdTrackedTimes, and
|
|
||||||
// clears all tracked times on an issue.
|
|
||||||
var CmdTrackedTimesReset = cli.Command{
|
|
||||||
Name: "reset",
|
|
||||||
Usage: "Reset tracked time on an issue",
|
|
||||||
UsageText: "tea times reset <issue>",
|
|
||||||
Action: runTrackedTimesReset,
|
|
||||||
Flags: flags.LoginRepoFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTrackedTimesReset(cmd *cli.Context) error {
|
|
||||||
ctx := context.InitCommand(cmd)
|
|
||||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
|
||||||
client := ctx.Login.Client()
|
|
||||||
|
|
||||||
if ctx.Args().Len() != 1 {
|
|
||||||
return fmt.Errorf("No issue specified.\nUsage:\t%s", ctx.Command.UsageText)
|
|
||||||
}
|
|
||||||
|
|
||||||
issue, err := utils.ArgToIndex(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.ResetIssueTime(ctx.Owner, ctx.Repo, issue)
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
$fn = $($MyInvocation.MyCommand.Name)
|
|
||||||
$name = $fn -replace "(.*)\.ps1$", '$1'
|
|
||||||
Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
|
|
||||||
param($commandName, $wordToComplete, $cursorPosition)
|
|
||||||
$other = "$wordToComplete --generate-bash-completion"
|
|
||||||
Invoke-Expression $other | ForEach-Object {
|
|
||||||
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
: ${PROG:=$(basename ${BASH_SOURCE})}
|
|
||||||
|
|
||||||
_cli_bash_autocomplete() {
|
|
||||||
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
|
|
||||||
local cur opts base
|
|
||||||
COMPREPLY=()
|
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
||||||
if [[ "$cur" == "-"* ]]; then
|
|
||||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
|
|
||||||
else
|
|
||||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
|
||||||
fi
|
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG
|
|
||||||
unset PROG
|
|
@ -1,23 +0,0 @@
|
|||||||
#compdef $PROG
|
|
||||||
|
|
||||||
_cli_zsh_autocomplete() {
|
|
||||||
|
|
||||||
local -a opts
|
|
||||||
local cur
|
|
||||||
cur=${words[-1]}
|
|
||||||
if [[ "$cur" == "-"* ]]; then
|
|
||||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
|
||||||
else
|
|
||||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${opts[1]}" != "" ]]; then
|
|
||||||
_describe 'values' opts
|
|
||||||
else
|
|
||||||
_files
|
|
||||||
fi
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
compdef _cli_zsh_autocomplete $PROG
|
|
50
go.mod
50
go.mod
@ -1,40 +1,22 @@
|
|||||||
module code.gitea.io/tea
|
module code.gitea.io/tea
|
||||||
|
|
||||||
go 1.13
|
go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.gitea.io/gitea-vet v0.2.1
|
code.gitea.io/sdk/gitea v0.12.1
|
||||||
code.gitea.io/sdk/gitea v0.15.0
|
gitea.com/jolheiser/gitea-vet v0.1.0
|
||||||
gitea.com/noerw/unidiff-comments v0.0.0-20201219085024-64aec5658f2b
|
github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.1
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c // indirect
|
github.com/go-git/go-git/v5 v5.1.0
|
||||||
github.com/adrg/xdg v0.3.3
|
github.com/hashicorp/go-version v1.2.1 // indirect
|
||||||
github.com/alecthomas/chroma v0.9.2 // indirect
|
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
github.com/olekukonko/tablewriter v0.0.4
|
||||||
github.com/charmbracelet/glamour v0.3.0
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
|
||||||
github.com/go-git/go-git/v5 v5.4.2
|
|
||||||
github.com/hashicorp/go-version v1.3.0 // indirect
|
|
||||||
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.15 // indirect
|
|
||||||
github.com/muesli/reflow v0.3.0 // indirect
|
|
||||||
github.com/muesli/termenv v0.9.0
|
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
|
||||||
github.com/sergi/go-diff v1.2.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.5.1
|
||||||
github.com/urfave/cli/v2 v2.3.0
|
github.com/urfave/cli/v2 v2.2.0
|
||||||
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
|
||||||
github.com/yuin/goldmark v1.4.0 // indirect
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
|
||||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
|
golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed // indirect
|
||||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
|
gopkg.in/yaml.v2 v2.3.0
|
||||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
|
||||||
golang.org/x/text v0.3.7 // indirect
|
|
||||||
golang.org/x/tools v0.1.5 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/charmbracelet/glamour => github.com/noerw/glamour v0.3.0-patch
|
|
||||||
|
263
go.sum
263
go.sum
@ -1,56 +1,24 @@
|
|||||||
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
|
code.gitea.io/sdk/gitea v0.12.1 h1:bMgjEqPnNX/i6TpVwXwpjJtFOnUSuC9P6yy/jjy8sjY=
|
||||||
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
code.gitea.io/sdk/gitea v0.12.1/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
|
||||||
code.gitea.io/sdk/gitea v0.15.0 h1:tsNhxDM/2N1Ohv1Xq5UWrht/esg0WmtRj4wsHVHriTg=
|
gitea.com/jolheiser/gitea-vet v0.1.0 h1:gJEms9YWbIcrPOEmDOJ+5JZXCYFxNpwxlI73uRulAi4=
|
||||||
code.gitea.io/sdk/gitea v0.15.0/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
|
gitea.com/jolheiser/gitea-vet v0.1.0/go.mod h1:2Oa6TAdEp1N/38oBNh3ZeiSEER60D/CeDaBFv2sdH58=
|
||||||
gitea.com/noerw/unidiff-comments v0.0.0-20201219085024-64aec5658f2b h1:CLYsMGcGLohESQDMth+RgJ4cB3CCHToxnj0zBbvB3sE=
|
|
||||||
gitea.com/noerw/unidiff-comments v0.0.0-20201219085024-64aec5658f2b/go.mod h1:Fc8iyPm4NINRWujeIk2bTfcbGc4ZYY29/oMAAGcr4qI=
|
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.1 h1:lzkuHA60pER7L4eYL8qQJor4bUWlJe4V0gqAT19tdOA=
|
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.1/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
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/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||||
github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU=
|
|
||||||
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c h1:FP7mMdsXy0ybzar1sJeIcZtaJka0U/ZmLTW4wRpolYk=
|
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
|
||||||
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
|
|
||||||
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
|
||||||
github.com/adrg/xdg v0.3.3 h1:s/tV7MdqQnzB1nKY8aqHvAMD+uCiuEDzVB5HLRY849U=
|
|
||||||
github.com/adrg/xdg v0.3.3/go.mod h1:61xAR2VZcggl2St4O9ohF5qCKe08+JDmE4VNzPFQvOQ=
|
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
|
||||||
github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
|
||||||
github.com/alecthomas/chroma v0.9.2 h1:yU1sE2+TZbLIQPMk30SolL2Hn53SR/Pv750f7qZ/XMs=
|
|
||||||
github.com/alecthomas/chroma v0.9.2/go.mod h1:eMuEnpA18XbG/WhOWtCzJHS7WqEtDAI+HxdwoW0nVSk=
|
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
|
||||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
|
||||||
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-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1 h1:TEBmxO80TM04L8IuMWk77SGL1HomBmKTdzdJLLWznxI=
|
||||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
|
||||||
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/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||||
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/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
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/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
|
||||||
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/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
|
||||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
|
||||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||||
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/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||||
@ -59,201 +27,118 @@ 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-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.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
|
||||||
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
|
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/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
|
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
|
||||||
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.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk=
|
||||||
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
|
github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
|
||||||
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/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
||||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
|
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
|
||||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
|
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
|
||||||
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
|
||||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
|
||||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
|
||||||
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.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
|
||||||
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/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o=
|
|
||||||
github.com/kevinburke/ssh_config v1.1.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/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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
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.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||||
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.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
|
||||||
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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
|
||||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
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/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-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.6/go.mod h1:HOT/6NaBlR0f9XlxD3zolN6Z3N8Lp4pvhp+jLS5ihnI=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.15 h1:J4uN+qPng9rvkBZBoBb8YGR+ijuklIMpSOZZLjYpbeY=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.15/go.mod h1:ZLvAzeakRwrGnzQEvstVzVt3ZpqOF2+sdFr0Om+ce30=
|
|
||||||
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/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVhUqLX8=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
|
||||||
github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0=
|
|
||||||
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
|
|
||||||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/noerw/glamour v0.3.0-patch h1:yc3wdbUIySok6KYeX5BtWnlj+PvP1uYeCeTSwq2rtSw=
|
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||||
github.com/noerw/glamour v0.3.0-patch/go.mod h1:TzF0koPZhqq0YVBNL100cPHznAAjVj7fksX2RInwjGw=
|
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/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
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=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 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/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|
||||||
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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
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/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/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
|
||||||
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/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
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 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
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/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
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.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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
||||||
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
|
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||||
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
|
|
||||||
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.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.3/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
github.com/yuin/goldmark v1.4.0 h1:OtISOGfH6sOWa1/qXqqAiOIAO6Z5J3AEAE18WAq6BiQ=
|
|
||||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
|
|
||||||
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-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||||
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.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||||
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
|
|
||||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-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-20190222072716-a9d3bda3a223/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-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-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
||||||
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-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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
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-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
|
|
||||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/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-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
|
||||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
|
|
||||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
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 h1:azwY/v0y0K4mFHVsg5+UrTgchqALYWpqVo6vL5OmkmI=
|
||||||
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.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed h1:+qzWo37K31KxduIYaBeMqJ8MUOyTayOQKpH9aDPLMSY=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
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 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
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/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=
|
|
||||||
|
86
main.go
86
main.go
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
// Copyright 2018 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.
|
||||||
|
|
||||||
@ -6,11 +6,12 @@
|
|||||||
package main // import "code.gitea.io/tea"
|
package main // import "code.gitea.io/tea"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd"
|
"code.gitea.io/tea/cmd"
|
||||||
|
"code.gitea.io/tea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@ -21,41 +22,33 @@ var Version = "development"
|
|||||||
// Tags holds the build tags used
|
// Tags holds the build tags used
|
||||||
var Tags = ""
|
var Tags = ""
|
||||||
|
|
||||||
func main() {
|
func init() {
|
||||||
// make parsing tea --version easier, by printing /just/ the version string
|
setting.AppVer = Version
|
||||||
cli.VersionPrinter = func(c *cli.Context) { fmt.Fprintln(c.App.Writer, c.App.Version) }
|
setting.AppBuiltWith = formatBuiltWith(Tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "tea"
|
app.Name = "tea"
|
||||||
app.Usage = "command line tool to interact with Gitea"
|
app.Usage = "Command line tool to interact with Gitea"
|
||||||
app.Description = appDescription
|
app.Description = ``
|
||||||
app.CustomAppHelpTemplate = helpTemplate
|
|
||||||
app.Version = Version + formatBuiltWith(Tags)
|
app.Version = Version + formatBuiltWith(Tags)
|
||||||
app.Commands = []*cli.Command{
|
app.Commands = []*cli.Command{
|
||||||
&cmd.CmdLogin,
|
&cmd.CmdLogin,
|
||||||
&cmd.CmdLogout,
|
&cmd.CmdLogout,
|
||||||
&cmd.CmdAutocomplete,
|
|
||||||
|
|
||||||
&cmd.CmdIssues,
|
&cmd.CmdIssues,
|
||||||
&cmd.CmdPulls,
|
&cmd.CmdPulls,
|
||||||
&cmd.CmdLabels,
|
|
||||||
&cmd.CmdMilestones,
|
|
||||||
&cmd.CmdReleases,
|
&cmd.CmdReleases,
|
||||||
&cmd.CmdTrackedTimes,
|
|
||||||
&cmd.CmdOrgs,
|
|
||||||
&cmd.CmdRepos,
|
&cmd.CmdRepos,
|
||||||
&cmd.CmdAddComment,
|
&cmd.CmdLabels,
|
||||||
|
&cmd.CmdTrackedTimes,
|
||||||
&cmd.CmdOpen,
|
&cmd.CmdOpen,
|
||||||
&cmd.CmdNotifications,
|
&cmd.CmdNotifications,
|
||||||
}
|
}
|
||||||
app.EnableBashCompletion = true
|
app.EnableBashCompletion = true
|
||||||
err := app.Run(os.Args)
|
err := app.Run(os.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// app.Run already exits for errors implementing ErrorCoder,
|
log.Fatalf("Failed to run app with %s: %v", os.Args, err)
|
||||||
// so we only handle generic errors with code 1 here.
|
|
||||||
fmt.Fprintf(app.ErrWriter, "Error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,56 +59,3 @@ func formatBuiltWith(Tags string) string {
|
|||||||
|
|
||||||
return " built with: " + strings.Replace(Tags, " ", ", ", -1)
|
return " built with: " + strings.Replace(Tags, " ", ", ", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var appDescription = `tea is a productivity helper for Gitea. It can be used to manage most entities on one
|
|
||||||
or multiple Gitea instances and provides local helpers like 'tea pull checkout'.
|
|
||||||
tea makes use of context provided by the repository in $PWD if available, but is still
|
|
||||||
usable independently of $PWD. Configuration is persisted in $XDG_CONFIG_HOME/tea.
|
|
||||||
`
|
|
||||||
|
|
||||||
var helpTemplate = bold(`
|
|
||||||
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}`) + `
|
|
||||||
{{if .Version}}{{if not .HideVersion}}version {{.Version}}{{end}}{{end}}
|
|
||||||
|
|
||||||
USAGE
|
|
||||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .Commands}} command [subcommand] [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
{{.Description | nindent 3 | trim}}{{end}}{{if .VisibleCommands}}
|
|
||||||
|
|
||||||
COMMANDS{{range .VisibleCategories}}{{if .Name}}
|
|
||||||
{{.Name}}:{{range .VisibleCommands}}
|
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
|
|
||||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
|
||||||
{{end}}{{$option}}{{end}}{{end}}
|
|
||||||
|
|
||||||
EXAMPLES
|
|
||||||
tea login add # add a login once to get started
|
|
||||||
|
|
||||||
tea pulls # list open pulls for the repo in $PWD
|
|
||||||
tea pulls --repo $HOME/foo # list open pulls for the repo in $HOME/foo
|
|
||||||
tea pulls --remote upstream # list open pulls for the repo pointed at by
|
|
||||||
# your local "upstream" git remote
|
|
||||||
# list open pulls for any gitea repo at the given login instance
|
|
||||||
tea pulls --repo gitea/tea --login gitea.com
|
|
||||||
|
|
||||||
tea milestone issues 0.7.0 # view open issues for milestone '0.7.0'
|
|
||||||
tea issue 189 # view contents of issue 189
|
|
||||||
tea open 189 # open web ui for issue 189
|
|
||||||
tea open milestones # open web ui for milestones
|
|
||||||
|
|
||||||
# send gitea desktop notifications every 5 minutes (bash + libnotify)
|
|
||||||
while :; do tea notifications --all -o simple | xargs -i notify-send {}; sleep 300; done
|
|
||||||
|
|
||||||
ABOUT
|
|
||||||
Written & maintained by The Gitea Authors.
|
|
||||||
If you find a bug or want to contribute, we'll welcome you at https://gitea.com/gitea/tea.
|
|
||||||
More info about Gitea itself on https://gitea.io.
|
|
||||||
`
|
|
||||||
|
|
||||||
func bold(t string) string {
|
|
||||||
return fmt.Sprintf("\033[1m%s\033[0m", t)
|
|
||||||
}
|
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
"github.com/adrg/xdg"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LocalConfig represents local configurations
|
|
||||||
type LocalConfig struct {
|
|
||||||
Logins []Login `yaml:"logins"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// config contain if loaded local tea config
|
|
||||||
config LocalConfig
|
|
||||||
loadConfigOnce sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetConfigPath return path to tea config file
|
|
||||||
func GetConfigPath() string {
|
|
||||||
configFilePath, err := xdg.ConfigFile("tea/config.yml")
|
|
||||||
|
|
||||||
var exists bool
|
|
||||||
if err != nil {
|
|
||||||
exists = false
|
|
||||||
} else {
|
|
||||||
exists, _ = utils.PathExists(configFilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallback to old config if no new one exists
|
|
||||||
if !exists {
|
|
||||||
file := filepath.Join(xdg.Home, ".tea", "tea.yml")
|
|
||||||
exists, _ = utils.PathExists(file)
|
|
||||||
if exists {
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("unable to get or create config file")
|
|
||||||
}
|
|
||||||
|
|
||||||
return configFilePath
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadConfig load config from file
|
|
||||||
func loadConfig() (err error) {
|
|
||||||
loadConfigOnce.Do(func() {
|
|
||||||
ymlPath := GetConfigPath()
|
|
||||||
exist, _ := utils.FileExist(ymlPath)
|
|
||||||
if exist {
|
|
||||||
bs, err := ioutil.ReadFile(ymlPath)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Failed to read config file: %s", ymlPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = yaml.Unmarshal(bs, &config)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Failed to parse contents of config file: %s", ymlPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// saveConfig save config to file
|
|
||||||
func saveConfig() error {
|
|
||||||
ymlPath := GetConfigPath()
|
|
||||||
bs, err := yaml.Marshal(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ioutil.WriteFile(ymlPath, bs, 0660)
|
|
||||||
}
|
|
@ -1,180 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Login represents a login to a gitea server, you even could add multiple logins for one gitea server
|
|
||||||
type Login struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
URL string `yaml:"url"`
|
|
||||||
Token string `yaml:"token"`
|
|
||||||
Default bool `yaml:"default"`
|
|
||||||
SSHHost string `yaml:"ssh_host"`
|
|
||||||
// optional path to the private key
|
|
||||||
SSHKey string `yaml:"ssh_key"`
|
|
||||||
Insecure bool `yaml:"insecure"`
|
|
||||||
// User is username from gitea
|
|
||||||
User string `yaml:"user"`
|
|
||||||
// Created is auto created unix timestamp
|
|
||||||
Created int64 `yaml:"created"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLogins return all login available by config
|
|
||||||
func GetLogins() ([]Login, error) {
|
|
||||||
if err := loadConfig(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return config.Logins, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDefaultLogin return the default login
|
|
||||||
func GetDefaultLogin() (*Login, error) {
|
|
||||||
if err := loadConfig(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config.Logins) == 0 {
|
|
||||||
return nil, errors.New("No available login")
|
|
||||||
}
|
|
||||||
for _, l := range config.Logins {
|
|
||||||
if l.Default {
|
|
||||||
return &l, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &config.Logins[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDefaultLogin set the default login by name (case insensitive)
|
|
||||||
func SetDefaultLogin(name string) error {
|
|
||||||
if err := loadConfig(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
loginExist := false
|
|
||||||
for i := range config.Logins {
|
|
||||||
config.Logins[i].Default = false
|
|
||||||
if strings.ToLower(config.Logins[i].Name) == strings.ToLower(name) {
|
|
||||||
config.Logins[i].Default = true
|
|
||||||
loginExist = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !loginExist {
|
|
||||||
return fmt.Errorf("login '%s' not found", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return saveConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLoginByName get login by name (case insensitive)
|
|
||||||
func GetLoginByName(name string) *Login {
|
|
||||||
err := loadConfig()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, l := range config.Logins {
|
|
||||||
if strings.ToLower(l.Name) == strings.ToLower(name) {
|
|
||||||
return &l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLoginByToken get login by token
|
|
||||||
func GetLoginByToken(token string) *Login {
|
|
||||||
err := loadConfig()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, l := range config.Logins {
|
|
||||||
if l.Token == token {
|
|
||||||
return &l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteLogin delete a login by name from config
|
|
||||||
func DeleteLogin(name string) error {
|
|
||||||
var idx = -1
|
|
||||||
for i, l := range config.Logins {
|
|
||||||
if l.Name == name {
|
|
||||||
idx = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if idx == -1 {
|
|
||||||
return fmt.Errorf("can not delete login '%s', does not exist", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Logins = append(config.Logins[:idx], config.Logins[idx+1:]...)
|
|
||||||
|
|
||||||
return saveConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddLogin save a login to config
|
|
||||||
func AddLogin(login *Login) error {
|
|
||||||
if err := loadConfig(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// save login to global var
|
|
||||||
config.Logins = append(config.Logins, *login)
|
|
||||||
|
|
||||||
// save login to config file
|
|
||||||
return saveConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client returns a client to operate Gitea API. You may provide additional modifiers
|
|
||||||
// for the client like gitea.SetBasicAuth() for customization
|
|
||||||
func (l *Login) Client(options ...func(*gitea.Client)) *gitea.Client {
|
|
||||||
httpClient := &http.Client{}
|
|
||||||
if l.Insecure {
|
|
||||||
cookieJar, _ := cookiejar.New(nil)
|
|
||||||
|
|
||||||
httpClient = &http.Client{
|
|
||||||
Jar: cookieJar,
|
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
options = append(options, gitea.SetToken(l.Token), gitea.SetHTTPClient(httpClient))
|
|
||||||
|
|
||||||
client, err := gitea.NewClient(l.URL, options...)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSSHHost returns SSH host name
|
|
||||||
func (l *Login) GetSSHHost() string {
|
|
||||||
if l.SSHHost != "" {
|
|
||||||
return l.SSHHost
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(l.URL)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return u.Hostname()
|
|
||||||
}
|
|
@ -1,212 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"code.gitea.io/tea/modules/config"
|
|
||||||
"code.gitea.io/tea/modules/git"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
gogit "github.com/go-git/go-git/v5"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errNotAGiteaRepo = errors.New("No Gitea login found. You might want to specify --repo (and --login) to work outside of a repository")
|
|
||||||
)
|
|
||||||
|
|
||||||
// TeaContext contains all context derived during command initialization and wraps cli.Context
|
|
||||||
type TeaContext struct {
|
|
||||||
*cli.Context
|
|
||||||
Login *config.Login // config data & client for selected login
|
|
||||||
RepoSlug string // <owner>/<repo>, optional
|
|
||||||
Owner string // repo owner as derived from context or provided in flag, optional
|
|
||||||
Repo string // repo name as derived from context or provided in flag, optional
|
|
||||||
Output string // value of output flag
|
|
||||||
LocalRepo *git.TeaRepo // is set if flags specified a local repo via --repo, or if $PWD is a git repo
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetListOptions return ListOptions based on PaginationFlags
|
|
||||||
func (ctx *TeaContext) GetListOptions() gitea.ListOptions {
|
|
||||||
page := ctx.Int("page")
|
|
||||||
limit := ctx.Int("limit")
|
|
||||||
if limit < 0 {
|
|
||||||
limit = 0
|
|
||||||
}
|
|
||||||
if limit != 0 && page == 0 {
|
|
||||||
page = 1
|
|
||||||
}
|
|
||||||
return gitea.ListOptions{
|
|
||||||
Page: page,
|
|
||||||
PageSize: limit,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure checks if requirements on the context are set, and terminates otherwise.
|
|
||||||
func (ctx *TeaContext) Ensure(req CtxRequirement) {
|
|
||||||
if req.LocalRepo && ctx.LocalRepo == nil {
|
|
||||||
fmt.Println("Local repository required: Execute from a repo dir, or specify a path with --repo.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.RemoteRepo && len(ctx.RepoSlug) == 0 {
|
|
||||||
fmt.Println("Remote repository required: Specify ID via --repo or execute from a local git repo.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CtxRequirement specifies context needed for operation
|
|
||||||
type CtxRequirement struct {
|
|
||||||
// ensures a local git repo is available & ctx.LocalRepo is set. Implies .RemoteRepo
|
|
||||||
LocalRepo bool
|
|
||||||
// ensures ctx.RepoSlug, .Owner, .Repo are set
|
|
||||||
RemoteRepo bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitCommand resolves the application context, and returns the active login, and if
|
|
||||||
// available the repo slug. It does this by reading the config file for logins, parsing
|
|
||||||
// the remotes of the .git repo specified in repoFlag or $PWD, and using overrides from
|
|
||||||
// command flags. If a local git repo can't be found, repo slug values are unset.
|
|
||||||
func InitCommand(ctx *cli.Context) *TeaContext {
|
|
||||||
// these flags are used as overrides to the context detection via local git repo
|
|
||||||
repoFlag := ctx.String("repo")
|
|
||||||
loginFlag := ctx.String("login")
|
|
||||||
remoteFlag := ctx.String("remote")
|
|
||||||
|
|
||||||
var (
|
|
||||||
c TeaContext
|
|
||||||
err error
|
|
||||||
repoPath string // empty means PWD
|
|
||||||
repoFlagPathExists bool
|
|
||||||
)
|
|
||||||
|
|
||||||
// check if repoFlag can be interpreted as path to local repo.
|
|
||||||
if len(repoFlag) != 0 {
|
|
||||||
if repoFlagPathExists, err = utils.DirExists(repoFlag); err != nil {
|
|
||||||
log.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
if repoFlagPathExists {
|
|
||||||
repoPath = repoFlag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to read local git repo & extract context: if repoFlag specifies a valid path, read repo in that dir,
|
|
||||||
// otherwise attempt PWD. if no repo is found, continue with default login
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(repoFlag) != 0 && !repoFlagPathExists {
|
|
||||||
// if repoFlag is not a valid path, use it to override repoSlug
|
|
||||||
c.RepoSlug = repoFlag
|
|
||||||
}
|
|
||||||
|
|
||||||
// override login from flag, or use default login if repo based detection failed
|
|
||||||
if len(loginFlag) != 0 {
|
|
||||||
c.Login = config.GetLoginByName(loginFlag)
|
|
||||||
if c.Login == nil {
|
|
||||||
log.Fatalf("Login name '%s' does not exist", loginFlag)
|
|
||||||
}
|
|
||||||
} else if c.Login == nil {
|
|
||||||
if c.Login, err = config.GetDefaultLogin(); err != nil {
|
|
||||||
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)
|
|
||||||
c.Owner, c.Repo = utils.GetOwnerAndRepo(c.RepoSlug, c.Login.User)
|
|
||||||
|
|
||||||
c.Context = ctx
|
|
||||||
c.Output = ctx.String("output")
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
// contextFromLocalRepo discovers login & repo slug from the default branch remote of the given local repo
|
|
||||||
func contextFromLocalRepo(repoPath, remoteValue string) (*git.TeaRepo, *config.Login, string, error) {
|
|
||||||
repo, err := git.RepoFromPath(repoPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, "", err
|
|
||||||
}
|
|
||||||
gitConfig, err := repo.Config()
|
|
||||||
if err != nil {
|
|
||||||
return repo, nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no remote
|
|
||||||
if len(gitConfig.Remotes) == 0 {
|
|
||||||
return repo, nil, "", errors.New("No remote(s) found in this Git repository")
|
|
||||||
}
|
|
||||||
|
|
||||||
// if only one remote exists
|
|
||||||
if len(gitConfig.Remotes) >= 1 && len(remoteValue) == 0 {
|
|
||||||
for remote := range gitConfig.Remotes {
|
|
||||||
remoteValue = remote
|
|
||||||
}
|
|
||||||
if len(gitConfig.Remotes) > 1 {
|
|
||||||
// if master branch is present, use it as the default remote
|
|
||||||
mainBranches := []string{"main", "master", "trunk"}
|
|
||||||
for _, b := range mainBranches {
|
|
||||||
masterBranch, ok := gitConfig.Branches[b]
|
|
||||||
if ok {
|
|
||||||
if len(masterBranch.Remote) > 0 {
|
|
||||||
remoteValue = masterBranch.Remote
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteConfig, ok := gitConfig.Remotes[remoteValue]
|
|
||||||
if !ok || remoteConfig == nil {
|
|
||||||
return repo, nil, "", fmt.Errorf("Remote '%s' not found in this Git repository", remoteValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
logins, err := config.GetLogins()
|
|
||||||
if err != nil {
|
|
||||||
return repo, nil, "", err
|
|
||||||
}
|
|
||||||
for _, l := range logins {
|
|
||||||
sshHost := l.GetSSHHost()
|
|
||||||
for _, u := range remoteConfig.URLs {
|
|
||||||
p, err := git.ParseURL(u)
|
|
||||||
if err != nil {
|
|
||||||
return repo, nil, "", fmt.Errorf("Git remote URL parse failed: %s", err.Error())
|
|
||||||
}
|
|
||||||
if strings.EqualFold(p.Scheme, "http") || strings.EqualFold(p.Scheme, "https") {
|
|
||||||
if strings.HasPrefix(u, l.URL) {
|
|
||||||
ps := strings.Split(p.Path, "/")
|
|
||||||
path := strings.Join(ps[len(ps)-2:], "/")
|
|
||||||
return repo, &l, strings.TrimSuffix(path, ".git"), nil
|
|
||||||
}
|
|
||||||
} else if strings.EqualFold(p.Scheme, "ssh") {
|
|
||||||
if sshHost == p.Host {
|
|
||||||
return repo, &l, strings.TrimLeft(p.Path, "/"), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo, nil, "", errNotAGiteaRepo
|
|
||||||
}
|
|
@ -5,72 +5,116 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
git_transport "github.com/go-git/go-git/v5/plumbing/transport"
|
git_transport "github.com/go-git/go-git/v5/plumbing/transport"
|
||||||
gogit_http "github.com/go-git/go-git/v5/plumbing/transport/http"
|
gogit_http "github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||||
gogit_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
gogit_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pwCallback = func(string) (string, error)
|
|
||||||
|
|
||||||
// GetAuthForURL returns the appropriate AuthMethod to be used in Push() / Pull()
|
// GetAuthForURL returns the appropriate AuthMethod to be used in Push() / Pull()
|
||||||
// operations depending on the protocol, and prompts the user for credentials if
|
// operations depending on the protocol, and prompts the user for credentials if
|
||||||
// necessary.
|
// necessary.
|
||||||
func GetAuthForURL(remoteURL *url.URL, authToken, keyFile string, passwordCallback pwCallback) (git_transport.AuthMethod, error) {
|
func GetAuthForURL(remoteURL *url.URL, httpUser, keyFile string) (auth git_transport.AuthMethod, err error) {
|
||||||
|
user := remoteURL.User.Username()
|
||||||
|
|
||||||
switch remoteURL.Scheme {
|
switch remoteURL.Scheme {
|
||||||
case "http", "https":
|
case "https":
|
||||||
// gitea supports push/pull via app token as username.
|
if httpUser != "" {
|
||||||
return &gogit_http.BasicAuth{Password: "", Username: authToken}, nil
|
user = httpUser
|
||||||
|
}
|
||||||
|
if user == "" {
|
||||||
|
user, err = promptUser(remoteURL.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pass, isSet := remoteURL.User.Password()
|
||||||
|
if !isSet {
|
||||||
|
pass, err = promptPass(remoteURL.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auth = &gogit_http.BasicAuth{Password: pass, Username: user}
|
||||||
|
|
||||||
case "ssh":
|
case "ssh":
|
||||||
// try to select right key via ssh-agent. if it fails, try to read a key manually
|
// try to select right key via ssh-agent. if it fails, try to read a key manually
|
||||||
user := remoteURL.User.Username()
|
auth, err = gogit_ssh.DefaultAuthBuilder(user)
|
||||||
auth, err := gogit_ssh.DefaultAuthBuilder(user)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
signer, err2 := readSSHPrivKey(keyFile, passwordCallback)
|
signer, err := readSSHPrivKey(keyFile)
|
||||||
if err2 != nil {
|
if err != nil {
|
||||||
return nil, err2
|
return nil, err
|
||||||
}
|
}
|
||||||
auth = &gogit_ssh.PublicKeys{User: user, Signer: signer}
|
auth = &gogit_ssh.PublicKeys{User: user, Signer: signer}
|
||||||
}
|
}
|
||||||
return auth, nil
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("don't know how to handle url scheme %v", remoteURL.Scheme)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("don't know how to handle url scheme %v", remoteURL.Scheme)
|
|
||||||
|
return auth, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readSSHPrivKey(keyFile string, passwordCallback pwCallback) (sig ssh.Signer, err error) {
|
func readSSHPrivKey(keyFile string) (sig ssh.Signer, err error) {
|
||||||
if keyFile != "" {
|
if keyFile != "" {
|
||||||
keyFile, err = utils.AbsPathWithExpansion(keyFile)
|
keyFile, err = absPathWithExpansion(keyFile)
|
||||||
} else {
|
} else {
|
||||||
keyFile, err = utils.AbsPathWithExpansion("~/.ssh/id_rsa")
|
keyFile, err = absPathWithExpansion("~/.ssh/id_rsa")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sshKey, err := ioutil.ReadFile(keyFile)
|
sshKey, err := ioutil.ReadFile(keyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can not read ssh key '%s'", keyFile)
|
return nil, err
|
||||||
}
|
}
|
||||||
sig, err = ssh.ParsePrivateKey(sshKey)
|
sig, err = ssh.ParsePrivateKey(sshKey)
|
||||||
if _, ok := err.(*ssh.PassphraseMissingError); ok && passwordCallback != nil {
|
if err != nil {
|
||||||
// allow for up to 3 password attempts
|
pass, err := promptPass(keyFile)
|
||||||
for i := 0; i < 3; i++ {
|
if err != nil {
|
||||||
var pass string
|
return nil, err
|
||||||
pass, err = passwordCallback(keyFile)
|
}
|
||||||
if err != nil {
|
sig, err = ssh.ParsePrivateKeyWithPassphrase(sshKey, []byte(pass))
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
sig, err = ssh.ParsePrivateKeyWithPassphrase(sshKey, []byte(pass))
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sig, err
|
return sig, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func promptUser(domain string) (string, error) {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
fmt.Printf("%s username: ", domain)
|
||||||
|
username, err := reader.ReadString('\n')
|
||||||
|
return strings.TrimSpace(username), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func promptPass(domain string) (string, error) {
|
||||||
|
fmt.Printf("%s password: ", domain)
|
||||||
|
pass, err := terminal.ReadPassword(0)
|
||||||
|
return string(pass), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func absPathWithExpansion(p string) (string, error) {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if p == "~" {
|
||||||
|
return u.HomeDir, nil
|
||||||
|
} else if strings.HasPrefix(p, "~/") {
|
||||||
|
return filepath.Join(u.HomeDir, p[2:]), nil
|
||||||
|
} else {
|
||||||
|
return filepath.Abs(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -38,36 +38,42 @@ func (r TeaRepo) TeaCreateBranch(localBranchName, remoteBranchName, remoteName s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TeaCheckout checks out the given branch in the worktree.
|
// TeaCheckout checks out the given branch in the worktree.
|
||||||
func (r TeaRepo) TeaCheckout(ref git_plumbing.ReferenceName) error {
|
func (r TeaRepo) TeaCheckout(branchName string) error {
|
||||||
tree, err := r.Worktree()
|
tree, err := r.Worktree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return tree.Checkout(&git.CheckoutOptions{Branch: ref})
|
localBranchRefName := git_plumbing.NewBranchReferenceName(branchName)
|
||||||
|
return tree.Checkout(&git.CheckoutOptions{Branch: localBranchRefName})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TeaDeleteLocalBranch removes the given branch locally
|
// TeaDeleteBranch removes the given branch locally, and if `remoteBranch` is
|
||||||
func (r TeaRepo) TeaDeleteLocalBranch(branch *git_config.Branch) error {
|
// not empty deletes it at it's remote repo.
|
||||||
|
func (r TeaRepo) TeaDeleteBranch(branch *git_config.Branch, remoteBranch string, auth git_transport.AuthMethod) error {
|
||||||
err := r.DeleteBranch(branch.Name)
|
err := r.DeleteBranch(branch.Name)
|
||||||
// if the branch is not found that's ok, as .git/config may have no entry if
|
// if the branch is not found that's ok, as .git/config may have no entry if
|
||||||
// no remote tracking branch is configured for it (eg push without -u flag)
|
// no remote tracking branch is configured for it (eg push without -u flag)
|
||||||
if err != nil && err.Error() != "branch not found" {
|
if err != nil && err.Error() != "branch not found" {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return r.Storer.RemoveReference(git_plumbing.NewBranchReferenceName(branch.Name))
|
err = r.Storer.RemoveReference(git_plumbing.NewBranchReferenceName(branch.Name))
|
||||||
}
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// TeaDeleteRemoteBranch removes the given branch on the given remote via git protocol
|
if remoteBranch != "" {
|
||||||
func (r TeaRepo) TeaDeleteRemoteBranch(remoteName, remoteBranch string, auth git_transport.AuthMethod) error {
|
// delete remote branch via git protocol:
|
||||||
// delete remote branch via git protocol:
|
// an empty source in the refspec means remote deletion to git 🙃
|
||||||
// an empty source in the refspec means remote deletion to git 🙃
|
refspec := fmt.Sprintf(":%s", git_plumbing.NewBranchReferenceName(remoteBranch))
|
||||||
refspec := fmt.Sprintf(":%s", git_plumbing.NewBranchReferenceName(remoteBranch))
|
err = r.Push(&git.PushOptions{
|
||||||
return r.Push(&git.PushOptions{
|
RemoteName: branch.Remote,
|
||||||
RemoteName: remoteName,
|
RefSpecs: []git_config.RefSpec{git_config.RefSpec(refspec)},
|
||||||
RefSpecs: []git_config.RefSpec{git_config.RefSpec(refspec)},
|
Prune: true,
|
||||||
Prune: true,
|
Auth: auth,
|
||||||
Auth: auth,
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TeaFindBranchBySha returns a branch that is at the the given SHA and syncs to the
|
// TeaFindBranchBySha returns a branch that is at the the given SHA and syncs to the
|
||||||
@ -170,48 +176,6 @@ func (r TeaRepo) TeaFindBranchByName(branchName, repoURL string) (b *git_config.
|
|||||||
return b, b.Validate()
|
return b, b.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TeaFindBranchRemote gives the first remote that has a branch with the same name or sha,
|
|
||||||
// depending on what is passed in.
|
|
||||||
// This function is needed, as git does not always define branches in .git/config with remote entries.
|
|
||||||
func (r TeaRepo) TeaFindBranchRemote(branchName, hash string) (*git.Remote, error) {
|
|
||||||
remotes, err := r.Remotes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case len(remotes) == 0:
|
|
||||||
return nil, nil
|
|
||||||
case len(remotes) == 1:
|
|
||||||
return remotes[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if the given remote has our branch (.git/refs/remotes/<remoteName>/*)
|
|
||||||
iter, err := r.References()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer iter.Close()
|
|
||||||
|
|
||||||
var match *git.Remote
|
|
||||||
err = iter.ForEach(func(ref *git_plumbing.Reference) error {
|
|
||||||
if ref.Name().IsRemote() {
|
|
||||||
names := strings.SplitN(ref.Name().Short(), "/", 2)
|
|
||||||
remote := names[0]
|
|
||||||
branch := names[1]
|
|
||||||
hashMatch := hash != "" && hash == ref.Hash().String()
|
|
||||||
nameMatch := branchName != "" && branchName == branch
|
|
||||||
if hashMatch || nameMatch {
|
|
||||||
match, err = r.Remote(remote)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return match, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TeaGetCurrentBranchName return the name of the branch witch is currently active
|
// TeaGetCurrentBranchName return the name of the branch witch is currently active
|
||||||
func (r TeaRepo) TeaGetCurrentBranchName() (string, error) {
|
func (r TeaRepo) TeaGetCurrentBranchName() (string, error) {
|
||||||
localHead, err := r.Head()
|
localHead, err := r.Head()
|
||||||
@ -223,5 +187,5 @@ func (r TeaRepo) TeaGetCurrentBranchName() (string, error) {
|
|||||||
return "", fmt.Errorf("active ref is no branch")
|
return "", fmt.Errorf("active ref is no branch")
|
||||||
}
|
}
|
||||||
|
|
||||||
return localHead.Name().Short(), nil
|
return strings.TrimLeft(localHead.Name().String(), "refs/heads/"), nil
|
||||||
}
|
}
|
||||||
|
@ -16,14 +16,18 @@ type TeaRepo struct {
|
|||||||
// RepoForWorkdir tries to open the git repository in the local directory
|
// RepoForWorkdir tries to open the git repository in the local directory
|
||||||
// for reading or modification.
|
// for reading or modification.
|
||||||
func RepoForWorkdir() (*TeaRepo, error) {
|
func RepoForWorkdir() (*TeaRepo, error) {
|
||||||
return RepoFromPath("")
|
repo, err := git.PlainOpenWithOptions("./", &git.PlainOpenOptions{
|
||||||
|
DetectDotGit: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TeaRepo{repo}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepoFromPath tries to open the git repository by path
|
// RepoFromPath tries to open the git repository by path
|
||||||
func RepoFromPath(path string) (*TeaRepo, error) {
|
func RepoFromPath(path string) (*TeaRepo, error) {
|
||||||
if len(path) == 0 {
|
|
||||||
path = "./"
|
|
||||||
}
|
|
||||||
repo, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{
|
repo, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{
|
||||||
DetectDotGit: true,
|
DetectDotGit: true,
|
||||||
})
|
})
|
||||||
|
@ -20,10 +20,6 @@ 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) {
|
||||||
rawURL = strings.TrimSpace(rawURL)
|
|
||||||
|
|
||||||
// convert the weird git ssh url format to a canonical url:
|
|
||||||
// git@gitea.com:gitea/tea -> ssh://git@gitea.com/gitea/tea
|
|
||||||
if !protocolRe.MatchString(rawURL) &&
|
if !protocolRe.MatchString(rawURL) &&
|
||||||
strings.Contains(rawURL, ":") &&
|
strings.Contains(rawURL, ":") &&
|
||||||
// not a Windows path
|
// not a Windows path
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package interact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/print"
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ShowCommentsMaybeInteractive fetches & prints comments, depending on the --comments flag.
|
|
||||||
// If that flag is unset, and output is not piped, prompts the user first.
|
|
||||||
func ShowCommentsMaybeInteractive(ctx *context.TeaContext, idx int64, totalComments int) error {
|
|
||||||
if ctx.Bool("comments") {
|
|
||||||
opts := gitea.ListIssueCommentOptions{ListOptions: ctx.GetListOptions()}
|
|
||||||
c := ctx.Login.Client()
|
|
||||||
comments, _, err := c.ListIssueComments(ctx.Owner, ctx.Repo, idx, opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
print.Comments(comments)
|
|
||||||
} else if IsInteractive() && !ctx.IsSet("comments") {
|
|
||||||
// if we're interactive, but --comments hasn't been explicitly set to false
|
|
||||||
if err := ShowCommentsPaginated(ctx, idx, totalComments); err != nil {
|
|
||||||
fmt.Printf("error while loading comments: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowCommentsPaginated prompts if issue/pr comments should be shown and continues to do so.
|
|
||||||
func ShowCommentsPaginated(ctx *context.TeaContext, idx int64, totalComments int) error {
|
|
||||||
c := ctx.Login.Client()
|
|
||||||
opts := gitea.ListIssueCommentOptions{ListOptions: ctx.GetListOptions()}
|
|
||||||
prompt := "show comments?"
|
|
||||||
commentsLoaded := 0
|
|
||||||
|
|
||||||
// paginated fetch
|
|
||||||
// NOTE: as of gitea 1.13, pagination is not provided by this endpoint, but handles
|
|
||||||
// this function gracefully anyways.
|
|
||||||
for {
|
|
||||||
loadComments := false
|
|
||||||
confirm := survey.Confirm{Message: prompt, Default: true}
|
|
||||||
if err := survey.AskOne(&confirm, &loadComments); err != nil {
|
|
||||||
return err
|
|
||||||
} else if !loadComments {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
if comments, _, err := c.ListIssueComments(ctx.Owner, ctx.Repo, idx, opts); err != nil {
|
|
||||||
return err
|
|
||||||
} else if len(comments) != 0 {
|
|
||||||
print.Comments(comments)
|
|
||||||
commentsLoaded += len(comments)
|
|
||||||
}
|
|
||||||
if commentsLoaded >= totalComments {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
opts.ListOptions.Page++
|
|
||||||
prompt = "load more?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
func IsStdinPiped() bool {
|
|
||||||
return !terminal.IsTerminal(int(os.Stdin.Fd()))
|
|
||||||
}
|
|
@ -1,162 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package interact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"code.gitea.io/tea/modules/config"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateIssue interactively creates an issue
|
|
||||||
func CreateIssue(login *config.Login, owner, repo string) error {
|
|
||||||
owner, repo, err := promptRepoSlug(owner, repo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts gitea.CreateIssueOption
|
|
||||||
if err := promptIssueProperties(login, owner, repo, &opts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.CreateIssue(login, owner, repo, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func promptIssueProperties(login *config.Login, owner, repo string, o *gitea.CreateIssueOption) error {
|
|
||||||
var milestoneName string
|
|
||||||
var labels []string
|
|
||||||
var err error
|
|
||||||
|
|
||||||
selectableChan := make(chan (issueSelectables), 1)
|
|
||||||
go fetchIssueSelectables(login, owner, repo, selectableChan)
|
|
||||||
|
|
||||||
// title
|
|
||||||
promptOpts := survey.WithValidator(survey.Required)
|
|
||||||
promptI := &survey.Input{Message: "Issue title:", Default: o.Title}
|
|
||||||
if err = survey.AskOne(promptI, &o.Title, promptOpts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// description
|
|
||||||
promptD := &survey.Multiline{Message: "Issue description:", Default: o.Body}
|
|
||||||
if err = survey.AskOne(promptD, &o.Body); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait until selectables are fetched
|
|
||||||
selectables := <-selectableChan
|
|
||||||
if selectables.Err != nil {
|
|
||||||
return selectables.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip remaining props if we don't have permission to set them
|
|
||||||
if !selectables.Repo.Permissions.Push {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// assignees
|
|
||||||
if o.Assignees, err = promptMultiSelect("Assignees:", selectables.Collaborators, "[other]"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// milestone
|
|
||||||
if len(selectables.MilestoneList) != 0 {
|
|
||||||
if milestoneName, err = promptSelect("Milestone:", selectables.MilestoneList, "", "[none]"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.Milestone = selectables.MilestoneMap[milestoneName]
|
|
||||||
}
|
|
||||||
|
|
||||||
// labels
|
|
||||||
if len(selectables.LabelList) != 0 {
|
|
||||||
promptL := &survey.MultiSelect{Message: "Labels:", Options: selectables.LabelList, VimMode: true, Default: o.Labels}
|
|
||||||
if err := survey.AskOne(promptL, &labels); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.Labels = make([]int64, len(labels))
|
|
||||||
for i, l := range labels {
|
|
||||||
o.Labels[i] = selectables.LabelMap[l]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// deadline
|
|
||||||
if o.Deadline, err = promptDatetime("Due date:"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type issueSelectables struct {
|
|
||||||
Repo *gitea.Repository
|
|
||||||
Collaborators []string
|
|
||||||
MilestoneList []string
|
|
||||||
MilestoneMap map[string]int64
|
|
||||||
LabelList []string
|
|
||||||
LabelMap map[string]int64
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchIssueSelectables(login *config.Login, owner, repo string, done chan issueSelectables) {
|
|
||||||
// TODO PERF make these calls concurrent
|
|
||||||
r := issueSelectables{}
|
|
||||||
c := login.Client()
|
|
||||||
|
|
||||||
r.Repo, _, r.Err = c.GetRepo(owner, repo)
|
|
||||||
if r.Err != nil {
|
|
||||||
done <- r
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// we can set the following properties only if we have write access to the repo
|
|
||||||
// so we fastpath this if not.
|
|
||||||
if !r.Repo.Permissions.Push {
|
|
||||||
done <- r
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: this should ideally be ListAssignees(), https://github.com/go-gitea/gitea/issues/14856
|
|
||||||
colabs, _, err := c.ListCollaborators(owner, repo, gitea.ListCollaboratorsOptions{})
|
|
||||||
if err != nil {
|
|
||||||
r.Err = err
|
|
||||||
done <- r
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.Collaborators = make([]string, len(colabs)+1)
|
|
||||||
r.Collaborators[0] = login.User
|
|
||||||
for i, u := range colabs {
|
|
||||||
r.Collaborators[i+1] = u.UserName
|
|
||||||
}
|
|
||||||
|
|
||||||
milestones, _, err := c.ListRepoMilestones(owner, repo, gitea.ListMilestoneOption{})
|
|
||||||
if err != nil {
|
|
||||||
r.Err = err
|
|
||||||
done <- r
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.MilestoneMap = make(map[string]int64)
|
|
||||||
r.MilestoneList = make([]string, len(milestones))
|
|
||||||
for i, m := range milestones {
|
|
||||||
r.MilestoneMap[m.Title] = m.ID
|
|
||||||
r.MilestoneList[i] = m.Title
|
|
||||||
}
|
|
||||||
|
|
||||||
labels, _, err := c.ListRepoLabels(owner, repo, gitea.ListLabelsOptions{})
|
|
||||||
if err != nil {
|
|
||||||
r.Err = err
|
|
||||||
done <- r
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.LabelMap = make(map[string]int64)
|
|
||||||
r.LabelList = make([]string, len(labels))
|
|
||||||
for i, l := range labels {
|
|
||||||
r.LabelMap[l.Name] = l.ID
|
|
||||||
r.LabelList[i] = l.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
done <- r
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package interact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateLogin create an login interactive
|
|
||||||
func CreateLogin() error {
|
|
||||||
var name, token, user, passwd, sshKey, giteaURL string
|
|
||||||
var insecure = false
|
|
||||||
|
|
||||||
promptI := &survey.Input{Message: "URL of Gitea instance: "}
|
|
||||||
if err := survey.AskOne(promptI, &giteaURL, survey.WithValidator(survey.Required)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
giteaURL = strings.TrimSuffix(strings.TrimSpace(giteaURL), "/")
|
|
||||||
if len(giteaURL) == 0 {
|
|
||||||
fmt.Println("URL is required!")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
name, err := task.GenerateLoginName(giteaURL, "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
promptI = &survey.Input{Message: "Name of new Login [" + name + "]: "}
|
|
||||||
if err := survey.AskOne(promptI, &name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasToken bool
|
|
||||||
promptYN := &survey.Confirm{
|
|
||||||
Message: "Do you have an access token?",
|
|
||||||
Default: false,
|
|
||||||
}
|
|
||||||
if err = survey.AskOne(promptYN, &hasToken); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasToken {
|
|
||||||
promptI = &survey.Input{Message: "Token: "}
|
|
||||||
if err := survey.AskOne(promptI, &token, survey.WithValidator(survey.Required)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
promptI = &survey.Input{Message: "Username: "}
|
|
||||||
if err = survey.AskOne(promptI, &user, survey.WithValidator(survey.Required)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
promptPW := &survey.Password{Message: "Password: "}
|
|
||||||
if err = survey.AskOne(promptPW, &passwd, survey.WithValidator(survey.Required)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var optSettings bool
|
|
||||||
promptYN = &survey.Confirm{
|
|
||||||
Message: "Set Optional settings: ",
|
|
||||||
Default: false,
|
|
||||||
}
|
|
||||||
if err = survey.AskOne(promptYN, &optSettings); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if optSettings {
|
|
||||||
promptI = &survey.Input{Message: "SSH Key Path (leave empty for auto-discovery):"}
|
|
||||||
if err := survey.AskOne(promptI, &sshKey); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
promptYN = &survey.Confirm{
|
|
||||||
Message: "Allow Insecure connections: ",
|
|
||||||
Default: false,
|
|
||||||
}
|
|
||||||
if err = survey.AskOne(promptYN, &insecure); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.CreateLogin(name, token, user, passwd, sshKey, giteaURL, insecure)
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package interact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/config"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateMilestone interactively creates a milestone
|
|
||||||
func CreateMilestone(login *config.Login, owner, repo string) error {
|
|
||||||
var title, description string
|
|
||||||
var deadline *time.Time
|
|
||||||
|
|
||||||
// owner, repo
|
|
||||||
owner, repo, err := promptRepoSlug(owner, repo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// title
|
|
||||||
promptOpts := survey.WithValidator(survey.Required)
|
|
||||||
promptI := &survey.Input{Message: "Milestone title:"}
|
|
||||||
if err := survey.AskOne(promptI, &title, promptOpts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// description
|
|
||||||
promptM := &survey.Multiline{Message: "Milestone description:"}
|
|
||||||
if err := survey.AskOne(promptM, &description); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// deadline
|
|
||||||
if deadline, err = promptDatetime("Milestone deadline:"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.CreateMilestone(
|
|
||||||
login,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
deadline,
|
|
||||||
gitea.StateOpen)
|
|
||||||
}
|
|
@ -1,165 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package interact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
"github.com/araddon/dateparse"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PromptMultiline runs a textfield-style prompt and blocks until input was made.
|
|
||||||
func PromptMultiline(message string) (content string, err error) {
|
|
||||||
err = survey.AskOne(&survey.Multiline{Message: message}, &content)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// PromptPassword asks for a password and blocks until input was made.
|
|
||||||
func PromptPassword(name string) (pass string, err error) {
|
|
||||||
promptPW := &survey.Password{Message: name + " password:"}
|
|
||||||
err = survey.AskOne(promptPW, &pass, survey.WithValidator(survey.Required))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// promptRepoSlug interactively prompts for a Gitea repository or returns the current one
|
|
||||||
func promptRepoSlug(defaultOwner, defaultRepo string) (owner, repo string, err error) {
|
|
||||||
prompt := "Target repo:"
|
|
||||||
defaultVal := ""
|
|
||||||
required := true
|
|
||||||
if len(defaultOwner) != 0 && len(defaultRepo) != 0 {
|
|
||||||
defaultVal = fmt.Sprintf("%s/%s", defaultOwner, defaultRepo)
|
|
||||||
required = false
|
|
||||||
}
|
|
||||||
var repoSlug string
|
|
||||||
|
|
||||||
owner = defaultOwner
|
|
||||||
repo = defaultRepo
|
|
||||||
|
|
||||||
err = survey.AskOne(
|
|
||||||
&survey.Input{
|
|
||||||
Message: prompt,
|
|
||||||
Default: defaultVal,
|
|
||||||
},
|
|
||||||
&repoSlug,
|
|
||||||
survey.WithValidator(func(input interface{}) error {
|
|
||||||
if str, ok := input.(string); ok {
|
|
||||||
if !required && len(str) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
split := strings.Split(str, "/")
|
|
||||||
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {
|
|
||||||
return fmt.Errorf("must follow the <owner>/<repo> syntax")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("invalid result type")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err == nil && len(repoSlug) != 0 {
|
|
||||||
repoSlugSplit := strings.Split(repoSlug, "/")
|
|
||||||
owner = repoSlugSplit[0]
|
|
||||||
repo = repoSlugSplit[1]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// promptDatetime prompts for a date or datetime string.
|
|
||||||
// Supports all formats understood by araddon/dateparse.
|
|
||||||
func promptDatetime(prompt string) (val *time.Time, err error) {
|
|
||||||
var input string
|
|
||||||
err = survey.AskOne(
|
|
||||||
&survey.Input{Message: prompt},
|
|
||||||
&input,
|
|
||||||
survey.WithValidator(func(input interface{}) error {
|
|
||||||
if str, ok := input.(string); ok {
|
|
||||||
if len(str) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t, err := dateparse.ParseAny(str)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
val = &t
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("invalid result type")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// promptSelect creates a generic multiselect prompt, with processing of custom values.
|
|
||||||
func promptMultiSelect(prompt string, options []string, customVal string) ([]string, error) {
|
|
||||||
var selection []string
|
|
||||||
promptA := &survey.MultiSelect{
|
|
||||||
Message: prompt,
|
|
||||||
Options: makeSelectOpts(options, customVal, ""),
|
|
||||||
VimMode: true,
|
|
||||||
}
|
|
||||||
if err := survey.AskOne(promptA, &selection); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return promptCustomVal(prompt, customVal, selection)
|
|
||||||
}
|
|
||||||
|
|
||||||
// promptSelect creates a generic select prompt, with processing of custom values or none-option.
|
|
||||||
func promptSelect(prompt string, options []string, customVal, noneVal string) (string, error) {
|
|
||||||
var selection string
|
|
||||||
promptA := &survey.Select{
|
|
||||||
Message: prompt,
|
|
||||||
Options: makeSelectOpts(options, customVal, noneVal),
|
|
||||||
VimMode: true,
|
|
||||||
Default: noneVal,
|
|
||||||
}
|
|
||||||
if err := survey.AskOne(promptA, &selection); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if noneVal != "" && selection == noneVal {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
if customVal != "" {
|
|
||||||
sel, err := promptCustomVal(prompt, customVal, []string{selection})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
selection = sel[0]
|
|
||||||
}
|
|
||||||
return selection, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeSelectOpts adds cusotmVal & noneVal to opts if set.
|
|
||||||
func makeSelectOpts(opts []string, customVal, noneVal string) []string {
|
|
||||||
if customVal != "" {
|
|
||||||
opts = append(opts, customVal)
|
|
||||||
}
|
|
||||||
if noneVal != "" {
|
|
||||||
opts = append(opts, noneVal)
|
|
||||||
}
|
|
||||||
return opts
|
|
||||||
}
|
|
||||||
|
|
||||||
// promptCustomVal checks if customVal is present in selection, and prompts
|
|
||||||
// for custom input to add to the selection instead.
|
|
||||||
func promptCustomVal(prompt, customVal string, selection []string) ([]string, error) {
|
|
||||||
// check for custom value & prompt again with text input
|
|
||||||
// HACK until https://github.com/AlecAivazis/survey/issues/339 is implemented
|
|
||||||
if otherIndex := utils.IndexOf(selection, customVal); otherIndex != -1 {
|
|
||||||
var customAssignees string
|
|
||||||
promptA := &survey.Input{Message: prompt, Help: "comma separated list"}
|
|
||||||
if err := survey.AskOne(promptA, &customAssignees); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
selection = append(selection[:otherIndex], selection[otherIndex+1:]...)
|
|
||||||
selection = append(selection, strings.Split(customAssignees, ",")...)
|
|
||||||
}
|
|
||||||
return selection, nil
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package interact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreatePull interactively creates a PR
|
|
||||||
func CreatePull(ctx *context.TeaContext) (err error) {
|
|
||||||
var base, head string
|
|
||||||
|
|
||||||
// owner, repo
|
|
||||||
if ctx.Owner, ctx.Repo, err = promptRepoSlug(ctx.Owner, ctx.Repo); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// base
|
|
||||||
if base, err = task.GetDefaultPRBase(ctx.Login, ctx.Owner, ctx.Repo); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
promptI := &survey.Input{Message: "Target branch:", Default: base}
|
|
||||||
if err := survey.AskOne(promptI, &base); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// head
|
|
||||||
var headOwner, headBranch string
|
|
||||||
promptOpts := survey.WithValidator(survey.Required)
|
|
||||||
|
|
||||||
if ctx.LocalRepo != nil {
|
|
||||||
headOwner, headBranch, err = task.GetDefaultPRHead(ctx.LocalRepo)
|
|
||||||
if err == nil {
|
|
||||||
promptOpts = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
promptI = &survey.Input{Message: "Source repo owner:", Default: headOwner}
|
|
||||||
if err := survey.AskOne(promptI, &headOwner); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
promptI = &survey.Input{Message: "Source branch:", Default: headBranch}
|
|
||||||
if err := survey.AskOne(promptI, &headBranch, promptOpts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
head = task.GetHeadSpec(headOwner, headBranch, ctx.Owner)
|
|
||||||
|
|
||||||
opts := gitea.CreateIssueOption{Title: task.GetDefaultPRTitle(head)}
|
|
||||||
if err = promptIssueProperties(ctx.Login, ctx.Owner, ctx.Repo, &opts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.CreatePull(
|
|
||||||
ctx,
|
|
||||||
base,
|
|
||||||
head,
|
|
||||||
&opts)
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package interact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/context"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var reviewStates = map[string]gitea.ReviewStateType{
|
|
||||||
"approve": gitea.ReviewStateApproved,
|
|
||||||
"comment": gitea.ReviewStateComment,
|
|
||||||
"request changes": gitea.ReviewStateRequestChanges,
|
|
||||||
}
|
|
||||||
var reviewStateOptions = []string{"comment", "request changes", "approve"}
|
|
||||||
|
|
||||||
// ReviewPull interactively reviews a PR
|
|
||||||
func ReviewPull(ctx *context.TeaContext, idx int64) error {
|
|
||||||
var state gitea.ReviewStateType
|
|
||||||
var comment string
|
|
||||||
var codeComments []gitea.CreatePullReviewComment
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// codeComments
|
|
||||||
var reviewDiff bool
|
|
||||||
promptDiff := &survey.Confirm{Message: "Review / comment the diff?", Default: true}
|
|
||||||
if err = survey.AskOne(promptDiff, &reviewDiff); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if reviewDiff {
|
|
||||||
if codeComments, err = DoDiffReview(ctx, idx); err != nil {
|
|
||||||
fmt.Printf("Error during diff review: %s\n", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("Found %d code comments in your review\n", len(codeComments))
|
|
||||||
}
|
|
||||||
|
|
||||||
// state
|
|
||||||
var stateString string
|
|
||||||
promptState := &survey.Select{Message: "Your assessment:", Options: reviewStateOptions, VimMode: true}
|
|
||||||
if err = survey.AskOne(promptState, &stateString); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
state = reviewStates[stateString]
|
|
||||||
|
|
||||||
// comment
|
|
||||||
var promptOpts survey.AskOpt
|
|
||||||
if (state == gitea.ReviewStateComment && len(codeComments) == 0) || state == gitea.ReviewStateRequestChanges {
|
|
||||||
promptOpts = survey.WithValidator(survey.Required)
|
|
||||||
}
|
|
||||||
err = survey.AskOne(&survey.Multiline{Message: "Concluding comment:"}, &comment, promptOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.CreatePullReview(ctx, idx, state, comment, codeComments)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DoDiffReview (1) fetches & saves diff in tempfile, (2) starts $VISUAL or $EDITOR to comment on diff,
|
|
||||||
// (3) parses resulting file into code comments.
|
|
||||||
func DoDiffReview(ctx *context.TeaContext, idx int64) ([]gitea.CreatePullReviewComment, error) {
|
|
||||||
tmpFile, err := task.SavePullDiff(ctx, idx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer os.Remove(tmpFile)
|
|
||||||
|
|
||||||
if err = task.OpenFileInEditor(tmpFile); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.ParseDiffComments(tmpFile)
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package print
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Comments renders a list of comments to stdout
|
|
||||||
func Comments(comments []*gitea.Comment) {
|
|
||||||
var baseURL string
|
|
||||||
if len(comments) != 0 {
|
|
||||||
baseURL = comments[0].HTMLURL
|
|
||||||
}
|
|
||||||
|
|
||||||
var out = make([]string, len(comments))
|
|
||||||
for i, c := range comments {
|
|
||||||
out[i] = formatComment(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
outputMarkdown(fmt.Sprintf(
|
|
||||||
// this will become a heading by means of the first --- from a comment
|
|
||||||
"Comments\n%s",
|
|
||||||
strings.Join(out, "\n"),
|
|
||||||
), baseURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comment renders a comment to stdout
|
|
||||||
func Comment(c *gitea.Comment) {
|
|
||||||
outputMarkdown(formatComment(c), c.HTMLURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatComment(c *gitea.Comment) string {
|
|
||||||
edited := ""
|
|
||||||
if c.Updated.After(c.Created) {
|
|
||||||
edited = fmt.Sprintf(" *(edited on %s)*", FormatTime(c.Updated))
|
|
||||||
}
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"---\n\n**@%s** wrote on %s%s:\n\n%s\n",
|
|
||||||
c.Poster.UserName,
|
|
||||||
FormatTime(c.Created),
|
|
||||||
edited,
|
|
||||||
c.Body,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package print
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/muesli/termenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// formatSize get kb in int and return string
|
|
||||||
func formatSize(kb int64) string {
|
|
||||||
if kb < 1024 {
|
|
||||||
return fmt.Sprintf("%d Kb", kb)
|
|
||||||
}
|
|
||||||
mb := kb / 1024
|
|
||||||
if mb < 1024 {
|
|
||||||
return fmt.Sprintf("%d Mb", mb)
|
|
||||||
}
|
|
||||||
gb := mb / 1024
|
|
||||||
if gb < 1024 {
|
|
||||||
return fmt.Sprintf("%d Gb", gb)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d Tb", gb/1024)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatTime give a date-time in local timezone if available
|
|
||||||
func FormatTime(t time.Time) string {
|
|
||||||
location, err := time.LoadLocation("Local")
|
|
||||||
if err != nil {
|
|
||||||
return t.Format("2006-01-02 15:04 UTC")
|
|
||||||
}
|
|
||||||
return t.In(location).Format("2006-01-02 15:04")
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatDuration(seconds int64, outputType string) string {
|
|
||||||
if isMachineReadable(outputType) {
|
|
||||||
return fmt.Sprint(seconds)
|
|
||||||
}
|
|
||||||
return time.Duration(1e9 * seconds).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatLabel(label *gitea.Label, allowColor bool, text string) string {
|
|
||||||
colorProfile := termenv.Ascii
|
|
||||||
if allowColor {
|
|
||||||
colorProfile = termenv.EnvColorProfile()
|
|
||||||
}
|
|
||||||
if len(text) == 0 {
|
|
||||||
text = label.Name
|
|
||||||
}
|
|
||||||
styled := termenv.String(text)
|
|
||||||
styled = styled.Foreground(colorProfile.Color("#" + label.Color))
|
|
||||||
return fmt.Sprint(styled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatPermission(p *gitea.Permission) string {
|
|
||||||
if p.Admin {
|
|
||||||
return "admin"
|
|
||||||
} else if p.Push {
|
|
||||||
return "write"
|
|
||||||
}
|
|
||||||
return "read"
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatUserName(u *gitea.User) string {
|
|
||||||
if len(u.FullName) == 0 {
|
|
||||||
return u.UserName
|
|
||||||
}
|
|
||||||
return u.FullName
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user