mirror of
https://gitea.com/gitea/tea.git
synced 2025-09-19 02:02:55 +02:00
Compare commits
54 Commits
Author | SHA1 | Date | |
---|---|---|---|
1c690c5ff8 | |||
802bdf7dc5 | |||
1731e00ebd | |||
7b7c7f57be | |||
6e728cf812 | |||
808e8b1c5a | |||
9201250f74 | |||
5b28a05eb7 | |||
3fca309f2c | |||
d6df0a53b5 | |||
4b9907fb54 | |||
ab4e11ae4d | |||
546fcc16de | |||
0f4f669cf0 | |||
2bdd72dfff | |||
64770a771f | |||
ebb2c38a0a | |||
616127cedc | |||
3129e60a73 | |||
df724b4006 | |||
ffdbdb3d02 | |||
568fde1ce5 | |||
46945415c9 | |||
195bd2199c | |||
0bf844018c | |||
2319724bb2 | |||
222d0501df | |||
cb404b53b5 | |||
3abc5a5b42 | |||
6f738df4a5 | |||
d22b314701 | |||
786c713ff5 | |||
d474883e90 | |||
0d98cbd657 | |||
15c4edba1a | |||
e96cfdbbe7 | |||
3c1efd33e2 | |||
9c8321f2e0 | |||
b5c670ebf8 | |||
95ef061711 | |||
32b7b771cc | |||
9efee7bf99 | |||
8bb5c15745 | |||
43a58bdba1 | |||
43e9943757 | |||
8b588f5313 | |||
a2e8b47c57 | |||
83b73ce78e | |||
782a6318f3 | |||
a948fd7e10 | |||
287df8a715 | |||
dc67630b64 | |||
e5cdad554e | |||
b9f5ba0702 |
16
.drone.yml
16
.drone.yml
@ -9,7 +9,7 @@ platform:
|
|||||||
steps:
|
steps:
|
||||||
- name: build
|
- name: build
|
||||||
pull: always
|
pull: always
|
||||||
image: golang:1.15
|
image: golang:1.16
|
||||||
environment:
|
environment:
|
||||||
GOPROXY: https://goproxy.cn
|
GOPROXY: https://goproxy.cn
|
||||||
commands:
|
commands:
|
||||||
@ -27,7 +27,7 @@ steps:
|
|||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: unit-test
|
- name: unit-test
|
||||||
image: golang:1.15
|
image: golang:1.16
|
||||||
commands:
|
commands:
|
||||||
- make unit-test-coverage
|
- make unit-test-coverage
|
||||||
settings:
|
settings:
|
||||||
@ -40,7 +40,7 @@ steps:
|
|||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: release-test
|
- name: release-test
|
||||||
image: golang:1.15
|
image: golang:1.16
|
||||||
commands:
|
commands:
|
||||||
- make test
|
- make test
|
||||||
settings:
|
settings:
|
||||||
@ -54,7 +54,7 @@ steps:
|
|||||||
|
|
||||||
- name: tag-test
|
- name: tag-test
|
||||||
pull: always
|
pull: always
|
||||||
image: golang:1.15
|
image: golang:1.16
|
||||||
commands:
|
commands:
|
||||||
- make test
|
- make test
|
||||||
settings:
|
settings:
|
||||||
@ -64,7 +64,7 @@ steps:
|
|||||||
- tag
|
- tag
|
||||||
|
|
||||||
- name: static
|
- name: static
|
||||||
image: golang:1.15
|
image: golang:1.16
|
||||||
environment:
|
environment:
|
||||||
GOPROXY: https://goproxy.cn
|
GOPROXY: https://goproxy.cn
|
||||||
commands:
|
commands:
|
||||||
@ -99,7 +99,7 @@ steps:
|
|||||||
image: plugins/s3:1
|
image: plugins/s3:1
|
||||||
settings:
|
settings:
|
||||||
acl: public-read
|
acl: public-read
|
||||||
bucket: releases
|
bucket: gitea-artifacts
|
||||||
endpoint: https://storage.gitea.io
|
endpoint: https://storage.gitea.io
|
||||||
path_style: true
|
path_style: true
|
||||||
source: "dist/release/*"
|
source: "dist/release/*"
|
||||||
@ -119,7 +119,7 @@ steps:
|
|||||||
image: plugins/s3:1
|
image: plugins/s3:1
|
||||||
settings:
|
settings:
|
||||||
acl: public-read
|
acl: public-read
|
||||||
bucket: releases
|
bucket: gitea-artifacts
|
||||||
endpoint: https://storage.gitea.io
|
endpoint: https://storage.gitea.io
|
||||||
path_style: true
|
path_style: true
|
||||||
source: "dist/release/*"
|
source: "dist/release/*"
|
||||||
@ -141,7 +141,7 @@ steps:
|
|||||||
image: plugins/s3:1
|
image: plugins/s3:1
|
||||||
settings:
|
settings:
|
||||||
acl: public-read
|
acl: public-read
|
||||||
bucket: releases
|
bucket: gitea-artifacts
|
||||||
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,3 +4,5 @@ tea
|
|||||||
.idea/
|
.idea/
|
||||||
.history/
|
.history/
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
|
.vscode/
|
||||||
|
63
CHANGELOG.md
63
CHANGELOG.md
@ -1,5 +1,68 @@
|
|||||||
# 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
|
## [v0.6.0](https://gitea.com/gitea/tea/releases/tag/v0.6.0) - 2020-12-11
|
||||||
|
|
||||||
* BREAKING
|
* BREAKING
|
||||||
|
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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"]
|
63
FEATURE-COMPARISON.md
Normal file
63
FEATURE-COMPARISON.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# 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
|
8
Makefile
8
Makefile
@ -38,6 +38,8 @@ 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/)
|
||||||
@ -139,6 +141,10 @@ 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-os release-compress release-check
|
||||||
|
|
||||||
@ -151,7 +157,7 @@ release-os:
|
|||||||
@hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
cd /tmp && $(GO) get -u github.com/mitchellh/gox; \
|
cd /tmp && $(GO) get -u github.com/mitchellh/gox; \
|
||||||
fi
|
fi
|
||||||
CGO_ENABLED=0 gox -verbose -cgo=false -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -osarch='!darwin/386 !darwin/arm64 !darwin/arm' -os="windows linux darwin" -arch="386 amd64 arm arm64" -output="$(DIST)/release/tea-$(VERSION)-{{.OS}}-{{.Arch}}"
|
CGO_ENABLED=0 gox -verbose -cgo=false -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -osarch='!darwin/386 !darwin/arm' -os="windows linux darwin" -arch="386 amd64 arm arm64" -output="$(DIST)/release/tea-$(VERSION)-{{.OS}}-{{.Arch}}"
|
||||||
|
|
||||||
.PHONY: release-compress
|
.PHONY: release-compress
|
||||||
release-compress:
|
release-compress:
|
||||||
|
127
README.md
127
README.md
@ -2,62 +2,95 @@
|
|||||||
|
|
||||||
[](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 interface for gitea
|
### The official CLI 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
|
||||||
|
|
||||||
You can use the prebuilt binaries from [dl.gitea.io](https://dl.gitea.io/tea/)
|
There are different ways to get `tea`:
|
||||||
|
|
||||||
To install from source, go 1.13 or newer is required:
|
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)
|
||||||
|
|
||||||
```sh
|
2. Use the prebuilt binaries from [dl.gitea.io](https://dl.gitea.io/tea/)
|
||||||
go get code.gitea.io/tea
|
|
||||||
go install code.gitea.io/tea
|
|
||||||
```
|
|
||||||
|
|
||||||
If you have `brew` installed, you can install `tea` via:
|
3. Install from source (go 1.13 or newer is required):
|
||||||
|
```sh
|
||||||
|
go get code.gitea.io/tea
|
||||||
|
go install code.gitea.io/tea
|
||||||
|
```
|
||||||
|
|
||||||
```sh
|
4. Docker (thirdparty): [tgerczei/tea](https://hub.docker.com/r/tgerczei/tea)
|
||||||
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`.
|
|
||||||
|
|
||||||
```none
|
|
||||||
login Log in to a Gitea server
|
|
||||||
logout Log out from a Gitea server
|
|
||||||
issues List, create and update issues
|
|
||||||
pulls List, create, checkout and clean pull requests
|
|
||||||
releases List, create, update and delete 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
|
|
||||||
notifications Show notifications
|
|
||||||
milestones List and create milestones
|
|
||||||
organizations List, create, delete organizations
|
|
||||||
help, h Shows a list of commands or help for one command
|
|
||||||
```
|
|
||||||
|
|
||||||
To fetch issues from different repos, use the `--remote` flag (when inside a gitea repository directory) or `--login` & `--repo` flags.
|
|
||||||
|
|
||||||
## Compilation
|
## Compilation
|
||||||
|
|
||||||
|
138
cmd/autocomplete.go
Normal file
138
cmd/autocomplete.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// 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
|
||||||
|
}
|
11
cmd/categories.go
Normal file
11
cmd/categories.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// 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"
|
||||||
|
)
|
77
cmd/comment.go
Normal file
77
cmd/comment.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// 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
|
||||||
|
}
|
49
cmd/flags/csvflag.go
Normal file
49
cmd/flags/csvflag.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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
|
||||||
|
}
|
@ -5,29 +5,22 @@
|
|||||||
package flags
|
package flags
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/sdk/gitea"
|
"fmt"
|
||||||
"github.com/urfave/cli/v2"
|
"strings"
|
||||||
)
|
|
||||||
|
|
||||||
// create global variables for global Flags to simplify
|
"code.gitea.io/sdk/gitea"
|
||||||
// access to the options without requiring cli.Context
|
"code.gitea.io/tea/modules/context"
|
||||||
var (
|
"code.gitea.io/tea/modules/task"
|
||||||
// GlobalLoginValue contain value of --login|-l arg
|
|
||||||
GlobalLoginValue string
|
"github.com/araddon/dateparse"
|
||||||
// GlobalRepoValue contain value of --repo|-r arg
|
"github.com/urfave/cli/v2"
|
||||||
GlobalRepoValue string
|
|
||||||
// GlobalOutputValue contain value of --output|-o arg
|
|
||||||
GlobalOutputValue string
|
|
||||||
// GlobalRemoteValue contain value of --remote|-R arg
|
|
||||||
GlobalRemoteValue string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoginFlag provides flag to specify tea login profile
|
// LoginFlag provides flag to specify tea login profile
|
||||||
var LoginFlag = cli.StringFlag{
|
var LoginFlag = cli.StringFlag{
|
||||||
Name: "login",
|
Name: "login",
|
||||||
Aliases: []string{"l"},
|
Aliases: []string{"l"},
|
||||||
Usage: "Use a different Gitea login. Optional",
|
Usage: "Use a different Gitea Login. Optional",
|
||||||
Destination: &GlobalLoginValue,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepoFlag provides flag to specify repository
|
// RepoFlag provides flag to specify repository
|
||||||
@ -35,7 +28,6 @@ var RepoFlag = cli.StringFlag{
|
|||||||
Name: "repo",
|
Name: "repo",
|
||||||
Aliases: []string{"r"},
|
Aliases: []string{"r"},
|
||||||
Usage: "Override local repository path or gitea repository slug to interact with. Optional",
|
Usage: "Override local repository path or gitea repository slug to interact with. Optional",
|
||||||
Destination: &GlobalRepoValue,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoteFlag provides flag to specify remote repository
|
// RemoteFlag provides flag to specify remote repository
|
||||||
@ -43,7 +35,6 @@ var RemoteFlag = cli.StringFlag{
|
|||||||
Name: "remote",
|
Name: "remote",
|
||||||
Aliases: []string{"R"},
|
Aliases: []string{"R"},
|
||||||
Usage: "Discover Gitea login from remote. Optional",
|
Usage: "Discover Gitea login from remote. Optional",
|
||||||
Destination: &GlobalRemoteValue,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutputFlag provides flag to specify output type
|
// OutputFlag provides flag to specify output type
|
||||||
@ -51,7 +42,6 @@ var OutputFlag = cli.StringFlag{
|
|||||||
Name: "output",
|
Name: "output",
|
||||||
Aliases: []string{"o"},
|
Aliases: []string{"o"},
|
||||||
Usage: "Output format. (csv, simple, table, tsv, yaml)",
|
Usage: "Output format. (csv, simple, table, tsv, yaml)",
|
||||||
Destination: &GlobalOutputValue,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateFlag provides flag to specify issue/pr state, defaulting to "open"
|
// StateFlag provides flag to specify issue/pr state, defaulting to "open"
|
||||||
@ -110,15 +100,105 @@ var IssuePRFlags = append([]cli.Flag{
|
|||||||
&PaginationLimitFlag,
|
&PaginationLimitFlag,
|
||||||
}, AllDefaultFlags...)
|
}, AllDefaultFlags...)
|
||||||
|
|
||||||
// GetListOptions return ListOptions based on PaginationFlags
|
// NotificationFlags defines flags that should be available on notifications.
|
||||||
func GetListOptions(ctx *cli.Context) gitea.ListOptions {
|
var NotificationFlags = append([]cli.Flag{
|
||||||
page := ctx.Int("page")
|
NotificationStateFlag,
|
||||||
limit := ctx.Int("limit")
|
&cli.BoolFlag{
|
||||||
if limit != 0 && page == 0 {
|
Name: "mine",
|
||||||
page = 1
|
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"), ","),
|
||||||
}
|
}
|
||||||
return gitea.ListOptions{
|
var err error
|
||||||
Page: page,
|
|
||||||
PageSize: limit,
|
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)
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,11 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/issues"
|
"code.gitea.io/tea/cmd/issues"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
|
"code.gitea.io/tea/modules/interact"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
@ -17,9 +19,10 @@ 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"},
|
Aliases: []string{"issue", "i"},
|
||||||
|
Category: catEntities,
|
||||||
Usage: "List, create and update issues",
|
Usage: "List, create and update issues",
|
||||||
Description: "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{
|
||||||
@ -28,27 +31,41 @@ var CmdIssues = cli.Command{
|
|||||||
&issues.CmdIssuesReopen,
|
&issues.CmdIssuesReopen,
|
||||||
&issues.CmdIssuesClose,
|
&issues.CmdIssuesClose,
|
||||||
},
|
},
|
||||||
Flags: flags.IssuePRFlags,
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "comments",
|
||||||
|
Usage: "Wether to display comments (will prompt if not provided & run interactively)",
|
||||||
|
},
|
||||||
|
}, issues.CmdIssuesList.Flags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
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.Args().First())
|
return runIssueDetail(ctx, ctx.Args().First())
|
||||||
}
|
}
|
||||||
return issues.RunIssuesList(ctx)
|
return issues.RunIssuesList(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIssueDetail(index string) error {
|
func runIssueDetail(cmd *cli.Context, index string) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
idx, err := utils.ArgToIndex(index)
|
idx, err := utils.ArgToIndex(index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
issue, _, err := login.Client().GetIssue(owner, repo, idx)
|
issue, _, err := ctx.Login.Client().GetIssue(ctx.Owner, ctx.Repo, idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
print.IssueDetails(issue)
|
print.IssueDetails(issue)
|
||||||
|
|
||||||
|
if issue.Comments > 0 {
|
||||||
|
err = interact.ShowCommentsMaybeInteractive(ctx, idx, issue.Comments)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading comments: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
package issues
|
package issues
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
@ -30,10 +30,11 @@ var CmdIssuesClose = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// editIssueState abstracts the arg parsing to edit the given issue
|
// editIssueState abstracts the arg parsing to edit the given issue
|
||||||
func editIssueState(ctx *cli.Context, opts gitea.EditIssueOption) error {
|
func editIssueState(cmd *cli.Context, opts gitea.EditIssueOption) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
if ctx.Args().Len() == 0 {
|
if ctx.Args().Len() == 0 {
|
||||||
log.Fatal(ctx.Command.ArgsUsage)
|
return fmt.Errorf(ctx.Command.ArgsUsage)
|
||||||
}
|
}
|
||||||
|
|
||||||
index, err := utils.ArgToIndex(ctx.Args().First())
|
index, err := utils.ArgToIndex(ctx.Args().First())
|
||||||
@ -41,7 +42,7 @@ func editIssueState(ctx *cli.Context, opts gitea.EditIssueOption) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
issue, _, err := login.Client().EditIssue(owner, repo, index, opts)
|
issue, _, err := ctx.Login.Client().EditIssue(ctx.Owner, ctx.Repo, index, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -5,57 +5,41 @@
|
|||||||
package issues
|
package issues
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/interact"
|
||||||
|
"code.gitea.io/tea/modules/task"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdIssuesCreate represents a sub command of issues to create issue
|
// CmdIssuesCreate represents a sub command of issues to create issue
|
||||||
var CmdIssuesCreate = cli.Command{
|
var CmdIssuesCreate = cli.Command{
|
||||||
Name: "create",
|
Name: "create",
|
||||||
|
Aliases: []string{"c"},
|
||||||
Usage: "Create an issue on repository",
|
Usage: "Create an issue on repository",
|
||||||
Description: `Create an issue on repository`,
|
Description: `Create an issue on repository`,
|
||||||
Action: runIssuesCreate,
|
Action: runIssuesCreate,
|
||||||
Flags: append([]cli.Flag{
|
Flags: flags.IssuePREditFlags,
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "title",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "issue title to create",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "body",
|
|
||||||
Aliases: []string{"b"},
|
|
||||||
Usage: "issue body to create",
|
|
||||||
},
|
|
||||||
}, flags.LoginRepoFlags...),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIssuesCreate(ctx *cli.Context) error {
|
func runIssuesCreate(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
issue, _, err := login.Client().CreateIssue(owner, repo, gitea.CreateIssueOption{
|
if ctx.NumFlags() == 0 {
|
||||||
Title: ctx.String("title"),
|
return interact.CreateIssue(ctx.Login, ctx.Owner, ctx.Repo)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
print.IssueDetails(issue)
|
opts, err := flags.GetIssuePREditFlags(ctx)
|
||||||
fmt.Println(issue.HTMLURL)
|
if err != nil {
|
||||||
return nil
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return task.CreateIssue(
|
||||||
|
ctx.Login,
|
||||||
|
ctx.Owner,
|
||||||
|
ctx.Repo,
|
||||||
|
*opts,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,29 +5,32 @@
|
|||||||
package issues
|
package issues
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var issueFieldsFlag = flags.FieldsFlag(print.IssueFields, []string{
|
||||||
|
"index", "title", "state", "author", "milestone", "labels",
|
||||||
|
})
|
||||||
|
|
||||||
// CmdIssuesList represents a sub command of issues to list issues
|
// CmdIssuesList represents a sub command of issues to list issues
|
||||||
var CmdIssuesList = cli.Command{
|
var CmdIssuesList = cli.Command{
|
||||||
Name: "ls",
|
Name: "list",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List issues of the repository",
|
Usage: "List issues of the repository",
|
||||||
Description: `List issues of the repository`,
|
Description: `List issues of the repository`,
|
||||||
Action: RunIssuesList,
|
Action: RunIssuesList,
|
||||||
Flags: flags.IssuePRFlags,
|
Flags: append([]cli.Flag{issueFieldsFlag}, flags.IssuePRFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunIssuesList list issues
|
// RunIssuesList list issues
|
||||||
func RunIssuesList(ctx *cli.Context) error {
|
func RunIssuesList(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
state := gitea.StateOpen
|
state := gitea.StateOpen
|
||||||
switch ctx.String("state") {
|
switch ctx.String("state") {
|
||||||
@ -39,16 +42,21 @@ func RunIssuesList(ctx *cli.Context) error {
|
|||||||
state = gitea.StateClosed
|
state = gitea.StateClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
issues, _, err := login.Client().ListRepoIssues(owner, repo, gitea.ListIssueOption{
|
issues, _, err := ctx.Login.Client().ListRepoIssues(ctx.Owner, ctx.Repo, gitea.ListIssueOption{
|
||||||
ListOptions: flags.GetListOptions(ctx),
|
ListOptions: ctx.GetListOptions(),
|
||||||
State: state,
|
State: state,
|
||||||
Type: gitea.IssueTypeIssue,
|
Type: gitea.IssueTypeIssue,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
print.IssuesList(issues, flags.GlobalOutputValue)
|
fields, err := issueFieldsFlag.GetValues(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
print.IssuesPullsList(issues, ctx.Output, fields)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/labels"
|
"code.gitea.io/tea/cmd/labels"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -15,6 +15,7 @@ import (
|
|||||||
var CmdLabels = cli.Command{
|
var CmdLabels = cli.Command{
|
||||||
Name: "labels",
|
Name: "labels",
|
||||||
Aliases: []string{"label"},
|
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,
|
||||||
@ -24,6 +25,7 @@ var CmdLabels = cli.Command{
|
|||||||
&labels.CmdLabelUpdate,
|
&labels.CmdLabelUpdate,
|
||||||
&labels.CmdLabelDelete,
|
&labels.CmdLabelDelete,
|
||||||
},
|
},
|
||||||
|
Flags: labels.CmdLabelsList.Flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLabels(ctx *cli.Context) error {
|
func runLabels(ctx *cli.Context) error {
|
||||||
@ -34,6 +36,5 @@ func runLabels(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runLabelsDetails(ctx *cli.Context) error {
|
func runLabelsDetails(ctx *cli.Context) error {
|
||||||
log.Fatal("Not yet implemented.")
|
return fmt.Errorf("Not yet implemented")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -20,10 +20,11 @@ import (
|
|||||||
// CmdLabelCreate represents a sub command of labels to create label.
|
// CmdLabelCreate represents a sub command of labels to create label.
|
||||||
var CmdLabelCreate = cli.Command{
|
var CmdLabelCreate = cli.Command{
|
||||||
Name: "create",
|
Name: "create",
|
||||||
|
Aliases: []string{"c"},
|
||||||
Usage: "Create a label",
|
Usage: "Create a label",
|
||||||
Description: `Create a label`,
|
Description: `Create a label`,
|
||||||
Action: runLabelCreate,
|
Action: runLabelCreate,
|
||||||
Flags: []cli.Flag{
|
Flags: append([]cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Usage: "label name",
|
Usage: "label name",
|
||||||
@ -40,16 +41,17 @@ var CmdLabelCreate = cli.Command{
|
|||||||
Name: "file",
|
Name: "file",
|
||||||
Usage: "indicate a label file",
|
Usage: "indicate a label file",
|
||||||
},
|
},
|
||||||
},
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLabelCreate(ctx *cli.Context) error {
|
func runLabelCreate(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
labelFile := ctx.String("file")
|
labelFile := ctx.String("file")
|
||||||
var err error
|
var err error
|
||||||
if len(labelFile) == 0 {
|
if len(labelFile) == 0 {
|
||||||
_, _, err = login.Client().CreateLabel(owner, repo, gitea.CreateLabelOption{
|
_, _, err = ctx.Login.Client().CreateLabel(ctx.Owner, ctx.Repo, gitea.CreateLabelOption{
|
||||||
Name: ctx.String("name"),
|
Name: ctx.String("name"),
|
||||||
Color: ctx.String("color"),
|
Color: ctx.String("color"),
|
||||||
Description: ctx.String("description"),
|
Description: ctx.String("description"),
|
||||||
@ -69,7 +71,7 @@ func runLabelCreate(ctx *cli.Context) error {
|
|||||||
if color == "" || name == "" {
|
if color == "" || name == "" {
|
||||||
log.Printf("Line %d ignored because lack of enough fields: %s\n", i, line)
|
log.Printf("Line %d ignored because lack of enough fields: %s\n", i, line)
|
||||||
} else {
|
} else {
|
||||||
_, _, err = login.Client().CreateLabel(owner, repo, gitea.CreateLabelOption{
|
_, _, err = ctx.Login.Client().CreateLabel(ctx.Owner, ctx.Repo, gitea.CreateLabelOption{
|
||||||
Name: name,
|
Name: name,
|
||||||
Color: color,
|
Color: color,
|
||||||
Description: description,
|
Description: description,
|
||||||
@ -80,11 +82,7 @@ func runLabelCreate(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
return err
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitLabelLine(line string) (string, string, string) {
|
func splitLabelLine(line string) (string, string, string) {
|
||||||
|
@ -5,10 +5,8 @@
|
|||||||
package labels
|
package labels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@ -16,24 +14,22 @@ import (
|
|||||||
// CmdLabelDelete represents a sub command of labels to delete label.
|
// CmdLabelDelete represents a sub command of labels to delete label.
|
||||||
var CmdLabelDelete = cli.Command{
|
var CmdLabelDelete = cli.Command{
|
||||||
Name: "delete",
|
Name: "delete",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
Usage: "Delete a label",
|
Usage: "Delete a label",
|
||||||
Description: `Delete a label`,
|
Description: `Delete a label`,
|
||||||
Action: runLabelDelete,
|
Action: runLabelDelete,
|
||||||
Flags: []cli.Flag{
|
Flags: append([]cli.Flag{
|
||||||
&cli.IntFlag{
|
&cli.IntFlag{
|
||||||
Name: "id",
|
Name: "id",
|
||||||
Usage: "label id",
|
Usage: "label id",
|
||||||
},
|
},
|
||||||
},
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLabelDelete(ctx *cli.Context) error {
|
func runLabelDelete(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
_, err := login.Client().DeleteLabel(owner, repo, ctx.Int64("id"))
|
_, err := ctx.Login.Client().DeleteLabel(ctx.Owner, ctx.Repo, ctx.Int64("id"))
|
||||||
if err != nil {
|
return err
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,8 @@
|
|||||||
package labels
|
package labels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
"code.gitea.io/tea/modules/task"
|
"code.gitea.io/tea/modules/task"
|
||||||
|
|
||||||
@ -18,8 +16,8 @@ import (
|
|||||||
|
|
||||||
// CmdLabelsList represents a sub command of labels to list labels
|
// CmdLabelsList represents a sub command of labels to list labels
|
||||||
var CmdLabelsList = cli.Command{
|
var CmdLabelsList = cli.Command{
|
||||||
Name: "ls",
|
Name: "list",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List labels",
|
Usage: "List labels",
|
||||||
Description: "List labels",
|
Description: "List labels",
|
||||||
Action: RunLabelsList,
|
Action: RunLabelsList,
|
||||||
@ -35,18 +33,22 @@ var CmdLabelsList = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunLabelsList list labels.
|
// RunLabelsList list labels.
|
||||||
func RunLabelsList(ctx *cli.Context) error {
|
func RunLabelsList(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
labels, _, err := login.Client().ListRepoLabels(owner, repo, gitea.ListLabelsOptions{ListOptions: flags.GetListOptions(ctx)})
|
client := ctx.Login.Client()
|
||||||
|
labels, _, err := client.ListRepoLabels(ctx.Owner, ctx.Repo, gitea.ListLabelsOptions{
|
||||||
|
ListOptions: ctx.GetListOptions(),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("save") {
|
if ctx.IsSet("save") {
|
||||||
return task.LabelsExport(labels, ctx.String("save"))
|
return task.LabelsExport(labels, ctx.String("save"))
|
||||||
}
|
}
|
||||||
|
|
||||||
print.LabelsList(labels, flags.GlobalOutputValue)
|
print.LabelsList(labels, ctx.Output)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,8 @@
|
|||||||
package labels
|
package labels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -20,7 +18,7 @@ var CmdLabelUpdate = cli.Command{
|
|||||||
Usage: "Update a label",
|
Usage: "Update a label",
|
||||||
Description: `Update a label`,
|
Description: `Update a label`,
|
||||||
Action: runLabelUpdate,
|
Action: runLabelUpdate,
|
||||||
Flags: []cli.Flag{
|
Flags: append([]cli.Flag{
|
||||||
&cli.IntFlag{
|
&cli.IntFlag{
|
||||||
Name: "id",
|
Name: "id",
|
||||||
Usage: "label id",
|
Usage: "label id",
|
||||||
@ -37,11 +35,12 @@ var CmdLabelUpdate = cli.Command{
|
|||||||
Name: "description",
|
Name: "description",
|
||||||
Usage: "label description",
|
Usage: "label description",
|
||||||
},
|
},
|
||||||
},
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLabelUpdate(ctx *cli.Context) error {
|
func runLabelUpdate(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
id := ctx.Int64("id")
|
id := ctx.Int64("id")
|
||||||
var pName, pColor, pDescription *string
|
var pName, pColor, pDescription *string
|
||||||
@ -61,14 +60,14 @@ func runLabelUpdate(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
_, _, err = login.Client().EditLabel(owner, repo, id, gitea.EditLabelOption{
|
_, _, err = ctx.Login.Client().EditLabel(ctx.Owner, ctx.Repo, id, gitea.EditLabelOption{
|
||||||
Name: pName,
|
Name: pName,
|
||||||
Color: pColor,
|
Color: pColor,
|
||||||
Description: pDescription,
|
Description: pDescription,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -7,7 +7,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/cmd/login"
|
"code.gitea.io/tea/cmd/login"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/config"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
@ -19,6 +18,7 @@ import (
|
|||||||
var CmdLogin = cli.Command{
|
var CmdLogin = cli.Command{
|
||||||
Name: "logins",
|
Name: "logins",
|
||||||
Aliases: []string{"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>]",
|
ArgsUsage: "[<login name>]",
|
||||||
@ -46,6 +46,6 @@ func runLoginDetail(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
print.LoginDetails(l, flags.GlobalOutputValue)
|
print.LoginDetails(l)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
// CmdLoginEdit represents to login a gitea server.
|
// CmdLoginEdit represents to login a gitea server.
|
||||||
var CmdLoginEdit = cli.Command{
|
var CmdLoginEdit = cli.Command{
|
||||||
Name: "edit",
|
Name: "edit",
|
||||||
|
Aliases: []string{"e"},
|
||||||
Usage: "Edit Gitea logins",
|
Usage: "Edit Gitea logins",
|
||||||
Description: `Edit Gitea logins`,
|
Description: `Edit Gitea logins`,
|
||||||
Action: runLoginEdit,
|
Action: runLoginEdit,
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
package login
|
package login
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/config"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
@ -16,8 +14,8 @@ import (
|
|||||||
|
|
||||||
// CmdLoginList represents to login a gitea server.
|
// CmdLoginList represents to login a gitea server.
|
||||||
var CmdLoginList = cli.Command{
|
var CmdLoginList = cli.Command{
|
||||||
Name: "ls",
|
Name: "list",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List Gitea logins",
|
Usage: "List Gitea logins",
|
||||||
Description: `List Gitea logins`,
|
Description: `List Gitea logins`,
|
||||||
Action: RunLoginList,
|
Action: RunLoginList,
|
||||||
@ -25,12 +23,11 @@ var CmdLoginList = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunLoginList list all logins
|
// RunLoginList list all logins
|
||||||
func RunLoginList(_ *cli.Context) error {
|
func RunLoginList(cmd *cli.Context) error {
|
||||||
logins, err := config.GetLogins()
|
logins, err := config.GetLogins()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
print.LoginsList(logins, cmd.String("output"))
|
||||||
print.LoginsList(logins, flags.GlobalOutputValue)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ 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>",
|
ArgsUsage: "<login name>",
|
||||||
|
@ -5,9 +5,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/cmd/milestones"
|
"code.gitea.io/tea/cmd/milestones"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -17,6 +16,7 @@ import (
|
|||||||
var CmdMilestones = cli.Command{
|
var CmdMilestones = cli.Command{
|
||||||
Name: "milestones",
|
Name: "milestones",
|
||||||
Aliases: []string{"milestone", "ms"},
|
Aliases: []string{"milestone", "ms"},
|
||||||
|
Category: catEntities,
|
||||||
Usage: "List and create milestones",
|
Usage: "List and create milestones",
|
||||||
Description: `List and create milestones`,
|
Description: `List and create milestones`,
|
||||||
ArgsUsage: "[<milestone name>]",
|
ArgsUsage: "[<milestone name>]",
|
||||||
@ -29,21 +29,22 @@ var CmdMilestones = cli.Command{
|
|||||||
&milestones.CmdMilestonesReopen,
|
&milestones.CmdMilestonesReopen,
|
||||||
&milestones.CmdMilestonesIssues,
|
&milestones.CmdMilestonesIssues,
|
||||||
},
|
},
|
||||||
Flags: flags.AllDefaultFlags,
|
Flags: milestones.CmdMilestonesList.Flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMilestones(ctx *cli.Context) error {
|
func runMilestones(ctx *cli.Context) error {
|
||||||
if ctx.Args().Len() == 1 {
|
if ctx.Args().Len() == 1 {
|
||||||
return runMilestoneDetail(ctx.Args().First())
|
return runMilestoneDetail(ctx, ctx.Args().First())
|
||||||
}
|
}
|
||||||
return milestones.RunMilestonesList(ctx)
|
return milestones.RunMilestonesList(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMilestoneDetail(name string) error {
|
func runMilestoneDetail(cmd *cli.Context, name string) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
milestone, _, err := client.GetMilestoneByName(owner, repo, name)
|
milestone, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -5,20 +5,22 @@
|
|||||||
package milestones
|
package milestones
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"time"
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/interact"
|
||||||
|
"code.gitea.io/tea/modules/task"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/araddon/dateparse"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdMilestonesCreate represents a sub command of milestones to create milestone
|
// CmdMilestonesCreate represents a sub command of milestones to create milestone
|
||||||
var CmdMilestonesCreate = cli.Command{
|
var CmdMilestonesCreate = cli.Command{
|
||||||
Name: "create",
|
Name: "create",
|
||||||
|
Aliases: []string{"c"},
|
||||||
Usage: "Create an milestone on repository",
|
Usage: "Create an milestone on repository",
|
||||||
Description: `Create an milestone on repository`,
|
Description: `Create an milestone on repository`,
|
||||||
Action: runMilestonesCreate,
|
Action: runMilestonesCreate,
|
||||||
@ -33,6 +35,11 @@ var CmdMilestonesCreate = cli.Command{
|
|||||||
Aliases: []string{"d"},
|
Aliases: []string{"d"},
|
||||||
Usage: "milestone description to create",
|
Usage: "milestone description to create",
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "deadline",
|
||||||
|
Aliases: []string{"expires", "x"},
|
||||||
|
Usage: "set milestone deadline (default is no due date)",
|
||||||
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "state",
|
Name: "state",
|
||||||
Usage: "set milestone state (default is open)",
|
Usage: "set milestone state (default is open)",
|
||||||
@ -41,13 +48,17 @@ var CmdMilestonesCreate = cli.Command{
|
|||||||
}, flags.AllDefaultFlags...),
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMilestonesCreate(ctx *cli.Context) error {
|
func runMilestonesCreate(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
|
||||||
title := ctx.String("title")
|
date := ctx.String("deadline")
|
||||||
if len(title) == 0 {
|
deadline := &time.Time{}
|
||||||
fmt.Printf("Title is required\n")
|
if date != "" {
|
||||||
return nil
|
t, err := dateparse.ParseAny(date)
|
||||||
|
if err == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
deadline = &t
|
||||||
}
|
}
|
||||||
|
|
||||||
state := gitea.StateOpen
|
state := gitea.StateOpen
|
||||||
@ -55,15 +66,17 @@ func runMilestonesCreate(ctx *cli.Context) error {
|
|||||||
state = gitea.StateClosed
|
state = gitea.StateClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
mile, _, err := login.Client().CreateMilestone(owner, repo, gitea.CreateMilestoneOption{
|
if ctx.NumFlags() == 0 {
|
||||||
Title: title,
|
return interact.CreateMilestone(ctx.Login, ctx.Owner, ctx.Repo)
|
||||||
Description: ctx.String("description"),
|
|
||||||
State: state,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
print.MilestoneDetails(mile)
|
return task.CreateMilestone(
|
||||||
return nil
|
ctx.Login,
|
||||||
|
ctx.Owner,
|
||||||
|
ctx.Repo,
|
||||||
|
ctx.String("title"),
|
||||||
|
ctx.String("description"),
|
||||||
|
deadline,
|
||||||
|
state,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ package milestones
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@ -22,10 +22,11 @@ var CmdMilestonesDelete = cli.Command{
|
|||||||
Flags: flags.AllDefaultFlags,
|
Flags: flags.AllDefaultFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteMilestone(ctx *cli.Context) error {
|
func deleteMilestone(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
_, err := client.DeleteMilestoneByName(owner, repo, ctx.Args().First())
|
_, err := client.DeleteMilestoneByName(ctx.Owner, ctx.Repo, ctx.Args().First())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
@ -16,6 +16,10 @@ import (
|
|||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var msIssuesFieldsFlag = flags.FieldsFlag(print.IssueFields, []string{
|
||||||
|
"index", "kind", "title", "state", "updated", "labels",
|
||||||
|
})
|
||||||
|
|
||||||
// CmdMilestonesIssues represents a sub command of milestones to manage issue/pull of an milestone
|
// CmdMilestonesIssues represents a sub command of milestones to manage issue/pull of an milestone
|
||||||
var CmdMilestonesIssues = cli.Command{
|
var CmdMilestonesIssues = cli.Command{
|
||||||
Name: "issues",
|
Name: "issues",
|
||||||
@ -40,6 +44,7 @@ var CmdMilestonesIssues = cli.Command{
|
|||||||
},
|
},
|
||||||
&flags.PaginationPageFlag,
|
&flags.PaginationPageFlag,
|
||||||
&flags.PaginationLimitFlag,
|
&flags.PaginationLimitFlag,
|
||||||
|
msIssuesFieldsFlag,
|
||||||
}, flags.AllDefaultFlags...),
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,9 +70,10 @@ var CmdMilestoneRemoveIssue = cli.Command{
|
|||||||
Flags: flags.AllDefaultFlags,
|
Flags: flags.AllDefaultFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMilestoneIssueList(ctx *cli.Context) error {
|
func runMilestoneIssueList(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
state := gitea.StateOpen
|
state := gitea.StateOpen
|
||||||
switch ctx.String("state") {
|
switch ctx.String("state") {
|
||||||
@ -91,13 +97,13 @@ func runMilestoneIssueList(ctx *cli.Context) error {
|
|||||||
|
|
||||||
milestone := ctx.Args().First()
|
milestone := ctx.Args().First()
|
||||||
// make sure milestone exist
|
// make sure milestone exist
|
||||||
_, _, err := client.GetMilestoneByName(owner, repo, milestone)
|
_, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, milestone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
issues, _, err := client.ListRepoIssues(owner, repo, gitea.ListIssueOption{
|
issues, _, err := client.ListRepoIssues(ctx.Owner, ctx.Repo, gitea.ListIssueOption{
|
||||||
ListOptions: flags.GetListOptions(ctx),
|
ListOptions: ctx.GetListOptions(),
|
||||||
Milestones: []string{milestone},
|
Milestones: []string{milestone},
|
||||||
Type: kind,
|
Type: kind,
|
||||||
State: state,
|
State: state,
|
||||||
@ -106,13 +112,18 @@ func runMilestoneIssueList(ctx *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
print.IssuesPullsList(issues, flags.GlobalOutputValue)
|
fields, err := msIssuesFieldsFlag.GetValues(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
print.IssuesPullsList(issues, ctx.Output, fields)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMilestoneIssueAdd(ctx *cli.Context) error {
|
func runMilestoneIssueAdd(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
client := ctx.Login.Client()
|
||||||
if ctx.Args().Len() != 2 {
|
if ctx.Args().Len() != 2 {
|
||||||
return fmt.Errorf("need two arguments")
|
return fmt.Errorf("need two arguments")
|
||||||
}
|
}
|
||||||
@ -125,20 +136,21 @@ func runMilestoneIssueAdd(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make sure milestone exist
|
// make sure milestone exist
|
||||||
mile, _, err := client.GetMilestoneByName(owner, repo, mileName)
|
mile, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, mileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = client.EditIssue(owner, repo, idx, gitea.EditIssueOption{
|
_, _, err = client.EditIssue(ctx.Owner, ctx.Repo, idx, gitea.EditIssueOption{
|
||||||
Milestone: &mile.ID,
|
Milestone: &mile.ID,
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMilestoneIssueRemove(ctx *cli.Context) error {
|
func runMilestoneIssueRemove(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
client := ctx.Login.Client()
|
||||||
if ctx.Args().Len() != 2 {
|
if ctx.Args().Len() != 2 {
|
||||||
return fmt.Errorf("need two arguments")
|
return fmt.Errorf("need two arguments")
|
||||||
}
|
}
|
||||||
@ -150,7 +162,7 @@ func runMilestoneIssueRemove(ctx *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
issue, _, err := client.GetIssue(owner, repo, idx)
|
issue, _, err := client.GetIssue(ctx.Owner, ctx.Repo, idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -164,7 +176,7 @@ func runMilestoneIssueRemove(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
zero := int64(0)
|
zero := int64(0)
|
||||||
_, _, err = client.EditIssue(owner, repo, idx, gitea.EditIssueOption{
|
_, _, err = client.EditIssue(ctx.Owner, ctx.Repo, idx, gitea.EditIssueOption{
|
||||||
Milestone: &zero,
|
Milestone: &zero,
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
|
@ -5,10 +5,8 @@
|
|||||||
package milestones
|
package milestones
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
@ -17,8 +15,8 @@ import (
|
|||||||
|
|
||||||
// CmdMilestonesList represents a sub command of milestones to list milestones
|
// CmdMilestonesList represents a sub command of milestones to list milestones
|
||||||
var CmdMilestonesList = cli.Command{
|
var CmdMilestonesList = cli.Command{
|
||||||
Name: "ls",
|
Name: "list",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List milestones of the repository",
|
Usage: "List milestones of the repository",
|
||||||
Description: `List milestones of the repository`,
|
Description: `List milestones of the repository`,
|
||||||
Action: RunMilestonesList,
|
Action: RunMilestonesList,
|
||||||
@ -34,8 +32,9 @@ var CmdMilestonesList = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunMilestonesList list milestones
|
// RunMilestonesList list milestones
|
||||||
func RunMilestonesList(ctx *cli.Context) error {
|
func RunMilestonesList(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
state := gitea.StateOpen
|
state := gitea.StateOpen
|
||||||
switch ctx.String("state") {
|
switch ctx.String("state") {
|
||||||
@ -45,15 +44,16 @@ func RunMilestonesList(ctx *cli.Context) error {
|
|||||||
state = gitea.StateClosed
|
state = gitea.StateClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
milestones, _, err := login.Client().ListRepoMilestones(owner, repo, gitea.ListMilestoneOption{
|
client := ctx.Login.Client()
|
||||||
ListOptions: flags.GetListOptions(ctx),
|
milestones, _, err := client.ListRepoMilestones(ctx.Owner, ctx.Repo, gitea.ListMilestoneOption{
|
||||||
|
ListOptions: ctx.GetListOptions(),
|
||||||
State: state,
|
State: state,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
print.MilestonesList(milestones, flags.GlobalOutputValue, state)
|
print.MilestonesList(milestones, ctx.Output, state)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ package milestones
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -25,15 +25,16 @@ var CmdMilestonesReopen = cli.Command{
|
|||||||
Flags: flags.AllDefaultFlags,
|
Flags: flags.AllDefaultFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func editMilestoneStatus(ctx *cli.Context, close bool) error {
|
func editMilestoneStatus(cmd *cli.Context, close bool) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
state := gitea.StateOpen
|
state := gitea.StateOpen
|
||||||
if close {
|
if close {
|
||||||
state = gitea.StateClosed
|
state = gitea.StateClosed
|
||||||
}
|
}
|
||||||
_, _, err := client.EditMilestoneByName(owner, repo, ctx.Args().First(), gitea.EditMilestoneOption{
|
_, _, err := client.EditMilestoneByName(ctx.Owner, ctx.Repo, ctx.Args().First(), gitea.EditMilestoneOption{
|
||||||
State: &state,
|
State: &state,
|
||||||
Title: ctx.Args().First(),
|
Title: ctx.Args().First(),
|
||||||
})
|
})
|
||||||
|
@ -5,78 +5,25 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"code.gitea.io/tea/cmd/notifications"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/modules/config"
|
|
||||||
"code.gitea.io/tea/modules/print"
|
|
||||||
|
|
||||||
"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", "notif"},
|
Aliases: []string{"notification", "n"},
|
||||||
|
Category: catHelpers,
|
||||||
Usage: "Show notifications",
|
Usage: "Show notifications",
|
||||||
Description: "Show notifications, by default based of the current repo and unread one",
|
Description: "Show notifications, by default based on the current repo if available",
|
||||||
Action: runNotifications,
|
Action: notifications.RunNotificationsList,
|
||||||
Flags: append([]cli.Flag{
|
Subcommands: []*cli.Command{
|
||||||
&cli.BoolFlag{
|
¬ifications.CmdNotificationsList,
|
||||||
Name: "all",
|
¬ifications.CmdNotificationsMarkRead,
|
||||||
Aliases: []string{"a"},
|
¬ifications.CmdNotificationsMarkUnread,
|
||||||
Usage: "show all notifications of related gitea instance",
|
¬ifications.CmdNotificationsMarkPinned,
|
||||||
|
¬ifications.CmdNotificationsUnpin,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
Flags: notifications.CmdNotificationsList.Flags,
|
||||||
Name: "read",
|
|
||||||
Aliases: []string{"rd"},
|
|
||||||
Usage: "show read notifications instead unread",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "pinned",
|
|
||||||
Aliases: []string{"pd"},
|
|
||||||
Usage: "show pinned notifications instead unread",
|
|
||||||
},
|
|
||||||
&flags.PaginationPageFlag,
|
|
||||||
&flags.PaginationLimitFlag,
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
|
||||||
|
|
||||||
func runNotifications(ctx *cli.Context) error {
|
|
||||||
var news []*gitea.NotificationThread
|
|
||||||
var err error
|
|
||||||
|
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
|
||||||
|
|
||||||
listOpts := flags.GetListOptions(ctx)
|
|
||||||
if listOpts.Page == 0 {
|
|
||||||
listOpts.Page = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var status []gitea.NotifyStatus
|
|
||||||
if ctx.Bool("read") {
|
|
||||||
status = []gitea.NotifyStatus{gitea.NotifyStatusRead}
|
|
||||||
}
|
|
||||||
if ctx.Bool("pinned") {
|
|
||||||
status = append(status, gitea.NotifyStatusPinned)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Bool("all") {
|
|
||||||
news, _, err = login.Client().ListNotifications(gitea.ListNotificationOptions{
|
|
||||||
ListOptions: listOpts,
|
|
||||||
Status: status,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
news, _, err = login.Client().ListRepoNotifications(owner, repo, gitea.ListNotificationOptions{
|
|
||||||
ListOptions: listOpts,
|
|
||||||
Status: status,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
print.NotificationsList(news, flags.GlobalOutputValue, ctx.Bool("all"))
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
89
cmd/notifications/list.go
Normal file
89
cmd/notifications/list.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package 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
|
||||||
|
}
|
139
cmd/notifications/mark_as.go
Normal file
139
cmd/notifications/mark_as.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package notifications
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/context"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdNotificationsMarkRead represents a sub command of notifications to list read notifications
|
||||||
|
var CmdNotificationsMarkRead = cli.Command{
|
||||||
|
Name: "read",
|
||||||
|
Aliases: []string{"r"},
|
||||||
|
Usage: "Mark all filtered or a specific notification as read",
|
||||||
|
Description: "Mark all filtered or a specific notification as read",
|
||||||
|
ArgsUsage: "[all | <notification id>]",
|
||||||
|
Flags: flags.NotificationFlags,
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
cmd := context.InitCommand(ctx)
|
||||||
|
filter, err := flags.NotificationStateFlag.GetValues(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !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
|
||||||
|
}
|
28
cmd/open.go
28
cmd/open.go
@ -5,12 +5,11 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
local_git "code.gitea.io/tea/modules/git"
|
local_git "code.gitea.io/tea/modules/git"
|
||||||
|
|
||||||
"github.com/skratchdot/open-golang/open"
|
"github.com/skratchdot/open-golang/open"
|
||||||
@ -20,14 +19,17 @@ 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",
|
||||||
Usage: "Open something of the repository on web browser",
|
Aliases: []string{"o"},
|
||||||
Description: `Open something of the repository on web browser`,
|
Category: catHelpers,
|
||||||
|
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{}, flags.LoginRepoFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runOpen(ctx *cli.Context) error {
|
func runOpen(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
var suffix string
|
var suffix string
|
||||||
number := ctx.Args().Get(0)
|
number := ctx.Args().Get(0)
|
||||||
@ -41,12 +43,11 @@ func runOpen(ctx *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 {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
b, err := repo.Head()
|
b, err := repo.Head()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
name := b.Name()
|
name := b.Name()
|
||||||
switch {
|
switch {
|
||||||
@ -73,11 +74,6 @@ func runOpen(ctx *cli.Context) error {
|
|||||||
suffix = number
|
suffix = number
|
||||||
}
|
}
|
||||||
|
|
||||||
u := path.Join(login.URL, owner, repo, suffix)
|
u := path.Join(ctx.Login.URL, ctx.Owner, ctx.Repo, suffix)
|
||||||
err := open.Run(u)
|
return open.Run(u)
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/organizations"
|
"code.gitea.io/tea/cmd/organizations"
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ import (
|
|||||||
var CmdOrgs = cli.Command{
|
var CmdOrgs = cli.Command{
|
||||||
Name: "organizations",
|
Name: "organizations",
|
||||||
Aliases: []string{"organization", "org"},
|
Aliases: []string{"organization", "org"},
|
||||||
|
Category: catEntities,
|
||||||
Usage: "List, create, delete organizations",
|
Usage: "List, create, delete organizations",
|
||||||
Description: "Show organization details",
|
Description: "Show organization details",
|
||||||
ArgsUsage: "[<organization>]",
|
ArgsUsage: "[<organization>]",
|
||||||
@ -24,6 +25,7 @@ var CmdOrgs = cli.Command{
|
|||||||
&organizations.CmdOrganizationList,
|
&organizations.CmdOrganizationList,
|
||||||
&organizations.CmdOrganizationDelete,
|
&organizations.CmdOrganizationDelete,
|
||||||
},
|
},
|
||||||
|
Flags: organizations.CmdOrganizationList.Flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runOrganizations(ctx *cli.Context) error {
|
func runOrganizations(ctx *cli.Context) error {
|
||||||
@ -34,7 +36,5 @@ func runOrganizations(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runOrganizationDetail(path string) error {
|
func runOrganizationDetail(path string) error {
|
||||||
|
return fmt.Errorf("Not yet implemented")
|
||||||
log.Fatal("Not yet implemented.")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
package organizations
|
package organizations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,23 +20,25 @@ var CmdOrganizationDelete = cli.Command{
|
|||||||
Description: "Delete users organizations",
|
Description: "Delete users organizations",
|
||||||
ArgsUsage: "<organization name>",
|
ArgsUsage: "<organization name>",
|
||||||
Action: RunOrganizationDelete,
|
Action: RunOrganizationDelete,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&flags.LoginFlag,
|
||||||
|
&flags.RemoteFlag,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunOrganizationDelete delete user organization
|
// RunOrganizationDelete delete user organization
|
||||||
func RunOrganizationDelete(ctx *cli.Context) error {
|
func RunOrganizationDelete(cmd *cli.Context) error {
|
||||||
login, _, _ := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
|
||||||
client := login.Client()
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
if ctx.Args().Len() < 1 {
|
if ctx.Args().Len() < 1 {
|
||||||
log.Fatal("You have to specify the organization name you want to delete.")
|
return fmt.Errorf("You have to specify the organization name you want to delete")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := client.DeleteOrg(ctx.Args().First())
|
response, err := client.DeleteOrg(ctx.Args().First())
|
||||||
if response != nil && response.StatusCode == 404 {
|
if response != nil && response.StatusCode == 404 {
|
||||||
log.Fatal("The given organization does not exist.")
|
return fmt.Errorf("The given organization does not exist")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -5,10 +5,8 @@
|
|||||||
package organizations
|
package organizations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
@ -17,8 +15,8 @@ import (
|
|||||||
|
|
||||||
// CmdOrganizationList represents a sub command of organizations to list users organizations
|
// CmdOrganizationList represents a sub command of organizations to list users organizations
|
||||||
var CmdOrganizationList = cli.Command{
|
var CmdOrganizationList = cli.Command{
|
||||||
Name: "ls",
|
Name: "list",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List Organizations",
|
Usage: "List Organizations",
|
||||||
Description: "List users organizations",
|
Description: "List users organizations",
|
||||||
Action: RunOrganizationList,
|
Action: RunOrganizationList,
|
||||||
@ -29,17 +27,18 @@ var CmdOrganizationList = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunOrganizationList list user organizations
|
// RunOrganizationList list user organizations
|
||||||
func RunOrganizationList(ctx *cli.Context) error {
|
func RunOrganizationList(cmd *cli.Context) error {
|
||||||
login, _, _ := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
client := login.Client()
|
userOrganizations, _, err := client.ListUserOrgs(ctx.Login.User, gitea.ListOrgsOptions{
|
||||||
|
ListOptions: ctx.GetListOptions(),
|
||||||
userOrganizations, _, err := client.ListUserOrgs(login.User, gitea.ListOrgsOptions{ListOptions: flags.GetListOptions(ctx)})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
print.OrganizationsList(userOrganizations, flags.GlobalOutputValue)
|
print.OrganizationsList(userOrganizations, ctx.Output)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
54
cmd/pulls.go
54
cmd/pulls.go
@ -7,11 +7,12 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/cmd/pulls"
|
"code.gitea.io/tea/cmd/pulls"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
|
"code.gitea.io/tea/modules/interact"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
"code.gitea.io/tea/modules/workaround"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -21,44 +22,73 @@ import (
|
|||||||
var CmdPulls = cli.Command{
|
var CmdPulls = cli.Command{
|
||||||
Name: "pulls",
|
Name: "pulls",
|
||||||
Aliases: []string{"pull", "pr"},
|
Aliases: []string{"pull", "pr"},
|
||||||
Usage: "List, create, checkout and clean pull requests",
|
Category: catEntities,
|
||||||
Description: `List, create, checkout and clean pull requests`,
|
Usage: "Manage and checkout pull requests",
|
||||||
|
Description: `Lists PRs when called without argument. If PR index is provided, will show it in detail.`,
|
||||||
ArgsUsage: "[<pull index>]",
|
ArgsUsage: "[<pull index>]",
|
||||||
Action: runPulls,
|
Action: runPulls,
|
||||||
Flags: flags.IssuePRFlags,
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "comments",
|
||||||
|
Usage: "Wether to display comments (will prompt if not provided & run interactively)",
|
||||||
|
},
|
||||||
|
}, pulls.CmdPullsList.Flags...),
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
&pulls.CmdPullsList,
|
&pulls.CmdPullsList,
|
||||||
&pulls.CmdPullsCheckout,
|
&pulls.CmdPullsCheckout,
|
||||||
&pulls.CmdPullsClean,
|
&pulls.CmdPullsClean,
|
||||||
&pulls.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 {
|
if ctx.Args().Len() == 1 {
|
||||||
return runPullDetail(ctx.Args().First())
|
return runPullDetail(ctx, ctx.Args().First())
|
||||||
}
|
}
|
||||||
return pulls.RunPullsList(ctx)
|
return pulls.RunPullsList(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPullDetail(index string) error {
|
func runPullDetail(cmd *cli.Context, index string) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
idx, err := utils.ArgToIndex(index)
|
idx, err := utils.ArgToIndex(index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := login.Client()
|
client := ctx.Login.Client()
|
||||||
pr, _, err := client.GetPullRequest(owner, repo, idx)
|
pr, _, err := client.GetPullRequest(ctx.Owner, ctx.Repo, idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := workaround.FixPullHeadSha(client, pr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
reviews, _, err := client.ListPullReviews(owner, repo, idx, gitea.ListPullReviewsOptions{})
|
reviews, _, err := client.ListPullReviews(ctx.Owner, ctx.Repo, idx, gitea.ListPullReviewsOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("error while loading reviews: %v\n", err)
|
fmt.Printf("error while loading reviews: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
print.PullDetails(pr, reviews)
|
ci, _, err := client.GetCombinedStatus(ctx.Owner, ctx.Repo, pr.Head.Sha)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error while loading CI: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
45
cmd/pulls/approve.go
Normal file
45
cmd/pulls/approve.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// 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,
|
||||||
|
}
|
@ -5,10 +5,10 @@
|
|||||||
package pulls
|
package pulls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/interact"
|
"code.gitea.io/tea/modules/interact"
|
||||||
"code.gitea.io/tea/modules/task"
|
"code.gitea.io/tea/modules/task"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
@ -19,22 +19,30 @@ import (
|
|||||||
// CmdPullsCheckout is a command to locally checkout the given PR
|
// CmdPullsCheckout is a command to locally checkout the given PR
|
||||||
var CmdPullsCheckout = cli.Command{
|
var CmdPullsCheckout = cli.Command{
|
||||||
Name: "checkout",
|
Name: "checkout",
|
||||||
|
Aliases: []string{"co"},
|
||||||
Usage: "Locally check out the given PR",
|
Usage: "Locally check out the given PR",
|
||||||
Description: `Locally check out the given PR`,
|
Description: `Locally check out the given PR`,
|
||||||
Action: runPullsCheckout,
|
Action: runPullsCheckout,
|
||||||
ArgsUsage: "<pull index>",
|
ArgsUsage: "<pull index>",
|
||||||
Flags: flags.AllDefaultFlags,
|
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(ctx *cli.Context) error {
|
func runPullsCheckout(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{LocalRepo: true})
|
||||||
if ctx.Args().Len() != 1 {
|
if ctx.Args().Len() != 1 {
|
||||||
log.Fatal("Must specify a PR index")
|
return fmt.Errorf("Must specify a PR index")
|
||||||
}
|
}
|
||||||
idx, err := utils.ArgToIndex(ctx.Args().First())
|
idx, err := utils.ArgToIndex(ctx.Args().First())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return task.PullCheckout(login, owner, repo, idx, interact.PromptPassword)
|
return task.PullCheckout(ctx.Login, ctx.Owner, ctx.Repo, ctx.Bool("branch"), idx, interact.PromptPassword)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/interact"
|
"code.gitea.io/tea/modules/interact"
|
||||||
"code.gitea.io/tea/modules/task"
|
"code.gitea.io/tea/modules/task"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
@ -31,8 +31,9 @@ var CmdPullsClean = cli.Command{
|
|||||||
}, flags.AllDefaultFlags...),
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPullsClean(ctx *cli.Context) error {
|
func runPullsClean(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{LocalRepo: true})
|
||||||
if ctx.Args().Len() != 1 {
|
if ctx.Args().Len() != 1 {
|
||||||
return fmt.Errorf("Must specify a PR index")
|
return fmt.Errorf("Must specify a PR index")
|
||||||
}
|
}
|
||||||
@ -42,5 +43,5 @@ func runPullsClean(ctx *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return task.PullClean(login, owner, repo, idx, ctx.Bool("ignore-sha"), interact.PromptPassword)
|
return task.PullClean(ctx.Login, ctx.Owner, ctx.Repo, idx, ctx.Bool("ignore-sha"), interact.PromptPassword)
|
||||||
}
|
}
|
||||||
|
25
cmd/pulls/close.go
Normal file
25
cmd/pulls/close.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 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,
|
||||||
|
}
|
@ -6,7 +6,7 @@ package pulls
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/interact"
|
"code.gitea.io/tea/modules/interact"
|
||||||
"code.gitea.io/tea/modules/task"
|
"code.gitea.io/tea/modules/task"
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ import (
|
|||||||
// CmdPullsCreate creates a pull request
|
// CmdPullsCreate creates a pull request
|
||||||
var CmdPullsCreate = cli.Command{
|
var CmdPullsCreate = cli.Command{
|
||||||
Name: "create",
|
Name: "create",
|
||||||
|
Aliases: []string{"c"},
|
||||||
Usage: "Create a pull-request",
|
Usage: "Create a pull-request",
|
||||||
Description: "Create a pull-request",
|
Description: "Create a pull-request",
|
||||||
Action: runPullsCreate,
|
Action: runPullsCreate,
|
||||||
@ -29,35 +30,27 @@ var CmdPullsCreate = cli.Command{
|
|||||||
Aliases: []string{"b"},
|
Aliases: []string{"b"},
|
||||||
Usage: "Set base branch (default is default branch)",
|
Usage: "Set base branch (default is default branch)",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
}, flags.IssuePREditFlags...),
|
||||||
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",
|
|
||||||
},
|
|
||||||
}, flags.AllDefaultFlags...),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPullsCreate(ctx *cli.Context) error {
|
func runPullsCreate(cmd *cli.Context) error {
|
||||||
login, ownerArg, repoArg := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
|
||||||
// no args -> interactive mode
|
// no args -> interactive mode
|
||||||
if ctx.NumFlags() == 0 {
|
if ctx.NumFlags() == 0 {
|
||||||
return interact.CreatePull(login, ownerArg, repoArg)
|
return interact.CreatePull(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// else use args to create PR
|
// else use args to create PR
|
||||||
|
opts, err := flags.GetIssuePREditFlags(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return task.CreatePull(
|
return task.CreatePull(
|
||||||
login,
|
ctx,
|
||||||
ownerArg,
|
|
||||||
repoArg,
|
|
||||||
ctx.String("base"),
|
ctx.String("base"),
|
||||||
ctx.String("head"),
|
ctx.String("head"),
|
||||||
ctx.String("title"),
|
opts,
|
||||||
ctx.String("description"),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
38
cmd/pulls/edit.go
Normal file
38
cmd/pulls/edit.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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
|
||||||
|
}
|
@ -5,10 +5,8 @@
|
|||||||
package pulls
|
package pulls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
@ -17,8 +15,8 @@ import (
|
|||||||
|
|
||||||
// CmdPullsList represents a sub command of issues to list pulls
|
// CmdPullsList represents a sub command of issues to list pulls
|
||||||
var CmdPullsList = cli.Command{
|
var CmdPullsList = cli.Command{
|
||||||
Name: "ls",
|
Name: "list",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List pull requests of the repository",
|
Usage: "List pull requests of the repository",
|
||||||
Description: `List pull requests of the repository`,
|
Description: `List pull requests of the repository`,
|
||||||
Action: RunPullsList,
|
Action: RunPullsList,
|
||||||
@ -26,8 +24,9 @@ var CmdPullsList = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunPullsList return list of pulls
|
// RunPullsList return list of pulls
|
||||||
func RunPullsList(ctx *cli.Context) error {
|
func RunPullsList(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
state := gitea.StateOpen
|
state := gitea.StateOpen
|
||||||
switch ctx.String("state") {
|
switch ctx.String("state") {
|
||||||
@ -39,14 +38,14 @@ func RunPullsList(ctx *cli.Context) error {
|
|||||||
state = gitea.StateClosed
|
state = gitea.StateClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
prs, _, err := login.Client().ListRepoPullRequests(owner, repo, gitea.ListPullRequestsOptions{
|
prs, _, err := ctx.Login.Client().ListRepoPullRequests(ctx.Owner, ctx.Repo, gitea.ListPullRequestsOptions{
|
||||||
State: state,
|
State: state,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
print.PullsList(prs, flags.GlobalOutputValue)
|
print.PullsList(prs, ctx.Output)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
70
cmd/pulls/merge.go
Normal file
70
cmd/pulls/merge.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package pulls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"code.gitea.io/tea/cmd/flags"
|
||||||
|
"code.gitea.io/tea/modules/context"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdPullsMerge merges a PR
|
||||||
|
var CmdPullsMerge = cli.Command{
|
||||||
|
Name: "merge",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Usage: "Merge a pull request",
|
||||||
|
Description: "Merge a pull request",
|
||||||
|
ArgsUsage: "<pull index>",
|
||||||
|
Flags: append([]cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "style",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "Kind of merge to perform: merge, rebase, squash, rebase-merge",
|
||||||
|
Value: "merge",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "title",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "Merge commit title",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "message",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Usage: "Merge commit message",
|
||||||
|
},
|
||||||
|
}, flags.AllDefaultFlags...),
|
||||||
|
Action: func(cmd *cli.Context) error {
|
||||||
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
|
if ctx.Args().Len() != 1 {
|
||||||
|
return fmt.Errorf("Must specify a PR index")
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, err := utils.ArgToIndex(ctx.Args().First())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
success, _, err := ctx.Login.Client().MergePullRequest(ctx.Owner, ctx.Repo, idx, gitea.MergePullRequestOption{
|
||||||
|
Style: gitea.MergeStyle(ctx.String("style")),
|
||||||
|
Title: ctx.String("title"),
|
||||||
|
Message: ctx.String("message"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !success {
|
||||||
|
return fmt.Errorf("Failed to merge PR. Is it still open?")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
44
cmd/pulls/reject.go
Normal file
44
cmd/pulls/reject.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// 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,
|
||||||
|
}
|
26
cmd/pulls/reopen.go
Normal file
26
cmd/pulls/reopen.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// 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,
|
||||||
|
}
|
40
cmd/pulls/review.go
Normal file
40
cmd/pulls/review.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// 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,
|
||||||
|
}
|
@ -15,7 +15,8 @@ import (
|
|||||||
// ToDo: ReleaseDetails
|
// ToDo: ReleaseDetails
|
||||||
var CmdReleases = cli.Command{
|
var CmdReleases = cli.Command{
|
||||||
Name: "releases",
|
Name: "releases",
|
||||||
Aliases: []string{"release"},
|
Aliases: []string{"release", "r"},
|
||||||
|
Category: catEntities,
|
||||||
Usage: "Manage releases",
|
Usage: "Manage releases",
|
||||||
Description: "Manage releases",
|
Description: "Manage releases",
|
||||||
Action: releases.RunReleasesList,
|
Action: releases.RunReleasesList,
|
||||||
|
@ -6,13 +6,12 @@ package releases
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -21,6 +20,7 @@ import (
|
|||||||
// CmdReleaseCreate represents a sub command of Release to create release
|
// CmdReleaseCreate represents a sub command of Release to create release
|
||||||
var CmdReleaseCreate = cli.Command{
|
var CmdReleaseCreate = cli.Command{
|
||||||
Name: "create",
|
Name: "create",
|
||||||
|
Aliases: []string{"c"},
|
||||||
Usage: "Create a release",
|
Usage: "Create a release",
|
||||||
Description: `Create a release`,
|
Description: `Create a release`,
|
||||||
Action: runReleaseCreate,
|
Action: runReleaseCreate,
|
||||||
@ -61,10 +61,11 @@ var CmdReleaseCreate = cli.Command{
|
|||||||
}, flags.AllDefaultFlags...),
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runReleaseCreate(ctx *cli.Context) error {
|
func runReleaseCreate(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
release, resp, err := login.Client().CreateRelease(owner, repo, gitea.CreateReleaseOption{
|
release, resp, err := ctx.Login.Client().CreateRelease(ctx.Owner, ctx.Repo, gitea.CreateReleaseOption{
|
||||||
TagName: ctx.String("tag"),
|
TagName: ctx.String("tag"),
|
||||||
Target: ctx.String("target"),
|
Target: ctx.String("target"),
|
||||||
Title: ctx.String("title"),
|
Title: ctx.String("title"),
|
||||||
@ -75,24 +76,23 @@ func runReleaseCreate(ctx *cli.Context) error {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if resp != nil && resp.StatusCode == http.StatusConflict {
|
if resp != nil && resp.StatusCode == http.StatusConflict {
|
||||||
fmt.Println("error: There already is a release for this tag")
|
return fmt.Errorf("There already is a release for this tag")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, asset := range ctx.StringSlice("asset") {
|
for _, asset := range ctx.StringSlice("asset") {
|
||||||
var file *os.File
|
var file *os.File
|
||||||
|
|
||||||
if file, err = os.Open(asset); err != nil {
|
if file, err = os.Open(asset); err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := filepath.Base(asset)
|
filePath := filepath.Base(asset)
|
||||||
|
|
||||||
if _, _, err = login.Client().CreateReleaseAttachment(owner, repo, release.ID, file, filePath); err != nil {
|
if _, _, err = ctx.Login.Client().CreateReleaseAttachment(ctx.Owner, ctx.Repo, release.ID, file, filePath); err != nil {
|
||||||
file.Close()
|
file.Close()
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Close()
|
file.Close()
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@ -16,6 +16,7 @@ import (
|
|||||||
// CmdReleaseDelete represents a sub command of Release to delete a release
|
// CmdReleaseDelete represents a sub command of Release to delete a release
|
||||||
var CmdReleaseDelete = cli.Command{
|
var CmdReleaseDelete = cli.Command{
|
||||||
Name: "delete",
|
Name: "delete",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
Usage: "Delete a release",
|
Usage: "Delete a release",
|
||||||
Description: `Delete a release`,
|
Description: `Delete a release`,
|
||||||
ArgsUsage: "<release tag>",
|
ArgsUsage: "<release tag>",
|
||||||
@ -33,9 +34,10 @@ var CmdReleaseDelete = cli.Command{
|
|||||||
}, flags.AllDefaultFlags...),
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runReleaseDelete(ctx *cli.Context) error {
|
func runReleaseDelete(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
tag := ctx.Args().First()
|
tag := ctx.Args().First()
|
||||||
if len(tag) == 0 {
|
if len(tag) == 0 {
|
||||||
@ -48,7 +50,7 @@ func runReleaseDelete(ctx *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
release, err := getReleaseByTag(owner, repo, tag, client)
|
release, err := getReleaseByTag(ctx.Owner, ctx.Repo, tag, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -56,13 +58,13 @@ func runReleaseDelete(ctx *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = client.DeleteRelease(owner, repo, release.ID)
|
_, err = client.DeleteRelease(ctx.Owner, ctx.Repo, release.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Bool("delete-tag") {
|
if ctx.Bool("delete-tag") {
|
||||||
_, err = client.DeleteReleaseTag(owner, repo, tag)
|
_, err = client.DeleteTag(ctx.Owner, ctx.Repo, tag)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -18,6 +18,7 @@ import (
|
|||||||
// CmdReleaseEdit represents a sub command of Release to edit releases
|
// CmdReleaseEdit represents a sub command of Release to edit releases
|
||||||
var CmdReleaseEdit = cli.Command{
|
var CmdReleaseEdit = cli.Command{
|
||||||
Name: "edit",
|
Name: "edit",
|
||||||
|
Aliases: []string{"e"},
|
||||||
Usage: "Edit a release",
|
Usage: "Edit a release",
|
||||||
Description: `Edit a release`,
|
Description: `Edit a release`,
|
||||||
ArgsUsage: "<release tag>",
|
ArgsUsage: "<release tag>",
|
||||||
@ -56,9 +57,10 @@ var CmdReleaseEdit = cli.Command{
|
|||||||
}, flags.AllDefaultFlags...),
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runReleaseEdit(ctx *cli.Context) error {
|
func runReleaseEdit(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
tag := ctx.Args().First()
|
tag := ctx.Args().First()
|
||||||
if len(tag) == 0 {
|
if len(tag) == 0 {
|
||||||
@ -66,7 +68,7 @@ func runReleaseEdit(ctx *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
release, err := getReleaseByTag(owner, repo, tag, client)
|
release, err := getReleaseByTag(ctx.Owner, ctx.Repo, tag, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -82,7 +84,7 @@ func runReleaseEdit(ctx *cli.Context) error {
|
|||||||
isPre = gitea.OptionalBool(strings.ToLower(ctx.String("prerelease"))[:1] == "t")
|
isPre = gitea.OptionalBool(strings.ToLower(ctx.String("prerelease"))[:1] == "t")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = client.EditRelease(owner, repo, release.ID, gitea.EditReleaseOption{
|
_, _, err = client.EditRelease(ctx.Owner, ctx.Repo, release.ID, gitea.EditReleaseOption{
|
||||||
TagName: ctx.String("tag"),
|
TagName: ctx.String("tag"),
|
||||||
Target: ctx.String("target"),
|
Target: ctx.String("target"),
|
||||||
Title: ctx.String("title"),
|
Title: ctx.String("title"),
|
||||||
|
@ -6,10 +6,9 @@ package releases
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
@ -18,8 +17,8 @@ import (
|
|||||||
|
|
||||||
// CmdReleaseList represents a sub command of Release to list releases
|
// CmdReleaseList represents a sub command of Release to list releases
|
||||||
var CmdReleaseList = cli.Command{
|
var CmdReleaseList = cli.Command{
|
||||||
Name: "ls",
|
Name: "list",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List Releases",
|
Usage: "List Releases",
|
||||||
Description: "List Releases",
|
Description: "List Releases",
|
||||||
Action: RunReleasesList,
|
Action: RunReleasesList,
|
||||||
@ -30,15 +29,18 @@ var CmdReleaseList = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunReleasesList list releases
|
// RunReleasesList list releases
|
||||||
func RunReleasesList(ctx *cli.Context) error {
|
func RunReleasesList(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
releases, _, err := login.Client().ListReleases(owner, repo, gitea.ListReleasesOptions{ListOptions: flags.GetListOptions(ctx)})
|
releases, _, err := ctx.Login.Client().ListReleases(ctx.Owner, ctx.Repo, gitea.ListReleasesOptions{
|
||||||
|
ListOptions: ctx.GetListOptions(),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
print.ReleasesList(releases, flags.GlobalOutputValue)
|
print.ReleasesList(releases, ctx.Output)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
cmd/repos.go
16
cmd/repos.go
@ -5,9 +5,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/tea/cmd/flags"
|
|
||||||
"code.gitea.io/tea/cmd/repos"
|
"code.gitea.io/tea/cmd/repos"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
@ -19,6 +18,7 @@ import (
|
|||||||
var CmdRepos = cli.Command{
|
var CmdRepos = cli.Command{
|
||||||
Name: "repos",
|
Name: "repos",
|
||||||
Aliases: []string{"repo"},
|
Aliases: []string{"repo"},
|
||||||
|
Category: catEntities,
|
||||||
Usage: "Show repository details",
|
Usage: "Show repository details",
|
||||||
Description: "Show repository details",
|
Description: "Show repository details",
|
||||||
ArgsUsage: "[<repo owner>/<repo name>]",
|
ArgsUsage: "[<repo owner>/<repo name>]",
|
||||||
@ -33,20 +33,20 @@ var CmdRepos = cli.Command{
|
|||||||
|
|
||||||
func runRepos(ctx *cli.Context) error {
|
func runRepos(ctx *cli.Context) error {
|
||||||
if ctx.Args().Len() == 1 {
|
if ctx.Args().Len() == 1 {
|
||||||
return runRepoDetail(ctx.Args().First())
|
return runRepoDetail(ctx, ctx.Args().First())
|
||||||
}
|
}
|
||||||
return repos.RunReposList(ctx)
|
return repos.RunReposList(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRepoDetail(path string) error {
|
func runRepoDetail(cmd *cli.Context, path string) error {
|
||||||
login, ownerFallback, _ := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
client := ctx.Login.Client()
|
||||||
repoOwner, repoName := utils.GetOwnerAndRepo(path, ownerFallback)
|
repoOwner, repoName := utils.GetOwnerAndRepo(path, ctx.Owner)
|
||||||
repo, _, err := client.GetRepo(repoOwner, repoName)
|
repo, _, err := client.GetRepo(repoOwner, repoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
topics, _, err := client.ListRepoTopics(repo.Owner.UserName, repo.Name, gitea.ListRepoTopicsOptions{})
|
topics, _, err := client.ListRepoTopics(repoOwner, repoName, gitea.ListRepoTopicsOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
@ -82,9 +82,9 @@ var CmdRepoCreate = cli.Command{
|
|||||||
}, flags.LoginOutputFlags...),
|
}, flags.LoginOutputFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRepoCreate(ctx *cli.Context) error {
|
func runRepoCreate(cmd *cli.Context) error {
|
||||||
login, _, _ := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
client := ctx.Login.Client()
|
||||||
var (
|
var (
|
||||||
repo *gitea.Repository
|
repo *gitea.Repository
|
||||||
err error
|
err error
|
||||||
|
@ -6,28 +6,11 @@ package repos
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/print"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// printFieldsFlag provides a selection of fields to print
|
|
||||||
var printFieldsFlag = cli.StringFlag{
|
|
||||||
Name: "fields",
|
|
||||||
Aliases: []string{"f"},
|
|
||||||
Usage: fmt.Sprintf(`Comma-separated list of fields to print. Available values:
|
|
||||||
%s
|
|
||||||
`, strings.Join(print.RepoFields, ",")),
|
|
||||||
Value: "owner,name,type,ssh",
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFields(ctx *cli.Context) []string {
|
|
||||||
return strings.Split(ctx.String("fields"), ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeFilterFlag = cli.StringFlag{
|
var typeFilterFlag = cli.StringFlag{
|
||||||
Name: "type",
|
Name: "type",
|
||||||
Aliases: []string{"T"},
|
Aliases: []string{"T"},
|
||||||
|
@ -6,13 +6,17 @@ package repos
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var repoFieldsFlag = flags.FieldsFlag(print.RepoFields, []string{
|
||||||
|
"owner", "name", "type", "ssh",
|
||||||
|
})
|
||||||
|
|
||||||
// CmdReposListFlags contains all flags needed for repo listing
|
// CmdReposListFlags contains all flags needed for repo listing
|
||||||
var CmdReposListFlags = append([]cli.Flag{
|
var CmdReposListFlags = append([]cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
@ -27,7 +31,7 @@ var CmdReposListFlags = append([]cli.Flag{
|
|||||||
Required: false,
|
Required: false,
|
||||||
Usage: "List your starred repos instead",
|
Usage: "List your starred repos instead",
|
||||||
},
|
},
|
||||||
&printFieldsFlag,
|
repoFieldsFlag,
|
||||||
&typeFilterFlag,
|
&typeFilterFlag,
|
||||||
&flags.PaginationPageFlag,
|
&flags.PaginationPageFlag,
|
||||||
&flags.PaginationLimitFlag,
|
&flags.PaginationLimitFlag,
|
||||||
@ -35,8 +39,8 @@ var CmdReposListFlags = append([]cli.Flag{
|
|||||||
|
|
||||||
// CmdReposList represents a sub command of repos to list them
|
// CmdReposList represents a sub command of repos to list them
|
||||||
var CmdReposList = cli.Command{
|
var CmdReposList = cli.Command{
|
||||||
Name: "ls",
|
Name: "list",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List repositories you have access to",
|
Usage: "List repositories you have access to",
|
||||||
Description: "List repositories you have access to",
|
Description: "List repositories you have access to",
|
||||||
Action: RunReposList,
|
Action: RunReposList,
|
||||||
@ -44,11 +48,11 @@ var CmdReposList = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunReposList list repositories
|
// RunReposList list repositories
|
||||||
func RunReposList(ctx *cli.Context) error {
|
func RunReposList(cmd *cli.Context) error {
|
||||||
login, _, _ := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
typeFilter, err := getTypeFilter(ctx)
|
typeFilter, err := getTypeFilter(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -60,14 +64,14 @@ func RunReposList(ctx *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rps, _, err = client.SearchRepos(gitea.SearchRepoOptions{
|
rps, _, err = client.SearchRepos(gitea.SearchRepoOptions{
|
||||||
ListOptions: flags.GetListOptions(ctx),
|
ListOptions: ctx.GetListOptions(),
|
||||||
StarredByUserID: user.ID,
|
StarredByUserID: user.ID,
|
||||||
})
|
})
|
||||||
} else if ctx.Bool("watched") {
|
} else if ctx.Bool("watched") {
|
||||||
rps, _, err = client.GetMyWatchedRepos() // TODO: this does not expose pagination..
|
rps, _, err = client.GetMyWatchedRepos() // TODO: this does not expose pagination..
|
||||||
} else {
|
} else {
|
||||||
rps, _, err = client.ListMyRepos(gitea.ListReposOptions{
|
rps, _, err = client.ListMyRepos(gitea.ListReposOptions{
|
||||||
ListOptions: flags.GetListOptions(ctx),
|
ListOptions: ctx.GetListOptions(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +84,12 @@ func RunReposList(ctx *cli.Context) error {
|
|||||||
reposFiltered = filterReposByType(rps, typeFilter)
|
reposFiltered = filterReposByType(rps, typeFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
print.ReposList(reposFiltered, flags.GlobalOutputValue, getFields(ctx))
|
fields, err := repoFieldsFlag.GetValues(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
print.ReposList(reposFiltered, ctx.Output, fields)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
package repos
|
package repos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
@ -50,15 +50,15 @@ var CmdReposSearch = cli.Command{
|
|||||||
Required: false,
|
Required: false,
|
||||||
Usage: "Filter archived repos (true|false)",
|
Usage: "Filter archived repos (true|false)",
|
||||||
},
|
},
|
||||||
&printFieldsFlag,
|
repoFieldsFlag,
|
||||||
&flags.PaginationPageFlag,
|
&flags.PaginationPageFlag,
|
||||||
&flags.PaginationLimitFlag,
|
&flags.PaginationLimitFlag,
|
||||||
}, flags.LoginOutputFlags...),
|
}, flags.LoginOutputFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func runReposSearch(ctx *cli.Context) error {
|
func runReposSearch(cmd *cli.Context) error {
|
||||||
login, _, _ := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
var ownerID int64
|
var ownerID int64
|
||||||
if ctx.IsSet("owner") {
|
if ctx.IsSet("owner") {
|
||||||
@ -67,7 +67,7 @@ func runReposSearch(ctx *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// HACK: the client does not return a response on 404, so we can't check res.StatusCode
|
// HACK: the client does not return a response on 404, so we can't check res.StatusCode
|
||||||
if err.Error() != "404 Not Found" {
|
if err.Error() != "404 Not Found" {
|
||||||
log.Fatal("could not find owner: ", err)
|
return fmt.Errorf("Could not find owner: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if owner is no org, its a user
|
// if owner is no org, its a user
|
||||||
@ -93,7 +93,7 @@ func runReposSearch(ctx *cli.Context) error {
|
|||||||
isPrivate = &private
|
isPrivate = &private
|
||||||
}
|
}
|
||||||
|
|
||||||
mode, err := getTypeFilter(ctx)
|
mode, err := getTypeFilter(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ func runReposSearch(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rps, _, err := client.SearchRepos(gitea.SearchRepoOptions{
|
rps, _, err := client.SearchRepos(gitea.SearchRepoOptions{
|
||||||
ListOptions: flags.GetListOptions(ctx),
|
ListOptions: ctx.GetListOptions(),
|
||||||
OwnerID: ownerID,
|
OwnerID: ownerID,
|
||||||
IsPrivate: isPrivate,
|
IsPrivate: isPrivate,
|
||||||
IsArchived: isArchived,
|
IsArchived: isArchived,
|
||||||
@ -123,6 +123,10 @@ func runReposSearch(ctx *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
print.ReposList(rps, flags.GlobalOutputValue, getFields(ctx))
|
fields, err := repoFieldsFlag.GetValues(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
print.ReposList(rps, ctx.Output, fields)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
10
cmd/times.go
10
cmd/times.go
@ -12,21 +12,19 @@ import (
|
|||||||
// 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"},
|
Aliases: []string{"time", "t"},
|
||||||
|
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: runTrackedTimes,
|
Action: times.RunTimesList,
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
×.CmdTrackedTimesAdd,
|
×.CmdTrackedTimesAdd,
|
||||||
×.CmdTrackedTimesDelete,
|
×.CmdTrackedTimesDelete,
|
||||||
×.CmdTrackedTimesReset,
|
×.CmdTrackedTimesReset,
|
||||||
×.CmdTrackedTimesList,
|
×.CmdTrackedTimesList,
|
||||||
},
|
},
|
||||||
}
|
Flags: times.CmdTrackedTimesList.Flags,
|
||||||
|
|
||||||
func runTrackedTimes(ctx *cli.Context) error {
|
|
||||||
return times.RunTimesList(ctx)
|
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,11 @@ package times
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
@ -21,6 +20,7 @@ import (
|
|||||||
// CmdTrackedTimesAdd represents a sub command of times to add time to an issue
|
// CmdTrackedTimesAdd represents a sub command of times to add time to an issue
|
||||||
var CmdTrackedTimesAdd = cli.Command{
|
var CmdTrackedTimesAdd = cli.Command{
|
||||||
Name: "add",
|
Name: "add",
|
||||||
|
Aliases: []string{"a"},
|
||||||
Usage: "Track spent time on an issue",
|
Usage: "Track spent time on an issue",
|
||||||
UsageText: "tea times add <issue> <duration>",
|
UsageText: "tea times add <issue> <duration>",
|
||||||
Description: `Track spent time on an issue
|
Description: `Track spent time on an issue
|
||||||
@ -31,8 +31,9 @@ var CmdTrackedTimesAdd = cli.Command{
|
|||||||
Flags: flags.LoginRepoFlags,
|
Flags: flags.LoginRepoFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTrackedTimesAdd(ctx *cli.Context) error {
|
func runTrackedTimesAdd(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
|
||||||
if ctx.Args().Len() < 2 {
|
if ctx.Args().Len() < 2 {
|
||||||
return fmt.Errorf("No issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText)
|
return fmt.Errorf("No issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||||
@ -40,20 +41,16 @@ func runTrackedTimesAdd(ctx *cli.Context) error {
|
|||||||
|
|
||||||
issue, err := utils.ArgToIndex(ctx.Args().First())
|
issue, err := utils.ArgToIndex(ctx.Args().First())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
duration, err := time.ParseDuration(strings.Join(ctx.Args().Tail(), ""))
|
duration, err := time.ParseDuration(strings.Join(ctx.Args().Tail(), ""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = login.Client().AddTime(owner, repo, issue, gitea.AddTimeOption{
|
_, _, err = ctx.Login.Client().AddTime(ctx.Owner, ctx.Repo, issue, gitea.AddTimeOption{
|
||||||
Time: int64(duration.Seconds()),
|
Time: int64(duration.Seconds()),
|
||||||
})
|
})
|
||||||
if err != nil {
|
return err
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,10 @@ package times
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -26,9 +25,10 @@ var CmdTrackedTimesDelete = cli.Command{
|
|||||||
Flags: flags.LoginRepoFlags,
|
Flags: flags.LoginRepoFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTrackedTimesDelete(ctx *cli.Context) error {
|
func runTrackedTimesDelete(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
if ctx.Args().Len() < 2 {
|
if ctx.Args().Len() < 2 {
|
||||||
return fmt.Errorf("No issue or time ID specified.\nUsage:\t%s", ctx.Command.UsageText)
|
return fmt.Errorf("No issue or time ID specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||||
@ -36,18 +36,14 @@ func runTrackedTimesDelete(ctx *cli.Context) error {
|
|||||||
|
|
||||||
issue, err := utils.ArgToIndex(ctx.Args().First())
|
issue, err := utils.ArgToIndex(ctx.Args().First())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
timeID, err := strconv.ParseInt(ctx.Args().Get(1), 10, 64)
|
timeID, err := strconv.ParseInt(ctx.Args().Get(1), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = client.DeleteTime(owner, repo, issue, timeID)
|
_, err = client.DeleteTime(ctx.Owner, ctx.Repo, issue, timeID)
|
||||||
if err != nil {
|
return err
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/print"
|
"code.gitea.io/tea/modules/print"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
@ -19,15 +19,28 @@ import (
|
|||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NOTE: not using NewCsvFlag, as we don't want an alias & default value.
|
||||||
|
var timeFieldsFlag = &flags.CsvFlag{
|
||||||
|
AvailableFields: print.TrackedTimeFields,
|
||||||
|
StringFlag: cli.StringFlag{
|
||||||
|
Name: "fields",
|
||||||
|
Usage: fmt.Sprintf(`Comma-separated list of fields to print. Available values:
|
||||||
|
%s
|
||||||
|
`, strings.Join(print.TrackedTimeFields, ",")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// CmdTrackedTimesList represents a sub command of times to list them
|
// CmdTrackedTimesList represents a sub command of times to list them
|
||||||
var CmdTrackedTimesList = cli.Command{
|
var CmdTrackedTimesList = cli.Command{
|
||||||
Name: "ls",
|
Name: "list",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"ls"},
|
||||||
Action: RunTimesList,
|
Action: RunTimesList,
|
||||||
Usage: "Operate on tracked times of a repository's issues & pulls",
|
Usage: "List tracked times on issues & pulls",
|
||||||
Description: `Operate on tracked times of a repository's issues & pulls.
|
Description: `List tracked times, across repos, or on a single repo or issue:
|
||||||
Depending on your permissions on the repository, only your own tracked
|
- given a username all times on a repo by that user are shown,
|
||||||
times might be listed.`,
|
- 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]",
|
ArgsUsage: "[username | #issue]",
|
||||||
|
|
||||||
Flags: append([]cli.Flag{
|
Flags: append([]cli.Flag{
|
||||||
@ -46,52 +59,74 @@ var CmdTrackedTimesList = cli.Command{
|
|||||||
Aliases: []string{"t"},
|
Aliases: []string{"t"},
|
||||||
Usage: "Print the total duration at the end",
|
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...),
|
}, flags.AllDefaultFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunTimesList list repositories
|
// RunTimesList list repositories
|
||||||
func RunTimesList(ctx *cli.Context) error {
|
func RunTimesList(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
var times []*gitea.TrackedTime
|
var times []*gitea.TrackedTime
|
||||||
var err error
|
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 := utils.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
|
var from, until time.Time
|
||||||
if ctx.String("from") != "" {
|
var fields []string
|
||||||
|
|
||||||
|
if ctx.IsSet("from") {
|
||||||
from, err = dateparse.ParseLocal(ctx.String("from"))
|
from, err = dateparse.ParseLocal(ctx.String("from"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ctx.String("until") != "" {
|
if ctx.IsSet("until") {
|
||||||
until, err = dateparse.ParseLocal(ctx.String("until"))
|
until, err = dateparse.ParseLocal(ctx.String("until"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print.TrackedTimesList(times, flags.GlobalOutputValue, from, until, ctx.Bool("total"))
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,9 @@ package times
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/cmd/flags"
|
"code.gitea.io/tea/cmd/flags"
|
||||||
"code.gitea.io/tea/modules/config"
|
"code.gitea.io/tea/modules/context"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -25,9 +24,10 @@ var CmdTrackedTimesReset = cli.Command{
|
|||||||
Flags: flags.LoginRepoFlags,
|
Flags: flags.LoginRepoFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTrackedTimesReset(ctx *cli.Context) error {
|
func runTrackedTimesReset(cmd *cli.Context) error {
|
||||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
ctx := context.InitCommand(cmd)
|
||||||
client := login.Client()
|
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||||
|
client := ctx.Login.Client()
|
||||||
|
|
||||||
if ctx.Args().Len() != 1 {
|
if ctx.Args().Len() != 1 {
|
||||||
return fmt.Errorf("No issue specified.\nUsage:\t%s", ctx.Command.UsageText)
|
return fmt.Errorf("No issue specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||||
@ -35,13 +35,9 @@ func runTrackedTimesReset(ctx *cli.Context) error {
|
|||||||
|
|
||||||
issue, err := utils.ArgToIndex(ctx.Args().First())
|
issue, err := utils.ArgToIndex(ctx.Args().First())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = client.ResetIssueTime(owner, repo, issue)
|
_, err = client.ResetIssueTime(ctx.Owner, ctx.Repo, issue)
|
||||||
if err != nil {
|
return err
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
9
contrib/autocomplete.ps1
Normal file
9
contrib/autocomplete.ps1
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
$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', $_)
|
||||||
|
}
|
||||||
|
}
|
21
contrib/autocomplete.sh
Normal file
21
contrib/autocomplete.sh
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#! /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
|
23
contrib/autocomplete.zsh
Normal file
23
contrib/autocomplete.zsh
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#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
|
55
go.mod
55
go.mod
@ -4,34 +4,37 @@ go 1.13
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
code.gitea.io/gitea-vet v0.2.1
|
code.gitea.io/gitea-vet v0.2.1
|
||||||
code.gitea.io/sdk/gitea v0.13.1-0.20201209180822-68eec69f472e
|
code.gitea.io/sdk/gitea v0.15.0
|
||||||
github.com/AlecAivazis/survey/v2 v2.2.2
|
gitea.com/noerw/unidiff-comments v0.0.0-20201219085024-64aec5658f2b
|
||||||
github.com/Microsoft/go-winio v0.4.15 // indirect
|
github.com/AlecAivazis/survey/v2 v2.3.1
|
||||||
github.com/adrg/xdg v0.2.2
|
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c // indirect
|
||||||
github.com/alecthomas/chroma v0.8.1 // indirect
|
github.com/adrg/xdg v0.3.3
|
||||||
github.com/araddon/dateparse v0.0.0-20201001162425-8aadafed4dc4
|
github.com/alecthomas/chroma v0.9.2 // indirect
|
||||||
github.com/charmbracelet/glamour v0.2.0
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
github.com/charmbracelet/glamour v0.3.0
|
||||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||||
github.com/go-git/go-git/v5 v5.2.0
|
github.com/go-git/go-git/v5 v5.4.2
|
||||||
github.com/imdario/mergo v0.3.11 // indirect
|
github.com/hashicorp/go-version v1.3.0 // indirect
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/microcosm-cc/bluemonday v1.0.4 // indirect
|
github.com/microcosm-cc/bluemonday v1.0.15 // indirect
|
||||||
github.com/muesli/reflow v0.2.0 // indirect
|
github.com/muesli/reflow v0.3.0 // indirect
|
||||||
github.com/muesli/termenv v0.7.4
|
github.com/muesli/termenv v0.9.0
|
||||||
github.com/olekukonko/tablewriter v0.0.4
|
github.com/olekukonko/tablewriter v0.0.5
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
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.6.1
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/urfave/cli/v2 v2.3.0
|
github.com/urfave/cli/v2 v2.3.0
|
||||||
github.com/xanzy/ssh-agent v0.3.0 // indirect
|
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
github.com/yuin/goldmark v1.4.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||||
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf // indirect
|
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
|
||||||
golang.org/x/text v0.3.4 // indirect
|
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
|
||||||
golang.org/x/tools v0.0.0-20201105220310-78b158585360 // indirect
|
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||||
gopkg.in/yaml.v2 v2.3.0
|
golang.org/x/text v0.3.7 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // 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
|
||||||
|
240
go.sum
240
go.sum
@ -1,26 +1,30 @@
|
|||||||
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
|
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
|
||||||
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
||||||
code.gitea.io/sdk/gitea v0.13.1-0.20201209180822-68eec69f472e h1:oJOoT5TGbSYRNGUhEiiEz3MqFjU6wELN0/liCZ3RmVg=
|
code.gitea.io/sdk/gitea v0.15.0 h1:tsNhxDM/2N1Ohv1Xq5UWrht/esg0WmtRj4wsHVHriTg=
|
||||||
code.gitea.io/sdk/gitea v0.13.1-0.20201209180822-68eec69f472e/go.mod h1:89WiyOX1KEcvjP66sRHdu0RafojGo60bT9UqW17VbWs=
|
code.gitea.io/sdk/gitea v0.15.0/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
|
||||||
github.com/AlecAivazis/survey/v2 v2.2.2 h1:1I4qBrNsHQE+91tQCqVlfrKe9DEL65949d1oKZWVELY=
|
gitea.com/noerw/unidiff-comments v0.0.0-20201219085024-64aec5658f2b h1:CLYsMGcGLohESQDMth+RgJ4cB3CCHToxnj0zBbvB3sE=
|
||||||
github.com/AlecAivazis/survey/v2 v2.2.2/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
|
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 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
|
||||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||||
github.com/Microsoft/go-winio v0.4.15 h1:qkLXKzb1QoVatRyd/YlXZ/Kg0m5K3SPuoD82jjSOaBc=
|
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||||
github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
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 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||||
github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo=
|
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
|
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c h1:FP7mMdsXy0ybzar1sJeIcZtaJka0U/ZmLTW4wRpolYk=
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
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 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||||
github.com/alecthomas/chroma v0.7.3 h1:NfdAERMy+esYQs8OXk0I868/qDxxCEo7FMz1WIqMAeI=
|
github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||||
github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
github.com/alecthomas/chroma v0.9.2 h1:yU1sE2+TZbLIQPMk30SolL2Hn53SR/Pv750f7qZ/XMs=
|
||||||
github.com/alecthomas/chroma v0.8.1 h1:ym20sbvyC6RXz45u4qDglcgr8E313oPROshcuCHqiEE=
|
github.com/alecthomas/chroma v0.9.2/go.mod h1:eMuEnpA18XbG/WhOWtCzJHS7WqEtDAI+HxdwoW0nVSk=
|
||||||
github.com/alecthomas/chroma v0.8.1/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
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/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/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||||
@ -28,27 +32,22 @@ github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkx
|
|||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
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-20201001162425-8aadafed4dc4 h1:OkS1BqB3CzLtGRznRyvriSY8jeaVk2CrDn2ZiRQgMUI=
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||||
github.com/araddon/dateparse v0.0.0-20201001162425-8aadafed4dc4/go.mod h1:hMAUZFIkk4B1FouGxqlogyMyU6BwY/UiVmmbbzz9Up8=
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
github.com/charmbracelet/glamour v0.2.0 h1:mTgaiNiumpqTZp3qVM6DH9UB0NlbY17wejoMf1kM8Pg=
|
|
||||||
github.com/charmbracelet/glamour v0.2.0/go.mod h1:UA27Kwj3QHialP74iU6C+Gpc8Y7IOAKupeKMLLBURWM=
|
|
||||||
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
|
|
||||||
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
|
||||||
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.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
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 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
|
||||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
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 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
@ -60,198 +59,201 @@ 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.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
|
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||||
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
|
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
|
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
|
||||||
github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI=
|
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
|
||||||
github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
|
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
|
||||||
|
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
|
||||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
|
|
||||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
|
||||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
|
|
||||||
github.com/hashicorp/go-version v1.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/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
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/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||||
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
|
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
|
||||||
github.com/imdario/mergo v0.3.11/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.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
|
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
|
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
|
github.com/kevinburke/ssh_config v1.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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
|
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
|
||||||
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||||
|
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
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 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
|
||||||
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/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
|
github.com/microcosm-cc/bluemonday v1.0.6/go.mod h1:HOT/6NaBlR0f9XlxD3zolN6Z3N8Lp4pvhp+jLS5ihnI=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
github.com/microcosm-cc/bluemonday v1.0.15 h1:J4uN+qPng9rvkBZBoBb8YGR+ijuklIMpSOZZLjYpbeY=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDEq1axMbGg=
|
github.com/microcosm-cc/bluemonday v1.0.15/go.mod h1:ZLvAzeakRwrGnzQEvstVzVt3ZpqOF2+sdFr0Om+ce30=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
|
|
||||||
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.1.0 h1:oQdpLfO56lr5pgLvqD0TcjW85rDjSYSBVdiG1Ch1ddM=
|
|
||||||
github.com/muesli/reflow v0.1.0/go.mod h1:I9bWAt7QTg/que/qmUCJBGlj7wEq8OAFBjPNjc6xK4I=
|
|
||||||
github.com/muesli/reflow v0.2.0 h1:2o0UBJPHHH4fa2GCXU4Rg4DwOtWPMekCeyc5EWbAQp0=
|
|
||||||
github.com/muesli/reflow v0.2.0/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVhUqLX8=
|
github.com/muesli/reflow v0.2.0/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVhUqLX8=
|
||||||
github.com/muesli/termenv v0.6.0 h1:zxvzTBmo4ZcxhNGGWeMz+Tttm51eF5bmPjfy4MCRYlk=
|
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||||
github.com/muesli/termenv v0.6.0/go.mod h1:SohX91w6swWA4AYU+QmPx+aSgXhWO0juiyID9UZmbpA=
|
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||||
github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8=
|
github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0=
|
||||||
github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc=
|
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
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/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
github.com/noerw/glamour v0.3.0-patch h1:yc3wdbUIySok6KYeX5BtWnlj+PvP1uYeCeTSwq2rtSw=
|
||||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
github.com/noerw/glamour v0.3.0-patch/go.mod h1:TzF0koPZhqq0YVBNL100cPHznAAjVj7fksX2RInwjGw=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 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/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||||
|
github.com/seletskiy/tplutil v0.0.0-20200921103632-f880f6245597 h1:nZY1S2jo+VtDrUfjO9XYI137O41hhRkxZNV5Fb5ixCA=
|
||||||
|
github.com/seletskiy/tplutil v0.0.0-20200921103632-f880f6245597/go.mod h1:F8CBHSOjnzjx9EeXyWJTAzJyVxN+Y8JH2WjLMn4utiw=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||||
|
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/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.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 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
|
||||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
|
||||||
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
|
|
||||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
|
||||||
|
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.0 h1:WOOcyaJPlzb8fZ8TloxFe8QZkhOOJx87leDa9MIT9dc=
|
|
||||||
github.com/yuin/goldmark v1.2.0/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.3.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-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/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-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
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.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
|
||||||
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 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf h1:kt3wY1Lu5MJAnKTfoMR52Cu4gwvna4VTzNOiT8tY73s=
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/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.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 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.0.0-20201105220310-78b158585360 h1:/9CzsU8hOpnSUCtem1vfWNgsVeCTgkMdx+VE5YIYxnU=
|
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
||||||
golang.org/x/tools v0.0.0-20201105220310-78b158585360/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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.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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
|
82
main.go
82
main.go
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
@ -6,7 +6,7 @@
|
|||||||
package main // import "code.gitea.io/tea"
|
package main // import "code.gitea.io/tea"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -22,29 +22,40 @@ var Version = "development"
|
|||||||
var Tags = ""
|
var Tags = ""
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// make parsing tea --version easier, by printing /just/ the version string
|
||||||
|
cli.VersionPrinter = func(c *cli.Context) { fmt.Fprintln(c.App.Writer, c.App.Version) }
|
||||||
|
|
||||||
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 = ``
|
app.Description = appDescription
|
||||||
|
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.CmdReleases,
|
|
||||||
&cmd.CmdRepos,
|
|
||||||
&cmd.CmdLabels,
|
&cmd.CmdLabels,
|
||||||
|
&cmd.CmdMilestones,
|
||||||
|
&cmd.CmdReleases,
|
||||||
&cmd.CmdTrackedTimes,
|
&cmd.CmdTrackedTimes,
|
||||||
|
&cmd.CmdOrgs,
|
||||||
|
&cmd.CmdRepos,
|
||||||
|
&cmd.CmdAddComment,
|
||||||
|
|
||||||
&cmd.CmdOpen,
|
&cmd.CmdOpen,
|
||||||
&cmd.CmdNotifications,
|
&cmd.CmdNotifications,
|
||||||
&cmd.CmdMilestones,
|
|
||||||
&cmd.CmdOrgs,
|
|
||||||
}
|
}
|
||||||
app.EnableBashCompletion = true
|
app.EnableBashCompletion = true
|
||||||
err := app.Run(os.Args)
|
err := app.Run(os.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to run app with %s: %v", os.Args, err)
|
// app.Run already exits for errors implementing ErrorCoder,
|
||||||
|
// so we only handle generic errors with code 1 here.
|
||||||
|
fmt.Fprintf(app.ErrWriter, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,3 +66,56 @@ 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,130 +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 (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/git"
|
|
||||||
"code.gitea.io/tea/modules/utils"
|
|
||||||
|
|
||||||
gogit "github.com/go-git/go-git/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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(repoFlag, loginFlag, remoteFlag string) (login *Login, owner string, reponame string) {
|
|
||||||
err := loadConfig()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var repoSlug string
|
|
||||||
var repoPath string // empty means PWD
|
|
||||||
var repoFlagPathExists bool
|
|
||||||
|
|
||||||
// check if repoFlag can be interpreted as path to local repo.
|
|
||||||
if len(repoFlag) != 0 {
|
|
||||||
repoFlagPathExists, err = utils.PathExists(repoFlag)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
if repoFlagPathExists {
|
|
||||||
repoPath = repoFlag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to read git repo & extract context, ignoring if PWD is not a repo
|
|
||||||
login, repoSlug, err = contextFromLocalRepo(repoPath, remoteFlag)
|
|
||||||
if err != nil && err != gogit.ErrRepositoryNotExists {
|
|
||||||
log.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// if repoFlag is not a path, use it to override repoSlug
|
|
||||||
if len(repoFlag) != 0 && !repoFlagPathExists {
|
|
||||||
repoSlug = repoFlag
|
|
||||||
}
|
|
||||||
|
|
||||||
// override login from flag, or use default login if repo based detection failed
|
|
||||||
if len(loginFlag) != 0 {
|
|
||||||
login = GetLoginByName(loginFlag)
|
|
||||||
if login == nil {
|
|
||||||
log.Fatalf("Login name '%s' does not exist", loginFlag)
|
|
||||||
}
|
|
||||||
} else if login == nil {
|
|
||||||
if login, err = GetDefaultLogin(); err != nil {
|
|
||||||
log.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse reposlug (owner falling back to login owner if reposlug contains only repo name)
|
|
||||||
owner, reponame = utils.GetOwnerAndRepo(repoSlug, login.User)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// contextFromLocalRepo discovers login & repo slug from the default branch remote of the given local repo
|
|
||||||
func contextFromLocalRepo(repoValue, remoteValue string) (*Login, string, error) {
|
|
||||||
repo, err := git.RepoFromPath(repoValue)
|
|
||||||
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")
|
|
||||||
}
|
|
@ -142,8 +142,9 @@ func AddLogin(login *Login) error {
|
|||||||
return saveConfig()
|
return saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client returns a client to operate Gitea API
|
// Client returns a client to operate Gitea API. You may provide additional modifiers
|
||||||
func (l *Login) Client() *gitea.Client {
|
// for the client like gitea.SetBasicAuth() for customization
|
||||||
|
func (l *Login) Client(options ...func(*gitea.Client)) *gitea.Client {
|
||||||
httpClient := &http.Client{}
|
httpClient := &http.Client{}
|
||||||
if l.Insecure {
|
if l.Insecure {
|
||||||
cookieJar, _ := cookiejar.New(nil)
|
cookieJar, _ := cookiejar.New(nil)
|
||||||
@ -155,10 +156,9 @@ func (l *Login) Client() *gitea.Client {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := gitea.NewClient(l.URL,
|
options = append(options, gitea.SetToken(l.Token), gitea.SetHTTPClient(httpClient))
|
||||||
gitea.SetToken(l.Token),
|
|
||||||
gitea.SetHTTPClient(httpClient),
|
client, err := gitea.NewClient(l.URL, options...)
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
212
modules/context/context.go
Normal file
212
modules/context/context.go
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
// 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
|
||||||
|
}
|
@ -55,7 +55,7 @@ func readSSHPrivKey(keyFile string, passwordCallback pwCallback) (sig ssh.Signer
|
|||||||
}
|
}
|
||||||
sshKey, err := ioutil.ReadFile(keyFile)
|
sshKey, err := ioutil.ReadFile(keyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("can not read ssh key '%s'", keyFile)
|
||||||
}
|
}
|
||||||
sig, err = ssh.ParsePrivateKey(sshKey)
|
sig, err = ssh.ParsePrivateKey(sshKey)
|
||||||
if _, ok := err.(*ssh.PassphraseMissingError); ok && passwordCallback != nil {
|
if _, ok := err.(*ssh.PassphraseMissingError); ok && passwordCallback != nil {
|
||||||
|
@ -38,42 +38,36 @@ 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(branchName string) error {
|
func (r TeaRepo) TeaCheckout(ref git_plumbing.ReferenceName) error {
|
||||||
tree, err := r.Worktree()
|
tree, err := r.Worktree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
localBranchRefName := git_plumbing.NewBranchReferenceName(branchName)
|
return tree.Checkout(&git.CheckoutOptions{Branch: ref})
|
||||||
return tree.Checkout(&git.CheckoutOptions{Branch: localBranchRefName})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TeaDeleteBranch removes the given branch locally, and if `remoteBranch` is
|
// TeaDeleteLocalBranch removes the given branch locally
|
||||||
// not empty deletes it at it's remote repo.
|
func (r TeaRepo) TeaDeleteLocalBranch(branch *git_config.Branch) error {
|
||||||
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
|
||||||
}
|
}
|
||||||
err = r.Storer.RemoveReference(git_plumbing.NewBranchReferenceName(branch.Name))
|
return r.Storer.RemoveReference(git_plumbing.NewBranchReferenceName(branch.Name))
|
||||||
if err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if remoteBranch != "" {
|
// TeaDeleteRemoteBranch removes the given branch on the given remote via git protocol
|
||||||
|
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
|
||||||
@ -229,5 +223,5 @@ func (r TeaRepo) TeaGetCurrentBranchName() (string, error) {
|
|||||||
return "", fmt.Errorf("active ref is no branch")
|
return "", fmt.Errorf("active ref is no branch")
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimPrefix(localHead.Name().String(), "refs/heads/"), nil
|
return localHead.Name().Short(), nil
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,10 @@ 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
|
||||||
|
80
modules/interact/comments.go
Normal file
80
modules/interact/comments.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// 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()))
|
||||||
|
}
|
162
modules/interact/issue_create.go
Normal file
162
modules/interact/issue_create.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// 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
|
||||||
|
}
|
54
modules/interact/milestone_create.go
Normal file
54
modules/interact/milestone_create.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
@ -5,12 +5,161 @@
|
|||||||
package interact
|
package interact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"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.
|
// PromptPassword asks for a password and blocks until input was made.
|
||||||
func PromptPassword(name string) (pass string, err error) {
|
func PromptPassword(name string) (pass string, err error) {
|
||||||
promptPW := &survey.Password{Message: name + " password:"}
|
promptPW := &survey.Password{Message: name + " password:"}
|
||||||
err = survey.AskOne(promptPW, &pass, survey.WithValidator(survey.Required))
|
err = survey.AskOne(promptPW, &pass, survey.WithValidator(survey.Required))
|
||||||
return
|
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
|
||||||
|
}
|
||||||
|
@ -5,129 +5,60 @@
|
|||||||
package interact
|
package interact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"code.gitea.io/sdk/gitea"
|
||||||
"strings"
|
"code.gitea.io/tea/modules/context"
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/config"
|
|
||||||
"code.gitea.io/tea/modules/git"
|
|
||||||
"code.gitea.io/tea/modules/task"
|
"code.gitea.io/tea/modules/task"
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreatePull interactively creates a PR
|
// CreatePull interactively creates a PR
|
||||||
func CreatePull(login *config.Login, owner, repo string) error {
|
func CreatePull(ctx *context.TeaContext) (err error) {
|
||||||
var base, head, title, description string
|
var base, head string
|
||||||
|
|
||||||
// owner, repo
|
// owner, repo
|
||||||
owner, repo, err := promptRepoSlug(owner, repo)
|
if ctx.Owner, ctx.Repo, err = promptRepoSlug(ctx.Owner, ctx.Repo); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// base
|
// base
|
||||||
baseBranch, err := task.GetDefaultPRBase(login, owner, repo)
|
if base, err = task.GetDefaultPRBase(ctx.Login, ctx.Owner, ctx.Repo); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
promptI := &survey.Input{Message: "Target branch [" + baseBranch + "]:"}
|
promptI := &survey.Input{Message: "Target branch:", Default: base}
|
||||||
if err := survey.AskOne(promptI, &base); err != nil {
|
if err := survey.AskOne(promptI, &base); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(base) == 0 {
|
|
||||||
base = baseBranch
|
|
||||||
}
|
|
||||||
|
|
||||||
// head
|
// head
|
||||||
localRepo, err := git.RepoForWorkdir()
|
var headOwner, headBranch string
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
promptOpts := survey.WithValidator(survey.Required)
|
promptOpts := survey.WithValidator(survey.Required)
|
||||||
headOwner, headBranch, err := task.GetDefaultPRHead(localRepo)
|
|
||||||
|
if ctx.LocalRepo != nil {
|
||||||
|
headOwner, headBranch, err = task.GetDefaultPRHead(ctx.LocalRepo)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
promptOpts = nil
|
promptOpts = nil
|
||||||
}
|
}
|
||||||
var headOwnerInput, headBranchInput string
|
}
|
||||||
promptI = &survey.Input{Message: "Source repo owner [" + headOwner + "]:"}
|
promptI = &survey.Input{Message: "Source repo owner:", Default: headOwner}
|
||||||
if err := survey.AskOne(promptI, &headOwnerInput); err != nil {
|
if err := survey.AskOne(promptI, &headOwner); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(headOwnerInput) != 0 {
|
promptI = &survey.Input{Message: "Source branch:", Default: headBranch}
|
||||||
headOwner = headOwnerInput
|
if err := survey.AskOne(promptI, &headBranch, promptOpts); err != nil {
|
||||||
}
|
|
||||||
promptI = &survey.Input{Message: "Source branch [" + headBranch + "]:"}
|
|
||||||
if err := survey.AskOne(promptI, &headBranchInput, promptOpts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(headBranchInput) != 0 {
|
|
||||||
headBranch = headBranchInput
|
|
||||||
}
|
|
||||||
|
|
||||||
head = task.GetHeadSpec(headOwner, headBranch, owner)
|
|
||||||
|
|
||||||
// title
|
|
||||||
title = task.GetDefaultPRTitle(head)
|
|
||||||
promptOpts = survey.WithValidator(survey.Required)
|
|
||||||
if len(title) != 0 {
|
|
||||||
promptOpts = nil
|
|
||||||
}
|
|
||||||
promptI = &survey.Input{Message: "PR title [" + title + "]:"}
|
|
||||||
if err := survey.AskOne(promptI, &title, promptOpts); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// description
|
head = task.GetHeadSpec(headOwner, headBranch, ctx.Owner)
|
||||||
promptM := &survey.Multiline{Message: "PR description:"}
|
|
||||||
if err := survey.AskOne(promptM, &description); err != nil {
|
opts := gitea.CreateIssueOption{Title: task.GetDefaultPRTitle(head)}
|
||||||
|
if err = promptIssueProperties(ctx.Login, ctx.Owner, ctx.Repo, &opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return task.CreatePull(
|
return task.CreatePull(
|
||||||
login,
|
ctx,
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
base,
|
base,
|
||||||
head,
|
head,
|
||||||
title,
|
&opts)
|
||||||
description)
|
|
||||||
}
|
|
||||||
|
|
||||||
func promptRepoSlug(defaultOwner, defaultRepo string) (owner, repo string, err error) {
|
|
||||||
prompt := "Target repo:"
|
|
||||||
required := true
|
|
||||||
if len(defaultOwner) != 0 && len(defaultRepo) != 0 {
|
|
||||||
prompt = fmt.Sprintf("Target repo [%s/%s]:", defaultOwner, defaultRepo)
|
|
||||||
required = false
|
|
||||||
}
|
|
||||||
var repoSlug string
|
|
||||||
|
|
||||||
owner = defaultOwner
|
|
||||||
repo = defaultRepo
|
|
||||||
|
|
||||||
err = survey.AskOne(
|
|
||||||
&survey.Input{Message: prompt},
|
|
||||||
&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
|
|
||||||
}
|
}
|
||||||
|
80
modules/interact/pull_review.go
Normal file
80
modules/interact/pull_review.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
50
modules/print/comment.go
Normal file
50
modules/print/comment.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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,
|
||||||
|
)
|
||||||
|
}
|
74
modules/print/formatters.go
Normal file
74
modules/print/formatters.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// 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
|
||||||
|
}
|
@ -6,7 +6,7 @@ package print
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
)
|
)
|
||||||
@ -21,71 +21,109 @@ func IssueDetails(issue *gitea.Issue) {
|
|||||||
issue.Poster.UserName,
|
issue.Poster.UserName,
|
||||||
FormatTime(issue.Created),
|
FormatTime(issue.Created),
|
||||||
issue.Body,
|
issue.Body,
|
||||||
))
|
), issue.HTMLURL)
|
||||||
}
|
|
||||||
|
|
||||||
// IssuesList prints a listing of issues
|
|
||||||
func IssuesList(issues []*gitea.Issue, output string) {
|
|
||||||
t := tableWithHeader(
|
|
||||||
"Index",
|
|
||||||
"Title",
|
|
||||||
"State",
|
|
||||||
"Author",
|
|
||||||
"Milestone",
|
|
||||||
"Updated",
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, issue := range issues {
|
|
||||||
author := issue.Poster.FullName
|
|
||||||
if len(author) == 0 {
|
|
||||||
author = issue.Poster.UserName
|
|
||||||
}
|
|
||||||
mile := ""
|
|
||||||
if issue.Milestone != nil {
|
|
||||||
mile = issue.Milestone.Title
|
|
||||||
}
|
|
||||||
t.addRow(
|
|
||||||
strconv.FormatInt(issue.Index, 10),
|
|
||||||
issue.Title,
|
|
||||||
string(issue.State),
|
|
||||||
author,
|
|
||||||
mile,
|
|
||||||
FormatTime(issue.Updated),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
t.print(output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IssuesPullsList prints a listing of issues & pulls
|
// IssuesPullsList prints a listing of issues & pulls
|
||||||
// TODO combine with IssuesList
|
func IssuesPullsList(issues []*gitea.Issue, output string, fields []string) {
|
||||||
func IssuesPullsList(issues []*gitea.Issue, output string) {
|
printIssues(issues, output, fields)
|
||||||
t := tableWithHeader(
|
}
|
||||||
"Index",
|
|
||||||
"State",
|
|
||||||
"Kind",
|
|
||||||
"Author",
|
|
||||||
"Updated",
|
|
||||||
"Title",
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, issue := range issues {
|
// IssueFields are all available fields to print with IssuesList()
|
||||||
name := issue.Poster.FullName
|
var IssueFields = []string{
|
||||||
if len(name) == 0 {
|
"index",
|
||||||
name = issue.Poster.UserName
|
"state",
|
||||||
|
"kind",
|
||||||
|
"author",
|
||||||
|
"author-id",
|
||||||
|
"url",
|
||||||
|
|
||||||
|
"title",
|
||||||
|
"body",
|
||||||
|
|
||||||
|
"created",
|
||||||
|
"updated",
|
||||||
|
"deadline",
|
||||||
|
|
||||||
|
"assignees",
|
||||||
|
"milestone",
|
||||||
|
"labels",
|
||||||
|
"comments",
|
||||||
|
}
|
||||||
|
|
||||||
|
func printIssues(issues []*gitea.Issue, output string, fields []string) {
|
||||||
|
labelMap := map[int64]string{}
|
||||||
|
var printables = make([]printable, len(issues))
|
||||||
|
|
||||||
|
for i, x := range issues {
|
||||||
|
// pre-serialize labels for performance
|
||||||
|
for _, label := range x.Labels {
|
||||||
|
if _, ok := labelMap[label.ID]; !ok {
|
||||||
|
labelMap[label.ID] = formatLabel(label, !isMachineReadable(output), "")
|
||||||
}
|
}
|
||||||
kind := "Issue"
|
|
||||||
if issue.PullRequest != nil {
|
|
||||||
kind = "Pull"
|
|
||||||
}
|
}
|
||||||
t.addRow(
|
// store items with printable interface
|
||||||
strconv.FormatInt(issue.Index, 10),
|
printables[i] = &printableIssue{x, &labelMap}
|
||||||
string(issue.State),
|
|
||||||
kind,
|
|
||||||
name,
|
|
||||||
FormatTime(issue.Updated),
|
|
||||||
issue.Title,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t := tableFromItems(fields, printables)
|
||||||
t.print(output)
|
t.print(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type printableIssue struct {
|
||||||
|
*gitea.Issue
|
||||||
|
formattedLabels *map[int64]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x printableIssue) FormatField(field string) string {
|
||||||
|
switch field {
|
||||||
|
case "index":
|
||||||
|
return fmt.Sprintf("%d", x.Index)
|
||||||
|
case "state":
|
||||||
|
return string(x.State)
|
||||||
|
case "kind":
|
||||||
|
if x.PullRequest != nil {
|
||||||
|
return "Pull"
|
||||||
|
}
|
||||||
|
return "Issue"
|
||||||
|
case "author":
|
||||||
|
return formatUserName(x.Poster)
|
||||||
|
case "author-id":
|
||||||
|
return x.Poster.UserName
|
||||||
|
case "url":
|
||||||
|
return x.HTMLURL
|
||||||
|
case "title":
|
||||||
|
return x.Title
|
||||||
|
case "body":
|
||||||
|
return x.Body
|
||||||
|
case "created":
|
||||||
|
return FormatTime(x.Created)
|
||||||
|
case "updated":
|
||||||
|
return FormatTime(x.Updated)
|
||||||
|
case "deadline":
|
||||||
|
if x.Deadline == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return FormatTime(*x.Deadline)
|
||||||
|
case "milestone":
|
||||||
|
if x.Milestone != nil {
|
||||||
|
return x.Milestone.Title
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
case "labels":
|
||||||
|
var labels = make([]string, len(x.Labels))
|
||||||
|
for i, l := range x.Labels {
|
||||||
|
labels[i] = (*x.formattedLabels)[l.ID]
|
||||||
|
}
|
||||||
|
return strings.Join(labels, " ")
|
||||||
|
case "assignees":
|
||||||
|
var assignees = make([]string, len(x.Assignees))
|
||||||
|
for i, a := range x.Assignees {
|
||||||
|
assignees[i] = formatUserName(a)
|
||||||
|
}
|
||||||
|
return strings.Join(assignees, " ")
|
||||||
|
case "comments":
|
||||||
|
return fmt.Sprintf("%d", x.Comments)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
@ -5,11 +5,9 @@
|
|||||||
package print
|
package print
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/muesli/termenv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LabelsList prints a listing of labels
|
// LabelsList prints a listing of labels
|
||||||
@ -21,14 +19,10 @@ func LabelsList(labels []*gitea.Label, output string) {
|
|||||||
"Description",
|
"Description",
|
||||||
)
|
)
|
||||||
|
|
||||||
p := termenv.ColorProfile()
|
|
||||||
|
|
||||||
for _, label := range labels {
|
for _, label := range labels {
|
||||||
color := termenv.String(label.Color)
|
|
||||||
|
|
||||||
t.addRow(
|
t.addRow(
|
||||||
strconv.FormatInt(label.ID, 10),
|
strconv.FormatInt(label.ID, 10),
|
||||||
fmt.Sprint(color.Background(p.Color("#"+label.Color))),
|
formatLabel(label, !isMachineReadable(output), label.Color),
|
||||||
label.Name,
|
label.Name,
|
||||||
label.Description,
|
label.Description,
|
||||||
)
|
)
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// LoginDetails print login entry to stdout
|
// LoginDetails print login entry to stdout
|
||||||
func LoginDetails(login *config.Login, output string) {
|
func LoginDetails(login *config.Login) {
|
||||||
in := fmt.Sprintf("# %s\n\n[@%s](%s/%s)\n",
|
in := fmt.Sprintf("# %s\n\n[@%s](%s/%s)\n",
|
||||||
login.Name,
|
login.Name,
|
||||||
login.User,
|
login.User,
|
||||||
@ -28,7 +28,7 @@ func LoginDetails(login *config.Login, output string) {
|
|||||||
}
|
}
|
||||||
in += fmt.Sprintf("\nCreated: %s", time.Unix(login.Created, 0).Format(time.RFC822))
|
in += fmt.Sprintf("\nCreated: %s", time.Unix(login.Created, 0).Format(time.RFC822))
|
||||||
|
|
||||||
outputMarkdown(in)
|
outputMarkdown(in, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginsList prints a listing of logins
|
// LoginsList prints a listing of logins
|
||||||
|
@ -6,15 +6,27 @@ package print
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/charmbracelet/glamour"
|
"github.com/charmbracelet/glamour"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// outputMarkdown prints markdown to stdout, formatted for terminals.
|
// outputMarkdown prints markdown to stdout, formatted for terminals.
|
||||||
// If the input could not be parsed, it is printed unformatted, the error
|
// If the input could not be parsed, it is printed unformatted, the error
|
||||||
// is returned anyway.
|
// is returned anyway.
|
||||||
func outputMarkdown(markdown string) error {
|
func outputMarkdown(markdown string, baseURL string) error {
|
||||||
out, err := glamour.Render(markdown, "auto")
|
renderer, err := glamour.NewTermRenderer(
|
||||||
|
glamour.WithAutoStyle(),
|
||||||
|
glamour.WithBaseURL(baseURL),
|
||||||
|
glamour.WithWordWrap(getWordWrap()),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf(markdown)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := renderer.Render(markdown)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(markdown)
|
fmt.Printf(markdown)
|
||||||
return err
|
return err
|
||||||
@ -22,3 +34,18 @@ func outputMarkdown(markdown string) error {
|
|||||||
fmt.Print(out)
|
fmt.Print(out)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stolen from https://github.com/charmbracelet/glow/blob/e9d728c/main.go#L152-L165
|
||||||
|
func getWordWrap() int {
|
||||||
|
fd := int(os.Stdout.Fd())
|
||||||
|
width := 80
|
||||||
|
if terminal.IsTerminal(fd) {
|
||||||
|
if w, _, err := terminal.GetSize(fd); err == nil {
|
||||||
|
width = w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if width > 120 {
|
||||||
|
width = 120
|
||||||
|
}
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package print
|
package print
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
@ -13,7 +14,10 @@ import (
|
|||||||
// NotificationsList prints a listing of notification threads
|
// NotificationsList prints a listing of notification threads
|
||||||
func NotificationsList(news []*gitea.NotificationThread, output string, showRepository bool) {
|
func NotificationsList(news []*gitea.NotificationThread, output string, showRepository bool) {
|
||||||
headers := []string{
|
headers := []string{
|
||||||
|
"ID",
|
||||||
|
"Status",
|
||||||
"Type",
|
"Type",
|
||||||
|
"State",
|
||||||
"Index",
|
"Index",
|
||||||
"Title",
|
"Title",
|
||||||
}
|
}
|
||||||
@ -38,7 +42,21 @@ func NotificationsList(news []*gitea.NotificationThread, output string, showRepo
|
|||||||
index = "#" + index
|
index = "#" + index
|
||||||
}
|
}
|
||||||
|
|
||||||
item := []string{n.Subject.Type, index, n.Subject.Title}
|
status := "read"
|
||||||
|
if n.Pinned {
|
||||||
|
status = "pinned"
|
||||||
|
} else if n.Unread {
|
||||||
|
status = "unread"
|
||||||
|
}
|
||||||
|
|
||||||
|
item := []string{
|
||||||
|
fmt.Sprint(n.ID),
|
||||||
|
status,
|
||||||
|
string(n.Subject.Type),
|
||||||
|
string(n.Subject.State),
|
||||||
|
index,
|
||||||
|
n.Subject.Title,
|
||||||
|
}
|
||||||
if showRepository {
|
if showRepository {
|
||||||
item = append(item, n.Repository.FullName)
|
item = append(item, n.Repository.FullName)
|
||||||
}
|
}
|
||||||
|
@ -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 print
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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")
|
|
||||||
}
|
|
@ -7,12 +7,21 @@ package print
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ciStatusSymbols = map[gitea.StatusState]string{
|
||||||
|
gitea.StatusSuccess: "✓ ",
|
||||||
|
gitea.StatusPending: "⭮ ",
|
||||||
|
gitea.StatusWarning: "⚠ ",
|
||||||
|
gitea.StatusError: "✘ ",
|
||||||
|
gitea.StatusFailure: "❌ ",
|
||||||
|
}
|
||||||
|
|
||||||
// PullDetails print an pull rendered to stdout
|
// PullDetails print an pull rendered to stdout
|
||||||
func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview) {
|
func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview, ciStatus *gitea.CombinedStatus) {
|
||||||
base := pr.Base.Name
|
base := pr.Base.Name
|
||||||
head := pr.Head.Name
|
head := pr.Head.Name
|
||||||
if pr.Head.RepoID != pr.Base.RepoID {
|
if pr.Head.RepoID != pr.Base.RepoID {
|
||||||
@ -23,11 +32,16 @@ func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state := pr.State
|
||||||
|
if pr.Merged != nil {
|
||||||
|
state = "merged"
|
||||||
|
}
|
||||||
|
|
||||||
out := fmt.Sprintf(
|
out := fmt.Sprintf(
|
||||||
"# #%d %s (%s)\n@%s created %s\t**%s** <- **%s**\n\n%s\n",
|
"# #%d %s (%s)\n@%s created %s\t**%s** <- **%s**\n\n%s\n\n",
|
||||||
pr.Index,
|
pr.Index,
|
||||||
pr.Title,
|
pr.Title,
|
||||||
pr.State,
|
state,
|
||||||
pr.Poster.UserName,
|
pr.Poster.UserName,
|
||||||
FormatTime(*pr.Created),
|
FormatTime(*pr.Created),
|
||||||
base,
|
base,
|
||||||
@ -35,27 +49,68 @@ func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview) {
|
|||||||
pr.Body,
|
pr.Body,
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(reviews) != 0 {
|
if ciStatus != nil || len(reviews) != 0 || pr.State == gitea.StateOpen {
|
||||||
out += "\n"
|
out += "---\n"
|
||||||
revMap := make(map[string]gitea.ReviewStateType)
|
}
|
||||||
|
|
||||||
|
out += formatReviews(reviews)
|
||||||
|
|
||||||
|
if ciStatus != nil {
|
||||||
|
var summary, errors string
|
||||||
|
for _, s := range ciStatus.Statuses {
|
||||||
|
summary += ciStatusSymbols[s.State]
|
||||||
|
if s.State != gitea.StatusSuccess {
|
||||||
|
errors += fmt.Sprintf(" - [**%s**:\t%s](%s)\n", s.Context, s.Description, s.TargetURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ciStatus.Statuses) != 0 {
|
||||||
|
out += fmt.Sprintf("- CI: %s\n%s", summary, errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pr.State == gitea.StateOpen {
|
||||||
|
if pr.Mergeable {
|
||||||
|
out += "- No Conflicts\n"
|
||||||
|
} else {
|
||||||
|
out += "- **Conflicting files**\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputMarkdown(out, pr.HTMLURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatReviews(reviews []*gitea.PullReview) string {
|
||||||
|
result := ""
|
||||||
|
if len(reviews) == 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// deduplicate reviews by user (via review time & userID),
|
||||||
|
reviewByUser := make(map[int64]*gitea.PullReview)
|
||||||
for _, review := range reviews {
|
for _, review := range reviews {
|
||||||
switch review.State {
|
switch review.State {
|
||||||
case gitea.ReviewStateApproved,
|
case gitea.ReviewStateApproved,
|
||||||
gitea.ReviewStateRequestChanges,
|
gitea.ReviewStateRequestChanges,
|
||||||
gitea.ReviewStateRequestReview:
|
gitea.ReviewStateRequestReview:
|
||||||
revMap[review.Reviewer.UserName] = review.State
|
if r, ok := reviewByUser[review.Reviewer.ID]; !ok || review.Submitted.After(r.Submitted) {
|
||||||
|
reviewByUser[review.Reviewer.ID] = review
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for k, v := range revMap {
|
|
||||||
out += fmt.Sprintf("\n @%s: %s", k, v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pr.State == gitea.StateOpen && pr.Mergeable {
|
// group reviews by type
|
||||||
out += "\nNo Conflicts"
|
usersByState := make(map[gitea.ReviewStateType][]string)
|
||||||
|
for _, r := range reviewByUser {
|
||||||
|
u := r.Reviewer.UserName
|
||||||
|
users := usersByState[r.State]
|
||||||
|
usersByState[r.State] = append(users, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
outputMarkdown(out)
|
// stringify
|
||||||
|
for state, user := range usersByState {
|
||||||
|
result += fmt.Sprintf("- %s by @%s\n", state, strings.Join(user, ", @"))
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// PullsList prints a listing of pulls
|
// PullsList prints a listing of pulls
|
||||||
|
@ -6,91 +6,19 @@ package print
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
)
|
)
|
||||||
|
|
||||||
type rp = *gitea.Repository
|
|
||||||
type fieldFormatter = func(*gitea.Repository) string
|
|
||||||
|
|
||||||
var (
|
|
||||||
fieldFormatters map[string]fieldFormatter
|
|
||||||
|
|
||||||
// RepoFields are the available fields to print with ReposList()
|
|
||||||
RepoFields []string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
fieldFormatters = map[string]fieldFormatter{
|
|
||||||
"description": func(r rp) string { return r.Description },
|
|
||||||
"forks": func(r rp) string { return fmt.Sprintf("%d", r.Forks) },
|
|
||||||
"id": func(r rp) string { return r.FullName },
|
|
||||||
"name": func(r rp) string { return r.Name },
|
|
||||||
"owner": func(r rp) string { return r.Owner.UserName },
|
|
||||||
"stars": func(r rp) string { return fmt.Sprintf("%d", r.Stars) },
|
|
||||||
"ssh": func(r rp) string { return r.SSHURL },
|
|
||||||
"updated": func(r rp) string { return FormatTime(r.Updated) },
|
|
||||||
"url": func(r rp) string { return r.HTMLURL },
|
|
||||||
"permission": func(r rp) string {
|
|
||||||
if r.Permissions.Admin {
|
|
||||||
return "admin"
|
|
||||||
} else if r.Permissions.Push {
|
|
||||||
return "write"
|
|
||||||
}
|
|
||||||
return "read"
|
|
||||||
},
|
|
||||||
"type": func(r rp) string {
|
|
||||||
if r.Fork {
|
|
||||||
return "fork"
|
|
||||||
}
|
|
||||||
if r.Mirror {
|
|
||||||
return "mirror"
|
|
||||||
}
|
|
||||||
return "source"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for f := range fieldFormatters {
|
|
||||||
RepoFields = append(RepoFields, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReposList prints a listing of the repos
|
// ReposList prints a listing of the repos
|
||||||
func ReposList(repos []*gitea.Repository, output string, fields []string) {
|
func ReposList(repos []*gitea.Repository, output string, fields []string) {
|
||||||
if len(repos) == 0 {
|
var printables = make([]printable, len(repos))
|
||||||
fmt.Println("No repositories found")
|
for i, r := range repos {
|
||||||
return
|
printables[i] = &printableRepo{r}
|
||||||
}
|
}
|
||||||
|
t := tableFromItems(fields, printables)
|
||||||
if len(fields) == 0 {
|
|
||||||
fmt.Println("No fields to print")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
formatters := make([]fieldFormatter, len(fields))
|
|
||||||
values := make([][]string, len(repos))
|
|
||||||
|
|
||||||
// find field format functions by header name
|
|
||||||
for i, f := range fields {
|
|
||||||
if formatter, ok := fieldFormatters[strings.ToLower(f)]; ok {
|
|
||||||
formatters[i] = formatter
|
|
||||||
} else {
|
|
||||||
log.Fatalf("invalid field '%s'", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract values from each repo and store them in 2D table
|
|
||||||
for i, repo := range repos {
|
|
||||||
values[i] = make([]string, len(formatters))
|
|
||||||
for j, format := range formatters {
|
|
||||||
values[i][j] = format(repo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t := table{headers: fields, values: values}
|
|
||||||
t.print(output)
|
t.print(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +70,7 @@ func RepoDetails(repo *gitea.Repository, topics []string) {
|
|||||||
|
|
||||||
perm := fmt.Sprintf(
|
perm := fmt.Sprintf(
|
||||||
"- Permission:\t%s\n",
|
"- Permission:\t%s\n",
|
||||||
fieldFormatters["permission"](repo),
|
formatPermission(repo.Permissions),
|
||||||
)
|
)
|
||||||
|
|
||||||
var tops string
|
var tops string
|
||||||
@ -159,5 +87,56 @@ func RepoDetails(repo *gitea.Repository, topics []string) {
|
|||||||
urls,
|
urls,
|
||||||
perm,
|
perm,
|
||||||
tops,
|
tops,
|
||||||
))
|
), repo.HTMLURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepoFields are the available fields to print with ReposList()
|
||||||
|
var RepoFields = []string{
|
||||||
|
"description",
|
||||||
|
"forks",
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"owner",
|
||||||
|
"stars",
|
||||||
|
"ssh",
|
||||||
|
"updated",
|
||||||
|
"url",
|
||||||
|
"permission",
|
||||||
|
"type",
|
||||||
|
}
|
||||||
|
|
||||||
|
type printableRepo struct{ *gitea.Repository }
|
||||||
|
|
||||||
|
func (x printableRepo) FormatField(field string) string {
|
||||||
|
switch field {
|
||||||
|
case "description":
|
||||||
|
return x.Description
|
||||||
|
case "forks":
|
||||||
|
return fmt.Sprintf("%d", x.Forks)
|
||||||
|
case "id":
|
||||||
|
return x.FullName
|
||||||
|
case "name":
|
||||||
|
return x.Name
|
||||||
|
case "owner":
|
||||||
|
return x.Owner.UserName
|
||||||
|
case "stars":
|
||||||
|
return fmt.Sprintf("%d", x.Stars)
|
||||||
|
case "ssh":
|
||||||
|
return x.SSHURL
|
||||||
|
case "updated":
|
||||||
|
return FormatTime(x.Updated)
|
||||||
|
case "url":
|
||||||
|
return x.HTMLURL
|
||||||
|
case "permission":
|
||||||
|
return formatPermission(x.Permissions)
|
||||||
|
case "type":
|
||||||
|
if x.Fork {
|
||||||
|
return "fork"
|
||||||
|
}
|
||||||
|
if x.Mirror {
|
||||||
|
return "mirror"
|
||||||
|
}
|
||||||
|
return "source"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,24 @@ type table struct {
|
|||||||
sortColumn uint // ↑
|
sortColumn uint // ↑
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// printable can be implemented for structs to put fields dynamically into a table
|
||||||
|
type printable interface {
|
||||||
|
FormatField(field string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// high level api to print a table of items with dynamic fields
|
||||||
|
func tableFromItems(fields []string, values []printable) table {
|
||||||
|
t := table{headers: fields}
|
||||||
|
for _, v := range values {
|
||||||
|
row := make([]string, len(fields))
|
||||||
|
for i, f := range fields {
|
||||||
|
row[i] = v.FormatField(f)
|
||||||
|
}
|
||||||
|
t.addRowSlice(row)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
func tableWithHeader(header ...string) table {
|
func tableWithHeader(header ...string) table {
|
||||||
return table{headers: header}
|
return table{headers: header}
|
||||||
}
|
}
|
||||||
@ -54,16 +72,16 @@ func (t table) Less(i, j int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *table) print(output string) {
|
func (t *table) print(output string) {
|
||||||
switch {
|
switch output {
|
||||||
case output == "" || output == "table":
|
case "", "table":
|
||||||
outputtable(t.headers, t.values)
|
outputtable(t.headers, t.values)
|
||||||
case output == "csv":
|
case "csv":
|
||||||
outputdsv(t.headers, t.values, ",")
|
outputdsv(t.headers, t.values, ",")
|
||||||
case output == "simple":
|
case "simple":
|
||||||
outputsimple(t.headers, t.values)
|
outputsimple(t.headers, t.values)
|
||||||
case output == "tsv":
|
case "tsv":
|
||||||
outputdsv(t.headers, t.values, "\t")
|
outputdsv(t.headers, t.values, "\t")
|
||||||
case output == "yaml":
|
case "yml", "yaml":
|
||||||
outputyaml(t.headers, t.values)
|
outputyaml(t.headers, t.values)
|
||||||
default:
|
default:
|
||||||
fmt.Printf("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")
|
fmt.Printf("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")
|
||||||
@ -119,3 +137,11 @@ func outputyaml(headers []string, values [][]string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isMachineReadable(outputFormat string) bool {
|
||||||
|
switch outputFormat {
|
||||||
|
case "yml", "yaml", "csv":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -6,50 +6,59 @@ package print
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
)
|
)
|
||||||
|
|
||||||
func formatDuration(seconds int64, outputType string) string {
|
|
||||||
switch outputType {
|
|
||||||
case "yaml":
|
|
||||||
case "csv":
|
|
||||||
return fmt.Sprint(seconds)
|
|
||||||
}
|
|
||||||
return time.Duration(1e9 * seconds).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TrackedTimesList print list of tracked times to stdout
|
// TrackedTimesList print list of tracked times to stdout
|
||||||
func TrackedTimesList(times []*gitea.TrackedTime, outputType string, from, until time.Time, printTotal bool) {
|
func TrackedTimesList(times []*gitea.TrackedTime, outputType string, fields []string, printTotal bool) {
|
||||||
tab := tableWithHeader(
|
var printables = make([]printable, len(times))
|
||||||
"Created",
|
|
||||||
"Issue",
|
|
||||||
"User",
|
|
||||||
"Duration",
|
|
||||||
)
|
|
||||||
var totalDuration int64
|
var totalDuration int64
|
||||||
|
for i, t := range times {
|
||||||
for _, t := range times {
|
|
||||||
if !from.IsZero() && from.After(t.Created) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !until.IsZero() && until.Before(t.Created) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
totalDuration += t.Time
|
totalDuration += t.Time
|
||||||
tab.addRow(
|
printables[i] = &printableTrackedTime{t, outputType}
|
||||||
FormatTime(t.Created),
|
|
||||||
"#"+strconv.FormatInt(t.Issue.Index, 10),
|
|
||||||
t.UserName,
|
|
||||||
formatDuration(t.Time, outputType),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
t := tableFromItems(fields, printables)
|
||||||
|
|
||||||
if printTotal {
|
if printTotal {
|
||||||
tab.addRow("TOTAL", "", "", formatDuration(totalDuration, outputType))
|
total := make([]string, len(fields))
|
||||||
|
total[0] = "TOTAL"
|
||||||
|
total[len(fields)-1] = formatDuration(totalDuration, outputType)
|
||||||
|
t.addRowSlice(total)
|
||||||
}
|
}
|
||||||
tab.print(outputType)
|
|
||||||
|
t.print(outputType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackedTimeFields contains all available fields for printing of tracked times.
|
||||||
|
var TrackedTimeFields = []string{
|
||||||
|
"id",
|
||||||
|
"created",
|
||||||
|
"repo",
|
||||||
|
"issue",
|
||||||
|
"user",
|
||||||
|
"duration",
|
||||||
|
}
|
||||||
|
|
||||||
|
type printableTrackedTime struct {
|
||||||
|
*gitea.TrackedTime
|
||||||
|
outputFormat string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t printableTrackedTime) FormatField(field string) string {
|
||||||
|
switch field {
|
||||||
|
case "id":
|
||||||
|
return fmt.Sprintf("%d", t.ID)
|
||||||
|
case "created":
|
||||||
|
return FormatTime(t.Created)
|
||||||
|
case "repo":
|
||||||
|
return t.Issue.Repository.FullName
|
||||||
|
case "issue":
|
||||||
|
return fmt.Sprintf("#%d", t.Issue.Index)
|
||||||
|
case "user":
|
||||||
|
return t.UserName
|
||||||
|
case "duration":
|
||||||
|
return formatDuration(t.Time, t.outputFormat)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
33
modules/task/issue_create.go
Normal file
33
modules/task/issue_create.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// 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 task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"code.gitea.io/tea/modules/config"
|
||||||
|
"code.gitea.io/tea/modules/print"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateIssue creates an issue in the given repo and prints the result
|
||||||
|
func CreateIssue(login *config.Login, repoOwner, repoName string, opts gitea.CreateIssueOption) error {
|
||||||
|
|
||||||
|
// title is required
|
||||||
|
if len(opts.Title) == 0 {
|
||||||
|
return fmt.Errorf("Title is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, _, err := login.Client().CreateIssue(repoOwner, repoName, opts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create issue: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
print.IssueDetails(issue)
|
||||||
|
|
||||||
|
fmt.Println(issue.HTMLURL)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
25
modules/task/labels.go
Normal file
25
modules/task/labels.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 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 task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResolveLabelNames returns a list of label IDs for a given list of label names
|
||||||
|
func ResolveLabelNames(client *gitea.Client, owner, repo string, labelNames []string) ([]int64, error) {
|
||||||
|
labelIDs := make([]int64, len(labelNames))
|
||||||
|
labels, _, err := client.ListRepoLabels(owner, repo, gitea.ListLabelsOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, l := range labels {
|
||||||
|
if utils.Contains(labelNames, l.Name) {
|
||||||
|
labelIDs = append(labelIDs, l.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return labelIDs, nil
|
||||||
|
}
|
@ -6,7 +6,6 @@ package task
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
@ -16,7 +15,7 @@ import (
|
|||||||
func LabelsExport(labels []*gitea.Label, path string) error {
|
func LabelsExport(labels []*gitea.Label, path string) error {
|
||||||
f, err := os.Create(path)
|
f, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ package task
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -21,7 +20,7 @@ func CreateLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bo
|
|||||||
// checks ...
|
// checks ...
|
||||||
// ... if we have a url
|
// ... if we have a url
|
||||||
if len(giteaURL) == 0 {
|
if len(giteaURL) == 0 {
|
||||||
log.Fatal("You have to input Gitea server URL")
|
return fmt.Errorf("You have to input Gitea server URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... if there already exist a login with same name
|
// ... if there already exist a login with same name
|
||||||
@ -35,17 +34,17 @@ func CreateLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bo
|
|||||||
|
|
||||||
// .. if we have enough information to authenticate
|
// .. if we have enough information to authenticate
|
||||||
if len(token) == 0 && (len(user)+len(passwd)) == 0 {
|
if len(token) == 0 && (len(user)+len(passwd)) == 0 {
|
||||||
log.Fatal("No token set")
|
return fmt.Errorf("No token set")
|
||||||
} else if len(user) != 0 && len(passwd) == 0 {
|
} else if len(user) != 0 && len(passwd) == 0 {
|
||||||
log.Fatal("No password set")
|
return fmt.Errorf("No password set")
|
||||||
} else if len(user) == 0 && len(passwd) != 0 {
|
} else if len(user) == 0 && len(passwd) != 0 {
|
||||||
log.Fatal("No user set")
|
return fmt.Errorf("No user set")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize URL
|
// Normalize URL
|
||||||
serverURL, err := utils.NormalizeURL(giteaURL)
|
serverURL, err := utils.NormalizeURL(giteaURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to parse URL", err)
|
return fmt.Errorf("Unable to parse URL: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
login := config.Login{
|
login := config.Login{
|
||||||
@ -57,26 +56,24 @@ func CreateLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bo
|
|||||||
Created: time.Now().Unix(),
|
Created: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
client := login.Client()
|
|
||||||
|
|
||||||
if len(token) == 0 {
|
if len(token) == 0 {
|
||||||
login.Token, err = generateToken(client, user, passwd)
|
if login.Token, err = generateToken(login, user, passwd); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client := login.Client()
|
||||||
|
|
||||||
// Verify if authentication works and get user info
|
// Verify if authentication works and get user info
|
||||||
u, _, err := client.GetMyUserInfo()
|
u, _, err := client.GetMyUserInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
login.User = u.UserName
|
login.User = u.UserName
|
||||||
|
|
||||||
if len(login.Name) == 0 {
|
if len(login.Name) == 0 {
|
||||||
login.Name, err = GenerateLoginName(giteaURL, login.User)
|
if login.Name, err = GenerateLoginName(giteaURL, login.User); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,9 +88,8 @@ func CreateLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = config.AddLogin(&login)
|
if err = config.AddLogin(&login); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Login as %s on %s successful. Added this login as %s\n", login.User, login.URL, login.Name)
|
fmt.Printf("Login as %s on %s successful. Added this login as %s\n", login.User, login.URL, login.Name)
|
||||||
@ -102,16 +98,17 @@ func CreateLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generateToken creates a new token when given BasicAuth credentials
|
// generateToken creates a new token when given BasicAuth credentials
|
||||||
func generateToken(client *gitea.Client, user, pass string) (string, error) {
|
func generateToken(login config.Login, user, pass string) (string, error) {
|
||||||
gitea.SetBasicAuth(user, pass)(client)
|
client := login.Client(gitea.SetBasicAuth(user, pass))
|
||||||
|
|
||||||
host, _ := os.Hostname()
|
|
||||||
tl, _, err := client.ListAccessTokens(gitea.ListAccessTokensOptions{})
|
tl, _, err := client.ListAccessTokens(gitea.ListAccessTokensOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
host, _ := os.Hostname()
|
||||||
tokenName := host + "-tea"
|
tokenName := host + "-tea"
|
||||||
|
|
||||||
|
// append timestamp, if a token with this hostname already exists
|
||||||
for i := range tl {
|
for i := range tl {
|
||||||
if tl[i].Name == tokenName {
|
if tl[i].Name == tokenName {
|
||||||
tokenName += time.Now().Format("2006-01-02_15-04-05")
|
tokenName += time.Now().Format("2006-01-02_15-04-05")
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user