mirror of
https://gitea.com/gitea/tea.git
synced 2025-09-19 10:12:54 +02:00
Compare commits
113 Commits
Author | SHA1 | Date | |
---|---|---|---|
1b4487e6c9 | |||
b10d792687 | |||
5a41c79d7d | |||
f5b0004a52 | |||
c063329e9a | |||
eeb9cbafe7 | |||
0f38da068c | |||
7e191eb18b | |||
a91168fd36 | |||
4a11cf455f | |||
adb2382aa5 | |||
6d6922efa6 | |||
846fb3072a | |||
3acd42f8d7 | |||
c98441b13c | |||
5cb3e1ded5 | |||
2b11f408fd | |||
d0e05e8be2 | |||
9a3b54b9a3 | |||
16133212fc | |||
16df81ac94 | |||
7d486c2ec6 | |||
4cb7d21a8f | |||
476900ab41 | |||
e6fbba3f80 | |||
0cea700dd8 | |||
d5058b3b20 | |||
355fd7aa53 | |||
33468630e6 | |||
48c1c50796 | |||
a0330a3fb2 | |||
6ea331ce3b | |||
a4b792e24d | |||
c4e2db32b5 | |||
cbd1bccbf9 | |||
136688997c | |||
03ec6d0eee | |||
30c3aa4f5b | |||
7ac3ffcc1b | |||
e23f56e81c | |||
3ee5501257 | |||
f5dbd44ebe | |||
3bfae84d32 | |||
c1d725ed34 | |||
de5a00e807 | |||
f445ac7521 | |||
9602c149ca | |||
cf2c18c32b | |||
887495f38f | |||
159bf03d49 | |||
288a8574c3 | |||
2e701ee8a2 | |||
eacf1be066 | |||
3652f1dcb2 | |||
d7f429d246 | |||
89e93d90b3 | |||
f8d983b523 | |||
7c30579900 | |||
e4d7a77348 | |||
83b94ab864 | |||
3c1bcdb1e2 | |||
f47ac8f96e | |||
ed961c795e | |||
e7ee745488 | |||
9ae7196a50 | |||
25a7e85c1c | |||
eb37f14923 | |||
edd180a8f5 | |||
a979df1070 | |||
08a9a9a8ab | |||
2135af0304 | |||
19ee32168e | |||
12ea1ad35c | |||
3a382e73b1 | |||
f1801f39a6 | |||
66947bcf09 | |||
85e1244db8 | |||
618eeb9029 | |||
b2efb45f64 | |||
59fe58577a | |||
3c312cb409 | |||
6cff3b1cc7 | |||
4b059770ea | |||
a35fcb682b | |||
8bbeeae327 | |||
35ad046d8d | |||
e9dee07459 | |||
2653eaa6b2 | |||
1a4d8edf37 | |||
4cda7e0299 | |||
81fa4e78ff | |||
08a00add6d | |||
7a10ea10df | |||
b37673c954 | |||
f5d0790619 | |||
12f38c892f | |||
31a79f0605 | |||
897e4ce3c1 | |||
5601c57059 | |||
c8cbd6b0ee | |||
8d61d8beec | |||
c20d7d45aa | |||
0a5cdd60ac | |||
bffed6b4d3 | |||
3c66a7af24 | |||
6e85b47f7c | |||
d9e6db96a7 | |||
97f0ea1c22 | |||
aa88745177 | |||
d1d3c70530 | |||
0a2120db45 | |||
5b3c92ee11 | |||
72b602409b |
54
.changelog.yml
Executable file
54
.changelog.yml
Executable file
@ -0,0 +1,54 @@
|
||||
# The full repository name
|
||||
repo: gitea/tea
|
||||
|
||||
# Service type (gitea or github)
|
||||
service: gitea
|
||||
|
||||
# Base URL for Gitea instance if using gitea service type (optional)
|
||||
base-url: https://gitea.com
|
||||
|
||||
# Changelog groups and which labeled PRs to add to each group
|
||||
groups:
|
||||
-
|
||||
name: BREAKING
|
||||
labels:
|
||||
- kind/breaking
|
||||
-
|
||||
name: FEATURES
|
||||
labels:
|
||||
- kind/feature
|
||||
-
|
||||
name: BUGFIXES
|
||||
labels:
|
||||
- kind/bug
|
||||
-
|
||||
name: ENHANCEMENTS
|
||||
labels:
|
||||
- kind/enhancement
|
||||
-
|
||||
name: SECURITY
|
||||
labels:
|
||||
- kind/security
|
||||
-
|
||||
name: TESTING
|
||||
labels:
|
||||
- kind/testing
|
||||
-
|
||||
name: TRANSLATION
|
||||
labels:
|
||||
- kind/translation
|
||||
-
|
||||
name: BUILD
|
||||
labels:
|
||||
- kind/build
|
||||
- kind/lint
|
||||
-
|
||||
name: DOCS
|
||||
labels:
|
||||
- kind/docs
|
||||
-
|
||||
name: MISC
|
||||
default: true
|
||||
|
||||
# regex indicating which labels to skip for the changelog
|
||||
skip-labels: skip-changelog|backport\/.+
|
29
.drone.yml
29
.drone.yml
@ -6,14 +6,10 @@ platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/code.gitea.io/tea
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
pull: always
|
||||
image: golang:1.13
|
||||
image: golang:1.15
|
||||
environment:
|
||||
GOPROXY: https://goproxy.cn
|
||||
commands:
|
||||
@ -31,8 +27,7 @@ steps:
|
||||
- pull_request
|
||||
|
||||
- name: unit-test
|
||||
pull: always
|
||||
image: golang:1.13
|
||||
image: golang:1.15
|
||||
commands:
|
||||
- make unit-test-coverage
|
||||
settings:
|
||||
@ -45,8 +40,7 @@ steps:
|
||||
- pull_request
|
||||
|
||||
- name: release-test
|
||||
pull: always
|
||||
image: golang:1.13
|
||||
image: golang:1.15
|
||||
commands:
|
||||
- make test
|
||||
settings:
|
||||
@ -60,7 +54,7 @@ steps:
|
||||
|
||||
- name: tag-test
|
||||
pull: always
|
||||
image: golang:1.13
|
||||
image: golang:1.15
|
||||
commands:
|
||||
- make test
|
||||
settings:
|
||||
@ -70,8 +64,9 @@ steps:
|
||||
- tag
|
||||
|
||||
- name: static
|
||||
pull: always
|
||||
image: techknowlogick/xgo:latest
|
||||
image: golang:1.15
|
||||
environment:
|
||||
GOPROXY: https://goproxy.cn
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release
|
||||
@ -163,15 +158,15 @@ steps:
|
||||
event:
|
||||
- push
|
||||
|
||||
- name: github
|
||||
- name: gitea
|
||||
pull: always
|
||||
image: plugins/gitea-releases:1
|
||||
image: plugins/gitea-release:1
|
||||
settings:
|
||||
files:
|
||||
- "dist/release/*"
|
||||
environment:
|
||||
GITHUB_TOKEN:
|
||||
from_secret: github_token
|
||||
base_url: https://gitea.com
|
||||
api_key:
|
||||
from_secret: gitea_token
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +1,6 @@
|
||||
tea
|
||||
/gitea-vet
|
||||
|
||||
.idea/
|
||||
.history/
|
||||
|
||||
dist/
|
||||
|
139
CHANGELOG.md
Normal file
139
CHANGELOG.md
Normal file
@ -0,0 +1,139 @@
|
||||
# Changelog
|
||||
|
||||
## [v0.6.0](https://gitea.com/gitea/tea/releases/tag/v0.6.0) - 2020-12-11
|
||||
|
||||
* BREAKING
|
||||
* Add `tea repos search`, improve repo listing (#215)
|
||||
* Add Detail View for Login (#212)
|
||||
* FEATURES
|
||||
* Add interactive mode for `tea pr create` (#279)
|
||||
* Add organization delete command (#270)
|
||||
* Add organization list command (#264)
|
||||
* BUGFIXES
|
||||
* Forces needed arguments to `tea ms issues` (#297)
|
||||
* Subcommands work outside of git repos (#285)
|
||||
* Fix repo flag ignores local repo for login detection (#285)
|
||||
* Improve ssh handling (#277)
|
||||
* Issue create return web url (#257)
|
||||
* Support prerelease gitea instances (#252)
|
||||
* Fix `tea pr create` within same repo (#248)
|
||||
* Handle login name case-insensitive on all comands (#227)
|
||||
* ENHANCEMENTS
|
||||
* Add `tea login delete` (#296)
|
||||
* Release delete: add --delete-tag & --confirm (#286)
|
||||
* Sorted milestones list (#281)
|
||||
* Pull clean & checkout use token for http(s) auth (#275)
|
||||
* Show more infos in pull detail view (#271)
|
||||
* Specify fields to print on `tea repos list` (#223)
|
||||
* Print times in local timezone (#217)
|
||||
* Issue create/edit print details (#214)
|
||||
* Improve `tea logout` (#213)
|
||||
* Added a shorthand for notifications (#209)
|
||||
* Common subcommand naming scheme (#208)
|
||||
* `tea pr checkout`: fetch via ssh if available (#192)
|
||||
* Major refactor of codebase
|
||||
* BUILD
|
||||
* Use gox to cross-compile (#274)
|
||||
* DOCS
|
||||
* Update Docs to new code structure (#247)
|
||||
|
||||
## [v0.5.0](https://gitea.com/gitea/tea/releases/tag/v0.5.0) - 2020-09-27
|
||||
|
||||
* BREAKING
|
||||
* Add Login Manage Functions (#182)
|
||||
* FEATURES
|
||||
* Add Release Subcomands (#195)
|
||||
* Render Markdown and colorize labels table (#181)
|
||||
* Add BasicAuth & Interactive for Login (#174)
|
||||
* Add milestones subcomands (#149)
|
||||
* BUGFIXES
|
||||
* Fix Pulls Create (#202)
|
||||
* Pulls create: detect head branch repo owner (#193)
|
||||
* Fix Labels Delete (#180)
|
||||
* ENHANCEMENTS
|
||||
* Add Pagination Options for List Subcomands (#204)
|
||||
* Issues/Pulls: Details show State (#196)
|
||||
* Make issues & pulls subcommands consistent (#188)
|
||||
* Update SDK to v0.13.0 (#179)
|
||||
* More Options To Specify Repo (#178)
|
||||
* Add Repo Create subcomand & enhancements (#173)
|
||||
* Times: format duration as seconds for machine-readable outputs (#168)
|
||||
* Add user message to login list view (#166)
|
||||
|
||||
## [v0.4.1](https://gitea.com/gitea/tea/releases/tag/v0.4.1) - 2020-09-13
|
||||
|
||||
* BUGFIXES
|
||||
* Notification don't relay on a repo (#159)
|
||||
|
||||
## [v0.4.0](https://gitea.com/gitea/tea/pulls?q=&type=all&state=closed&milestone=1264) - 2020-07-18
|
||||
|
||||
* FEATURES
|
||||
* Add notifications subcomand (#148)
|
||||
* Add subcomand 'pulls create' (#144)
|
||||
* BUGFIXES
|
||||
* Fix Login Detection By Repo Param (#151)
|
||||
* Fix Login List Output (#150)
|
||||
* Fix --ssh-key Option (#135)
|
||||
* ENHANCEMENTS
|
||||
* Subcomand Login Show List By Default (#152)
|
||||
* BUILD
|
||||
* Migrate src-d/go-git to go-git/go-git (#128)
|
||||
* Migrate gitea-sdk to v0.12.0 (#133)
|
||||
* Migrate yaml lib (#130)
|
||||
* Add gitea-vet (#121)
|
||||
|
||||
## [v0.3.1](https://gitea.com/gitea/tea/pulls?q=&type=all&state=closed&milestone=1265) - 2020-06-15
|
||||
|
||||
* BUGFIXES
|
||||
* --ssh-key should be string not bool (#135) (#137)
|
||||
* modules/git: fix dropped error (#127)
|
||||
* Issues details: add missing newline (#126)
|
||||
|
||||
## [v0.3.0](https://gitea.com/gitea/tea/pulls?q=&type=all&state=closed&milestone=1227) - 2020-04-22
|
||||
|
||||
* FEATURES
|
||||
* Add `tea pulls [checkout | clean]` commands (#93 #97 #107) (#105)
|
||||
* Add `tea open` (#101)
|
||||
* Add `tea issues [open|close]` commands (#99)
|
||||
* ENHANCEMENTS
|
||||
* Ignore PRs for `tea issues` (#111)
|
||||
* Add --state flag filter to issue & PR lists (#100)
|
||||
|
||||
## [v0.2.0](https://gitea.com/gitea/tea/pulls?q=&type=all&state=closed&milestone=538) - 2020-03-06
|
||||
* FEATURES
|
||||
* Add `tea times` command (#54)
|
||||
* ENHANCEMENTS
|
||||
* Upgrade urfave/cli to v2 version (#85)
|
||||
* Add --remote flag to add/create subcommands (#77)
|
||||
* BUILD
|
||||
* Upgrade gitea/go-sdk to 2020-01-03 (#81)
|
||||
* Update stretchr/testify v1.3.0 -> v1.4.0 (#83)
|
||||
* Improve makefile to enable goproxy when go get tools (#98)
|
||||
|
||||
## [v0.1.2](https://gitea.com/gitea/tea/pulls?q=&type=all&state=closed&milestone=59) - 2019-11-15
|
||||
* BUILD
|
||||
* Fix typo in drone (#75)
|
||||
|
||||
## [v0.1.1](https://gitea.com/gitea/tea/pulls?q=&type=all&state=closed&milestone=59) - 2019-11-15
|
||||
* FEATURES
|
||||
* Add repos subcommand (#65)
|
||||
* ENHANCEMENTS
|
||||
* Minor improvements to command-line language (#66)
|
||||
|
||||
## [v0.1.0](https://gitea.com/gitea/tea/pulls?q=&type=all&state=closed&milestone=59) - 2019-10-28
|
||||
* BREAKING
|
||||
* Changed git config determination to go-git (#41) [continue #45] (#62)
|
||||
* FEATURES
|
||||
* Add labels commands (#36)
|
||||
* BUGFIXES
|
||||
* Fix out -o flag (#53)
|
||||
* Fix log formatting, refactor flag definition in cmd/labels.go (#52)
|
||||
* ENHANCEMENTS
|
||||
* List label description (#60)
|
||||
* Use Different Remote Repos (#58)
|
||||
* Unified output (#14) (#40)
|
||||
* Added global appendable Flags (#12) (#39)
|
||||
* BUILD
|
||||
* Change .drone.yml to new format (#33)
|
||||
* DOCS
|
||||
* Add install guide from brew on README (#61)
|
130
CONTRIBUTING.md
130
CONTRIBUTING.md
@ -8,7 +8,6 @@
|
||||
- [Discuss your design](#discuss-your-design)
|
||||
- [Testing redux](#testing-redux)
|
||||
- [Vendoring](#vendoring)
|
||||
- [Translation](#translation)
|
||||
- [Code review](#code-review)
|
||||
- [Styleguide](#styleguide)
|
||||
- [Sign-off your work](#sign-off-your-work)
|
||||
@ -20,36 +19,31 @@
|
||||
|
||||
## Introduction
|
||||
|
||||
This document explains how to contribute changes to the Gitea project.
|
||||
It assumes you have followed the
|
||||
[installation instructions](https://docs.gitea.io/en-us/).
|
||||
This document explains how to contribute changes to TEA.
|
||||
Sensitive security-related issues should be reported to
|
||||
[security@gitea.io](mailto:security@gitea.io).
|
||||
|
||||
For configuring IDE or code editor to develop Gitea see [IDE and code editor configuration](contrib/ide/)
|
||||
For configuring IDE or code editor to develop Gitea see [IDE and code editor configuration](https://github.com/go-gitea/gitea/tree/master/contrib/ide)
|
||||
|
||||
## Bug reports
|
||||
|
||||
Please search the issues on the issue tracker with a variety of keywords
|
||||
to ensure your bug is not already reported.
|
||||
|
||||
If unique, [open an issue](https://github.com/go-gitea/gitea/issues/new)
|
||||
and answer the questions so we can understand and reproduce the
|
||||
problematic behavior.
|
||||
If unique, [open an issue](https://gitea.com/gitea/tea/issues/new).
|
||||
|
||||
To show us that the issue you are having is in Gitea itself, please
|
||||
write clear, concise instructions so we can reproduce the behavior—
|
||||
Please write clear, concise instructions so we can reproduce the behavior—
|
||||
even if it seems obvious. The more detailed and specific you are,
|
||||
the faster we can fix the issue. Check out [How to Report Bugs
|
||||
Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
|
||||
|
||||
Please be kind, remember that Gitea comes at no cost to you, and you're
|
||||
Please be kind, remember that TEA comes at no cost to you, and you're
|
||||
getting free help.
|
||||
|
||||
## Discuss your design
|
||||
|
||||
The project welcomes submissions. If you want to change or add something,
|
||||
please let everyone know what you're working on—[file an issue](https://github.com/go-gitea/gitea/issues/new)!
|
||||
please let everyone know what you're working on—[file an issue](https://gitea.com/gitea/tea/issues/new)!
|
||||
Significant changes must go through the change proposal process
|
||||
before they can be accepted. To create a proposal, file an issue with
|
||||
your proposed changes documented, and make sure to note in the title
|
||||
@ -63,15 +57,8 @@ high-level discussions.
|
||||
|
||||
## Testing redux
|
||||
|
||||
Before sending code out for review, run all the tests for the
|
||||
whole tree to make sure the changes don't break other usage
|
||||
and keep the compatibility on upgrade. To make sure you are
|
||||
running the test suite exactly like we do, you should install
|
||||
the CLI for [Drone CI](https://github.com/drone/drone), as
|
||||
we are using the server for continous testing, following [these
|
||||
instructions](http://docs.drone.io/cli-installation/). After that,
|
||||
you can simply call `drone exec --local --build-event "pull_request"` within
|
||||
your working directory and it will try to run the test suite locally.
|
||||
Before sending code out for review, run all the test by execting: `make test`
|
||||
Since TEA is an cli tool it should be obvious to test your feature localy first.
|
||||
|
||||
## Vendoring
|
||||
|
||||
@ -87,31 +74,11 @@ an existing upstream commit.
|
||||
|
||||
You can find more information on how to get started with it on the [dep project website](https://golang.github.io/dep/docs/introduction.html).
|
||||
|
||||
## Translation
|
||||
|
||||
We do all translation work inside [Crowdin](https://crowdin.com/project/gitea).
|
||||
The only translation that is maintained in this git repository is
|
||||
[`en_US.ini`](https://github.com/go-gitea/gitea/blob/master/options/locale/locale_en-US.ini)
|
||||
and is synced regularily to Crowdin. Once a translation has reached
|
||||
A SATISFACTORY PERCENTAGE it will be synced back into this repo and
|
||||
included in the next released version.
|
||||
|
||||
## Building Gitea
|
||||
|
||||
Generally, the go build tools are installed as-needed in the `Makefile`.
|
||||
An exception are the tools to build the CSS and images.
|
||||
|
||||
- To build CSS: Install [Node.js](https://nodejs.org/en/download/package-manager)
|
||||
with `npm` and then run `npm install` and `make generate-stylesheets`.
|
||||
- To build Images: ImageMagick, inkscape and zopflipng binaries must be
|
||||
available in your `PATH` to run `make generate-images`.
|
||||
|
||||
## Code review
|
||||
|
||||
Changes to Gitea must be reviewed before they are accepted—no matter who
|
||||
makes the change, even if they are an owner or a maintainer. We use GitHub's
|
||||
pull request workflow to do that. And, we also use [LGTM](http://lgtm.co)
|
||||
to ensure every PR is reviewed by at least 2 maintainers.
|
||||
Changes to TEA must be reviewed before they are accepted—no matter who
|
||||
makes the change, even if they are an owner or a maintainer. We use Giteas's
|
||||
pull request & review workflow to do that. Gitea ensure every PR is reviewed by at least 2 maintainers.
|
||||
|
||||
Please try to make your pull request easy to review for us. And, please read
|
||||
the *[How to get faster PR reviews](https://github.com/kubernetes/community/blob/261cb0fd089b64002c91e8eddceebf032462ccd6/contributors/guide/pull-requests.md#best-practices-for-faster-reviews)* guide;
|
||||
@ -128,6 +95,41 @@ Some of the key points:
|
||||
|
||||
## Styleguide
|
||||
|
||||
### Commands
|
||||
- Subcommands should follow the following structure:
|
||||
```
|
||||
tea <noun> <verb> [<noun>] [<flags>]
|
||||
```
|
||||
|
||||
for example:
|
||||
|
||||
```
|
||||
tea issues list
|
||||
tea pulls create
|
||||
tea teams add user --team x --user y
|
||||
```
|
||||
- Commands should accept nouns as singular & plural by making use of the `Aliases` field.
|
||||
- The default action without a verb is `list`.
|
||||
- There is a standard set of verbs: `list/ls`, `create`, `edit`, `delete`
|
||||
- `ls` lists objects with filter options, and applies pagination where available.
|
||||
- `delete` should show info what is deleted and ask user again, if force flag`-y` is not set
|
||||
- Verbs that accept large numbers of flags provide an interactive mode when called without any arguments or flags.
|
||||
- Try to reuse as many flag definitions as possible, see `cmd/flags/flags.go`.
|
||||
- Always make sure that the help texts are properly set, and as concise as possible.
|
||||
|
||||
### Internal Module Structure
|
||||
- `cmd`: only contains comand/flag options for `urfave/cli`
|
||||
- subcomands are in a subpackage named after its parent comand
|
||||
- `modules/task`: contain func for doing something with gitea
|
||||
(e.g. create token by user/passwd)
|
||||
- `modules/print`: contain all functions that print to stdout
|
||||
- `modules/config`: config tea & login things
|
||||
- `modules/interact`: contain functions to interact with user by prompts
|
||||
- `modules/git`: do git related stuff (get info/push/pull/checkout/...)
|
||||
- `modules/utils`: helper functions used by other functions
|
||||
|
||||
### Code Style
|
||||
Use `make fmt`, check with `make lint`.
|
||||
For imports you should use the following format (_without_ the comments)
|
||||
```go
|
||||
import (
|
||||
@ -164,25 +166,7 @@ commit automatically with `git commit -s`.
|
||||
|
||||
## Release Cycle
|
||||
|
||||
We adopted a release schedule to streamline the process of working
|
||||
on, finishing, and issuing releases. The overall goal is to make a
|
||||
minor release every two months, which breaks down into one month of
|
||||
general development followed by one month of testing and polishing
|
||||
known as the release freeze. All the feature pull requests should be
|
||||
merged in the first month of one release period. And, during the frozen
|
||||
period, a corresponding release branch is open for fixes backported from
|
||||
master. Release candidates are made during this period for user testing to
|
||||
obtain a final version that is maintained in this branch. A release is
|
||||
maintained by issuing patch releases to only correct critical problems
|
||||
such as crashes or security issues.
|
||||
|
||||
Major release cycles are bimonthly. They always begin on the 25th and end on
|
||||
the 24th (i.e., the 25th of December to February 24th).
|
||||
|
||||
During a development cycle, we may also publish any necessary minor releases
|
||||
for the previous version. For example, if the latest, published release is
|
||||
v1.2, then minor changes for the previous release—e.g., v1.1.0 -> v1.1.1—are
|
||||
still possible.
|
||||
Before we reach v1 there is no fixed release cycle.
|
||||
|
||||
## Maintainers
|
||||
|
||||
@ -208,6 +192,9 @@ https://help.github.com/articles/signing-commits-with-gpg/
|
||||
|
||||
## Owners
|
||||
|
||||
This repo is part of the Gitea project and as such part of that project's
|
||||
governance.
|
||||
|
||||
Since Gitea is a pure community organization without any company support,
|
||||
to keep the development healthy we will elect three owners every year. All
|
||||
contributors may vote to elect up to three candidates, one of which will
|
||||
@ -230,22 +217,9 @@ I'm honored to having been elected an owner of Gitea, I agree with
|
||||
and lead the development of Gitea.
|
||||
```
|
||||
|
||||
To honor the past owners, here's the history of the owners and the time
|
||||
they served:
|
||||
|
||||
* 2016-11-04 ~ 2017-12-31
|
||||
* [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
|
||||
* [Thomas Boerger](https://github.com/tboerger) <thomas@webhippie.de>
|
||||
* [Kim Carlbäcker](https://github.com/bkcsoft) <kim.carlbacker@gmail.com>
|
||||
|
||||
* 2018-01-01 ~ 2018-12-31
|
||||
* [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
|
||||
* [Lauris Bukšis-Haberkorns](https://github.com/lafriks) <lauris@nix.lv>
|
||||
* [Kim Carlbäcker](https://github.com/bkcsoft) <kim.carlbacker@gmail.com>
|
||||
|
||||
## Versions
|
||||
|
||||
Gitea has the `master` branch as a tip branch and has version branches
|
||||
tea has the `master` branch as a tip branch and has version branches
|
||||
such as `release/v0.9`. `release/v0.9` is a release branch and we will
|
||||
tag `v0.9.0` for binary download. If `v0.9.0` has bugs, we will accept
|
||||
pull requests on the `release/v0.9` branch and publish a `v0.9.1` tag,
|
||||
@ -261,7 +235,7 @@ be reviewed by two maintainers and must pass the automatic tests.
|
||||
Code that you contribute should use the standard copyright header:
|
||||
|
||||
```
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
```
|
||||
|
67
Makefile
67
Makefile
@ -1,6 +1,6 @@
|
||||
DIST := dist
|
||||
IMPORT := code.gitea.io/tea
|
||||
export GO111MODULE=off
|
||||
export GO111MODULE=on
|
||||
|
||||
GO ?= go
|
||||
SED_INPLACE := sed -i
|
||||
@ -59,7 +59,7 @@ all: build
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(GO) clean -i ./...
|
||||
$(GO) clean -mod=vendor -i ./...
|
||||
rm -rf $(EXECUTABLE) $(DIST)
|
||||
|
||||
.PHONY: fmt
|
||||
@ -68,26 +68,30 @@ fmt:
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
$(GO) vet $(PACKAGES)
|
||||
# Default vet
|
||||
$(GO) vet -mod=vendor $(PACKAGES)
|
||||
# Custom vet
|
||||
$(GO) build -mod=vendor code.gitea.io/gitea-vet
|
||||
$(GO) vet -vettool=gitea-vet $(PACKAGES)
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/mgechev/revive; \
|
||||
cd /tmp && $(GO) get -u github.com/mgechev/revive; \
|
||||
fi
|
||||
revive -config .revive.toml -exclude=./vendor/... ./... || exit 1
|
||||
|
||||
.PHONY: misspell-check
|
||||
misspell-check:
|
||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||
cd /tmp && $(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||
fi
|
||||
misspell -error -i unknwon,destory $(GOFILES)
|
||||
|
||||
.PHONY: misspell
|
||||
misspell:
|
||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||
cd /tmp && $(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||
fi
|
||||
misspell -w -i unknwon $(GOFILES)
|
||||
|
||||
@ -103,15 +107,15 @@ fmt-check:
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
GO111MODULE=on $(GO) test -mod=vendor -tags='sqlite sqlite_unlock_notify' $(PACKAGES)
|
||||
$(GO) test -mod=vendor -tags='sqlite sqlite_unlock_notify' $(PACKAGES)
|
||||
|
||||
.PHONY: unit-test-coverage
|
||||
unit-test-coverage:
|
||||
$(GO) test -tags='sqlite sqlite_unlock_notify' -cover -coverprofile coverage.out $(PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||
$(GO) test -mod=vendor -tags='sqlite sqlite_unlock_notify' -cover -coverprofile coverage.out $(PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||
|
||||
.PHONY: vendor
|
||||
vendor:
|
||||
GO111MODULE=on $(GO) mod tidy && GO111MODULE=on $(GO) mod vendor
|
||||
$(GO) mod tidy && $(GO) mod vendor
|
||||
|
||||
.PHONY: test-vendor
|
||||
test-vendor: vendor
|
||||
@ -127,59 +131,32 @@ check: test
|
||||
|
||||
.PHONY: install
|
||||
install: $(wildcard *.go)
|
||||
$(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
|
||||
$(GO) install -mod=vendor -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
|
||||
|
||||
.PHONY: build
|
||||
build: $(EXECUTABLE)
|
||||
|
||||
$(EXECUTABLE): $(SOURCES)
|
||||
GO111MODULE=on $(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: release
|
||||
release: release-dirs release-windows release-linux release-darwin release-copy release-compress release-check
|
||||
release: release-dirs release-os release-compress release-check
|
||||
|
||||
.PHONY: release-dirs
|
||||
release-dirs:
|
||||
mkdir -p $(DIST)/binaries $(DIST)/release
|
||||
|
||||
.PHONY: release-windows
|
||||
release-windows:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u src.techknowlogick.com/xgo; \
|
||||
.PHONY: release-os
|
||||
release-os:
|
||||
@hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
cd /tmp && $(GO) get -u github.com/mitchellh/gox; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out tea-$(VERSION) .
|
||||
ifeq ($(CI),drone)
|
||||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-linux
|
||||
release-linux:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u src.techknowlogick.com/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out tea-$(VERSION) .
|
||||
ifeq ($(CI),drone)
|
||||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-darwin
|
||||
release-darwin:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u src.techknowlogick.com/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out tea-$(VERSION) .
|
||||
ifeq ($(CI),drone)
|
||||
cp /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-copy
|
||||
release-copy:
|
||||
cd $(DIST); for file in `find /build -type f -name "*"`; do cp $${file} ./release/; done;
|
||||
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}}"
|
||||
|
||||
.PHONY: release-compress
|
||||
release-compress:
|
||||
@hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/ulikunitz/xz/cmd/gxz; \
|
||||
GO111MODULE=off $(GO) get -u github.com/ulikunitz/xz/cmd/gxz; \
|
||||
fi
|
||||
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done;
|
||||
|
||||
|
66
README.md
66
README.md
@ -1,31 +1,34 @@
|
||||
# Gitea Command Line Tool for Go
|
||||
# <img alt='' src='https://gitea.com/repo-avatars/550-80a3a8c2ab0e2c2d69f296b7f8582485' height="40"/> *T E A*
|
||||
|
||||
This project acts as a command line tool for operating one or multiple Gitea instances. It depends on [code.gitea.io/sdk](https://code.gitea.io/sdk) client SDK implementation written in Go to interact with
|
||||
the Gitea API implementation.
|
||||
[](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
|
||||
|
||||
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
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
Currently no prebuilt binaries are provided.
|
||||
To install, a Go installation is needed.
|
||||
You can use the prebuilt binaries from [dl.gitea.io](https://dl.gitea.io/tea/)
|
||||
|
||||
To install from source, go 1.13 or newer is required:
|
||||
|
||||
```sh
|
||||
go get code.gitea.io/tea
|
||||
go install code.gitea.io/tea
|
||||
```
|
||||
|
||||
If the `tea` executable is not found, you might need to set up your `$GOPATH` and `$PATH` variables first:
|
||||
|
||||
```sh
|
||||
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
|
||||
```
|
||||
|
||||
If you have `brew` installed, you can install tea version via:
|
||||
If you have `brew` installed, you can install `tea` via:
|
||||
|
||||
```sh
|
||||
brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea
|
||||
brew install --devel tea
|
||||
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.
|
||||
@ -35,33 +38,48 @@ Use this token to login with `tea`:
|
||||
tea login add --name=try --url=https://try.gitea.io --token=xxxxxx
|
||||
```
|
||||
|
||||
Now you can use the `tea` commands:
|
||||
Now you can use the following `tea` subcommands.
|
||||
Detailed usage information is available via `tea <command> --help`.
|
||||
|
||||
```sh
|
||||
tea issues
|
||||
tea releases
|
||||
```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
|
||||
```
|
||||
|
||||
> If you are inside a git repository hosted on a gitea instance, you don't need to specify the `--login` and `--repo` flags!
|
||||
To fetch issues from different repos, use the `--remote` flag (when inside a gitea repository directory) or `--login` & `--repo` flags.
|
||||
|
||||
## Compilation
|
||||
|
||||
Make sure you have installed a current go version.
|
||||
To compile the sources yourself run the following:
|
||||
|
||||
```sh
|
||||
go get code.gitea.io/tea
|
||||
cd "${GOPATH}/src/code.gitea.io/tea"
|
||||
go build
|
||||
git clone https://gitea.com/gitea/tea.git
|
||||
cd tea
|
||||
make
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Fork -> Patch -> Push -> Pull Request
|
||||
|
||||
## Authors
|
||||
- `make test` run testsuite
|
||||
- `make vet` run checks (check the order of imports; preventing failure on CI pipeline beforehand)
|
||||
- `make vendor` when adding new dependencies
|
||||
- ... (for other development tasks, check the `Makefile`)
|
||||
|
||||
* [Maintainers](https://github.com/orgs/go-gitea/people)
|
||||
* [Contributors](https://github.com/go-gitea/tea/graphs/contributors)
|
||||
**Please** read the [CONTRIBUTING](CONTRIBUTING.md) documentation, it will tell you about internal structures and concepts.
|
||||
|
||||
## License
|
||||
|
||||
|
14
build.go
Normal file
14
build.go
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//+build vendor
|
||||
|
||||
package main
|
||||
|
||||
// Libraries that are included to vendor utilities used during build.
|
||||
// These libraries will not be included in a normal compilation.
|
||||
|
||||
import (
|
||||
// for vet
|
||||
_ "code.gitea.io/gitea-vet"
|
||||
)
|
246
cmd/config.go
246
cmd/config.go
@ -1,246 +0,0 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
local_git "code.gitea.io/tea/modules/git"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
go_git "gopkg.in/src-d/go-git.v4"
|
||||
|
||||
"github.com/go-gitea/yaml"
|
||||
)
|
||||
|
||||
// Login represents a login to a gitea server, you even could add multiple logins for one gitea server
|
||||
type Login struct {
|
||||
Name string `yaml:"name"`
|
||||
URL string `yaml:"url"`
|
||||
Token string `yaml:"token"`
|
||||
Active bool `yaml:"active"`
|
||||
SSHHost string `yaml:"ssh_host"`
|
||||
Insecure bool `yaml:"insecure"`
|
||||
}
|
||||
|
||||
// Client returns a client to operate Gitea API
|
||||
func (l *Login) Client() *gitea.Client {
|
||||
client := gitea.NewClient(l.URL, l.Token)
|
||||
if l.Insecure {
|
||||
cookieJar, _ := cookiejar.New(nil)
|
||||
|
||||
client.SetHTTPClient(&http.Client{
|
||||
Jar: cookieJar,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
})
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// GetSSHHost returns SSH host name
|
||||
func (l *Login) GetSSHHost() string {
|
||||
if l.SSHHost != "" {
|
||||
return l.SSHHost
|
||||
}
|
||||
|
||||
u, err := url.Parse(l.URL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return u.Hostname()
|
||||
}
|
||||
|
||||
// Config reprensents local configurations
|
||||
type Config struct {
|
||||
Logins []Login `yaml:"logins"`
|
||||
}
|
||||
|
||||
var (
|
||||
config Config
|
||||
yamlConfigPath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
homeDir, err := utils.Home()
|
||||
if err != nil {
|
||||
log.Fatal("Retrieve home dir failed")
|
||||
}
|
||||
|
||||
dir := filepath.Join(homeDir, ".tea")
|
||||
err = os.MkdirAll(dir, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatal("Init tea config dir", dir, "failed")
|
||||
}
|
||||
|
||||
yamlConfigPath = filepath.Join(dir, "tea.yml")
|
||||
}
|
||||
|
||||
func splitRepo(repoPath string) (string, string) {
|
||||
p := strings.Split(repoPath, "/")
|
||||
if len(p) >= 2 {
|
||||
return p[0], p[1]
|
||||
}
|
||||
return repoPath, ""
|
||||
}
|
||||
|
||||
func getActiveLogin() (*Login, error) {
|
||||
if len(config.Logins) == 0 {
|
||||
return nil, errors.New("No available login")
|
||||
}
|
||||
for _, l := range config.Logins {
|
||||
if l.Active {
|
||||
return &l, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &config.Logins[0], nil
|
||||
}
|
||||
|
||||
func getLoginByName(name string) *Login {
|
||||
for _, l := range config.Logins {
|
||||
if l.Name == name {
|
||||
return &l
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addLogin(login Login) error {
|
||||
for _, l := range config.Logins {
|
||||
if l.Name == login.Name {
|
||||
if l.URL == login.URL && l.Token == login.Token {
|
||||
return nil
|
||||
}
|
||||
return errors.New("login name has already been used")
|
||||
}
|
||||
if l.URL == login.URL && l.Token == login.Token {
|
||||
return errors.New("URL has been added")
|
||||
}
|
||||
}
|
||||
|
||||
u, err := url.Parse(login.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if login.SSHHost == "" {
|
||||
login.SSHHost = u.Hostname()
|
||||
}
|
||||
config.Logins = append(config.Logins, login)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isFileExist(fileName string) (bool, error) {
|
||||
f, err := os.Stat(fileName)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if f.IsDir() {
|
||||
return false, errors.New("the same name directory exist")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func loadConfig(ymlPath string) error {
|
||||
exist, _ := isFileExist(ymlPath)
|
||||
if exist {
|
||||
Println("Found config file", ymlPath)
|
||||
bs, err := ioutil.ReadFile(ymlPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(bs, &config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveConfig(ymlPath string) error {
|
||||
bs, err := yaml.Marshal(&config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(ymlPath, bs, 0660)
|
||||
}
|
||||
|
||||
func curGitRepoPath() (*Login, string, error) {
|
||||
gitPath, err := go_git.PlainOpenWithOptions("./", &go_git.PlainOpenOptions{DetectDotGit: true})
|
||||
if err != nil {
|
||||
return nil, "", errors.New("No Gitea login found")
|
||||
}
|
||||
gitConfig, err := gitPath.Config()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// if no remote
|
||||
if len(gitConfig.Remotes) == 0 {
|
||||
return nil, "", errors.New("No remote repository set on 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("No remote " + remoteValue + " found on this git repository")
|
||||
}
|
||||
|
||||
for _, l := range config.Logins {
|
||||
for _, u := range remoteConfig.URLs {
|
||||
p, err := local_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")
|
||||
}
|
107
cmd/flags.go
107
cmd/flags.go
@ -1,107 +0,0 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// create global variables for global Flags to simplify
|
||||
// access to the options without requiring cli.Context
|
||||
var (
|
||||
loginValue string
|
||||
repoValue string
|
||||
outputValue string
|
||||
remoteValue string
|
||||
)
|
||||
|
||||
// LoginFlag provides flag to specify tea login profile
|
||||
var LoginFlag = cli.StringFlag{
|
||||
Name: "login, l",
|
||||
Usage: "Indicate one login, optional when inside a gitea repository",
|
||||
Destination: &loginValue,
|
||||
}
|
||||
|
||||
// RepoFlag provides flag to specify repository
|
||||
var RepoFlag = cli.StringFlag{
|
||||
Name: "repo, r",
|
||||
Usage: "Indicate one repository, optional when inside a gitea repository",
|
||||
Destination: &repoValue,
|
||||
}
|
||||
|
||||
// RemoteFlag provides flag to specify remote repository
|
||||
var RemoteFlag = cli.StringFlag{
|
||||
Name: "remote, R",
|
||||
Usage: "Set a specific remote repository, is optional if not set use git default one",
|
||||
Destination: &remoteValue,
|
||||
}
|
||||
|
||||
// OutputFlag provides flag to specify output type
|
||||
var OutputFlag = cli.StringFlag{
|
||||
Name: "output, o",
|
||||
Usage: "Specify output format. (csv, simple, table, tsv, yaml)",
|
||||
Destination: &outputValue,
|
||||
}
|
||||
|
||||
// LoginOutputFlags defines login and output flags that should
|
||||
// added to all subcommands and appended to the flags of the
|
||||
// subcommand to work around issue and provide --login and --output:
|
||||
// https://github.com/urfave/cli/issues/585
|
||||
var LoginOutputFlags = []cli.Flag{
|
||||
LoginFlag,
|
||||
OutputFlag,
|
||||
}
|
||||
|
||||
// LoginRepoFlags defines login and repo flags that should
|
||||
// be used for all subcommands and appended to the flags of
|
||||
// the subcommand to work around issue and provide --login and --repo:
|
||||
// https://github.com/urfave/cli/issues/585
|
||||
var LoginRepoFlags = []cli.Flag{
|
||||
LoginFlag,
|
||||
RepoFlag,
|
||||
}
|
||||
|
||||
// AllDefaultFlags defines flags that should be available
|
||||
// for all subcommands working with dedicated repositories
|
||||
// to work around issue and provide --login, --repo and --output:
|
||||
// https://github.com/urfave/cli/issues/585
|
||||
var AllDefaultFlags = append([]cli.Flag{
|
||||
RepoFlag,
|
||||
RemoteFlag,
|
||||
}, LoginOutputFlags...)
|
||||
|
||||
// initCommand returns repository and *Login based on flags
|
||||
func initCommand() (*Login, string, string) {
|
||||
err := loadConfig(yamlConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal("load config file failed ", yamlConfigPath)
|
||||
}
|
||||
|
||||
var login *Login
|
||||
if loginValue == "" {
|
||||
login, err = getActiveLogin()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
login = getLoginByName(loginValue)
|
||||
if login == nil {
|
||||
log.Fatal("indicated login name ", loginValue, " does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
repoPath := repoValue
|
||||
if repoPath == "" {
|
||||
login, repoPath, err = curGitRepoPath()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
owner, repo := splitRepo(repoPath)
|
||||
return login, owner, repo
|
||||
}
|
124
cmd/flags/flags.go
Normal file
124
cmd/flags/flags.go
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package flags
|
||||
|
||||
import (
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// create global variables for global Flags to simplify
|
||||
// access to the options without requiring cli.Context
|
||||
var (
|
||||
// GlobalLoginValue contain value of --login|-l arg
|
||||
GlobalLoginValue string
|
||||
// GlobalRepoValue contain value of --repo|-r arg
|
||||
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
|
||||
var LoginFlag = cli.StringFlag{
|
||||
Name: "login",
|
||||
Aliases: []string{"l"},
|
||||
Usage: "Use a different Gitea login. Optional",
|
||||
Destination: &GlobalLoginValue,
|
||||
}
|
||||
|
||||
// RepoFlag provides flag to specify repository
|
||||
var RepoFlag = cli.StringFlag{
|
||||
Name: "repo",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "Override local repository path or gitea repository slug to interact with. Optional",
|
||||
Destination: &GlobalRepoValue,
|
||||
}
|
||||
|
||||
// RemoteFlag provides flag to specify remote repository
|
||||
var RemoteFlag = cli.StringFlag{
|
||||
Name: "remote",
|
||||
Aliases: []string{"R"},
|
||||
Usage: "Discover Gitea login from remote. Optional",
|
||||
Destination: &GlobalRemoteValue,
|
||||
}
|
||||
|
||||
// OutputFlag provides flag to specify output type
|
||||
var OutputFlag = cli.StringFlag{
|
||||
Name: "output",
|
||||
Aliases: []string{"o"},
|
||||
Usage: "Output format. (csv, simple, table, tsv, yaml)",
|
||||
Destination: &GlobalOutputValue,
|
||||
}
|
||||
|
||||
// StateFlag provides flag to specify issue/pr state, defaulting to "open"
|
||||
var StateFlag = cli.StringFlag{
|
||||
Name: "state",
|
||||
Usage: "Filter by state (all|open|closed)",
|
||||
DefaultText: "open",
|
||||
}
|
||||
|
||||
// PaginationPageFlag provides flag for pagination options
|
||||
var PaginationPageFlag = cli.StringFlag{
|
||||
Name: "page",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "specify page, default is 1",
|
||||
}
|
||||
|
||||
// PaginationLimitFlag provides flag for pagination options
|
||||
var PaginationLimitFlag = cli.StringFlag{
|
||||
Name: "limit",
|
||||
Aliases: []string{"lm"},
|
||||
Usage: "specify limit of items per page",
|
||||
}
|
||||
|
||||
// LoginOutputFlags defines login and output flags that should
|
||||
// added to all subcommands and appended to the flags of the
|
||||
// subcommand to work around issue and provide --login and --output:
|
||||
// https://github.com/urfave/cli/issues/585
|
||||
var LoginOutputFlags = []cli.Flag{
|
||||
&LoginFlag,
|
||||
&OutputFlag,
|
||||
}
|
||||
|
||||
// LoginRepoFlags defines login and repo flags that should
|
||||
// be used for all subcommands and appended to the flags of
|
||||
// the subcommand to work around issue and provide --login and --repo:
|
||||
// https://github.com/urfave/cli/issues/585
|
||||
var LoginRepoFlags = []cli.Flag{
|
||||
&LoginFlag,
|
||||
&RepoFlag,
|
||||
&RemoteFlag,
|
||||
}
|
||||
|
||||
// AllDefaultFlags defines flags that should be available
|
||||
// for all subcommands working with dedicated repositories
|
||||
// to work around issue and provide --login, --repo and --output:
|
||||
// https://github.com/urfave/cli/issues/585
|
||||
var AllDefaultFlags = append([]cli.Flag{
|
||||
&RepoFlag,
|
||||
&RemoteFlag,
|
||||
}, LoginOutputFlags...)
|
||||
|
||||
// IssuePRFlags defines flags that should be available on issue & pr listing flags.
|
||||
var IssuePRFlags = append([]cli.Flag{
|
||||
&StateFlag,
|
||||
&PaginationPageFlag,
|
||||
&PaginationLimitFlag,
|
||||
}, AllDefaultFlags...)
|
||||
|
||||
// GetListOptions return ListOptions based on PaginationFlags
|
||||
func GetListOptions(ctx *cli.Context) gitea.ListOptions {
|
||||
page := ctx.Int("page")
|
||||
limit := ctx.Int("limit")
|
||||
if limit != 0 && page == 0 {
|
||||
page = 1
|
||||
}
|
||||
return gitea.ListOptions{
|
||||
Page: page,
|
||||
PageSize: limit,
|
||||
}
|
||||
}
|
152
cmd/issues.go
152
cmd/issues.go
@ -5,154 +5,50 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/cmd/issues"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdIssues represents to login a gitea server.
|
||||
var CmdIssues = cli.Command{
|
||||
Name: "issues",
|
||||
Usage: "Operate with issues of the repository",
|
||||
Description: `Operate with issues of the repository`,
|
||||
Aliases: []string{"issue"},
|
||||
Usage: "List, create and update issues",
|
||||
Description: "List, create and update issues",
|
||||
ArgsUsage: "[<issue index>]",
|
||||
Action: runIssues,
|
||||
Subcommands: []cli.Command{
|
||||
CmdIssuesList,
|
||||
CmdIssuesCreate,
|
||||
Subcommands: []*cli.Command{
|
||||
&issues.CmdIssuesList,
|
||||
&issues.CmdIssuesCreate,
|
||||
&issues.CmdIssuesReopen,
|
||||
&issues.CmdIssuesClose,
|
||||
},
|
||||
Flags: AllDefaultFlags,
|
||||
}
|
||||
|
||||
// CmdIssuesList represents a sub command of issues to list issues
|
||||
var CmdIssuesList = cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "List issues of the repository",
|
||||
Description: `List issues of the repository`,
|
||||
Action: runIssuesList,
|
||||
Flags: AllDefaultFlags,
|
||||
Flags: flags.IssuePRFlags,
|
||||
}
|
||||
|
||||
func runIssues(ctx *cli.Context) error {
|
||||
if len(os.Args) == 3 {
|
||||
return runIssueDetail(ctx, os.Args[2])
|
||||
if ctx.Args().Len() == 1 {
|
||||
return runIssueDetail(ctx.Args().First())
|
||||
}
|
||||
return runIssuesList(ctx)
|
||||
return issues.RunIssuesList(ctx)
|
||||
}
|
||||
|
||||
func runIssueDetail(ctx *cli.Context, index string) error {
|
||||
login, owner, repo := initCommand()
|
||||
func runIssueDetail(index string) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
if strings.HasPrefix(index, "#") {
|
||||
index = index[1:]
|
||||
}
|
||||
|
||||
idx, err := strconv.ParseInt(index, 10, 64)
|
||||
idx, err := utils.ArgToIndex(index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issue, err := login.Client().GetIssue(owner, repo, idx)
|
||||
issue, _, err := login.Client().GetIssue(owner, repo, idx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("#%d %s\n%s created %s\n\n%s", issue.Index,
|
||||
issue.Title,
|
||||
issue.Poster.UserName,
|
||||
issue.Created.Format("2006-01-02 15:04:05"),
|
||||
issue.Body,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runIssuesList(ctx *cli.Context) error {
|
||||
login, owner, repo := initCommand()
|
||||
|
||||
issues, err := login.Client().ListRepoIssues(owner, repo, gitea.ListIssueOption{
|
||||
Page: 0,
|
||||
State: string(gitea.StateOpen),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
headers := []string{
|
||||
"Index",
|
||||
"Name",
|
||||
"Updated",
|
||||
"Title",
|
||||
}
|
||||
|
||||
var values [][]string
|
||||
|
||||
if len(issues) == 0 {
|
||||
Output(outputValue, headers, values)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
name := issue.Poster.FullName
|
||||
if len(name) == 0 {
|
||||
name = issue.Poster.UserName
|
||||
}
|
||||
values = append(
|
||||
values,
|
||||
[]string{
|
||||
strconv.FormatInt(issue.Index, 10),
|
||||
name,
|
||||
issue.Updated.Format("2006-01-02 15:04:05"),
|
||||
issue.Title,
|
||||
},
|
||||
)
|
||||
}
|
||||
Output(outputValue, headers, values)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdIssuesCreate represents a sub command of issues to create issue
|
||||
var CmdIssuesCreate = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create an issue on repository",
|
||||
Description: `Create an issue on repository`,
|
||||
Action: runIssuesCreate,
|
||||
Flags: append([]cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "title, t",
|
||||
Usage: "issue title to create",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "body, b",
|
||||
Usage: "issue body to create",
|
||||
},
|
||||
}, LoginRepoFlags...),
|
||||
}
|
||||
|
||||
func runIssuesCreate(ctx *cli.Context) error {
|
||||
login, owner, repo := initCommand()
|
||||
|
||||
_, err := login.Client().CreateIssue(owner, repo, gitea.CreateIssueOption{
|
||||
Title: ctx.String("title"),
|
||||
Body: ctx.String("body"),
|
||||
// TODO:
|
||||
//Assignee string `json:"assignee"`
|
||||
//Assignees []string `json:"assignees"`
|
||||
//Deadline *time.Time `json:"due_date"`
|
||||
//Milestone int64 `json:"milestone"`
|
||||
//Labels []int64 `json:"labels"`
|
||||
//Closed bool `json:"closed"`
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
print.IssueDetails(issue)
|
||||
return nil
|
||||
}
|
||||
|
51
cmd/issues/close.go
Normal file
51
cmd/issues/close.go
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package issues
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdIssuesClose represents a sub command of issues to close an issue
|
||||
var CmdIssuesClose = cli.Command{
|
||||
Name: "close",
|
||||
Usage: "Change state of an issue to 'closed'",
|
||||
Description: `Change state of an issue to 'closed'`,
|
||||
ArgsUsage: "<issue index>",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
var s = gitea.StateClosed
|
||||
return editIssueState(ctx, gitea.EditIssueOption{State: &s})
|
||||
},
|
||||
Flags: flags.AllDefaultFlags,
|
||||
}
|
||||
|
||||
// editIssueState abstracts the arg parsing to edit the given issue
|
||||
func editIssueState(ctx *cli.Context, opts gitea.EditIssueOption) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
if ctx.Args().Len() == 0 {
|
||||
log.Fatal(ctx.Command.ArgsUsage)
|
||||
}
|
||||
|
||||
index, err := utils.ArgToIndex(ctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issue, _, err := login.Client().EditIssue(owner, repo, index, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print.IssueDetails(issue)
|
||||
return nil
|
||||
}
|
61
cmd/issues/create.go
Normal file
61
cmd/issues/create.go
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package issues
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// CmdIssuesCreate represents a sub command of issues to create issue
|
||||
var CmdIssuesCreate = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create an issue on repository",
|
||||
Description: `Create an issue on repository`,
|
||||
Action: runIssuesCreate,
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "title",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "issue title to create",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "body",
|
||||
Aliases: []string{"b"},
|
||||
Usage: "issue body to create",
|
||||
},
|
||||
}, flags.LoginRepoFlags...),
|
||||
}
|
||||
|
||||
func runIssuesCreate(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
issue, _, err := login.Client().CreateIssue(owner, repo, gitea.CreateIssueOption{
|
||||
Title: ctx.String("title"),
|
||||
Body: ctx.String("body"),
|
||||
// TODO:
|
||||
//Assignee string `json:"assignee"`
|
||||
//Assignees []string `json:"assignees"`
|
||||
//Deadline *time.Time `json:"due_date"`
|
||||
//Milestone int64 `json:"milestone"`
|
||||
//Labels []int64 `json:"labels"`
|
||||
//Closed bool `json:"closed"`
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
print.IssueDetails(issue)
|
||||
fmt.Println(issue.HTMLURL)
|
||||
return nil
|
||||
}
|
54
cmd/issues/list.go
Normal file
54
cmd/issues/list.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 issues
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// CmdIssuesList represents a sub command of issues to list issues
|
||||
var CmdIssuesList = cli.Command{
|
||||
Name: "ls",
|
||||
Aliases: []string{"list"},
|
||||
Usage: "List issues of the repository",
|
||||
Description: `List issues of the repository`,
|
||||
Action: RunIssuesList,
|
||||
Flags: flags.IssuePRFlags,
|
||||
}
|
||||
|
||||
// RunIssuesList list issues
|
||||
func RunIssuesList(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
state := gitea.StateOpen
|
||||
switch ctx.String("state") {
|
||||
case "all":
|
||||
state = gitea.StateAll
|
||||
case "open":
|
||||
state = gitea.StateOpen
|
||||
case "closed":
|
||||
state = gitea.StateClosed
|
||||
}
|
||||
|
||||
issues, _, err := login.Client().ListRepoIssues(owner, repo, gitea.ListIssueOption{
|
||||
ListOptions: flags.GetListOptions(ctx),
|
||||
State: state,
|
||||
Type: gitea.IssueTypeIssue,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
print.IssuesList(issues, flags.GlobalOutputValue)
|
||||
return nil
|
||||
}
|
26
cmd/issues/reopen.go
Normal file
26
cmd/issues/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 issues
|
||||
|
||||
import (
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdIssuesReopen represents a sub command of issues to open an issue
|
||||
var CmdIssuesReopen = cli.Command{
|
||||
Name: "reopen",
|
||||
Aliases: []string{"open"},
|
||||
Usage: "Change state of an issue to 'open'",
|
||||
Description: `Change state of an issue to 'open'`,
|
||||
ArgsUsage: "<issue index>",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
var s = gitea.StateOpen
|
||||
return editIssueState(ctx, gitea.EditIssueOption{State: &s})
|
||||
},
|
||||
Flags: flags.AllDefaultFlags,
|
||||
}
|
258
cmd/labels.go
258
cmd/labels.go
@ -5,261 +5,35 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"code.gitea.io/tea/cmd/labels"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdLabels represents to operate repositories' labels.
|
||||
var CmdLabels = cli.Command{
|
||||
Name: "labels",
|
||||
Usage: "Operate with labels of the repository",
|
||||
Description: `Operate with labels of the repository`,
|
||||
Aliases: []string{"label"},
|
||||
Usage: "Manage issue labels",
|
||||
Description: `Manage issue labels`,
|
||||
Action: runLabels,
|
||||
Subcommands: []cli.Command{
|
||||
CmdLabelCreate,
|
||||
CmdLabelUpdate,
|
||||
CmdLabelDelete,
|
||||
Subcommands: []*cli.Command{
|
||||
&labels.CmdLabelsList,
|
||||
&labels.CmdLabelCreate,
|
||||
&labels.CmdLabelUpdate,
|
||||
&labels.CmdLabelDelete,
|
||||
},
|
||||
Flags: append([]cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "save, s",
|
||||
Usage: "Save all the labels as a file",
|
||||
},
|
||||
}, AllDefaultFlags...),
|
||||
}
|
||||
|
||||
func runLabels(ctx *cli.Context) error {
|
||||
login, owner, repo := initCommand()
|
||||
|
||||
headers := []string{
|
||||
"Index",
|
||||
"Color",
|
||||
"Name",
|
||||
"Description",
|
||||
}
|
||||
|
||||
var values [][]string
|
||||
|
||||
labels, err := login.Client().ListRepoLabels(owner, repo)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(labels) == 0 {
|
||||
Output(outputValue, headers, values)
|
||||
return nil
|
||||
}
|
||||
|
||||
fPath := ctx.String("save")
|
||||
if len(fPath) > 0 {
|
||||
f, err := os.Create(fPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
for _, label := range labels {
|
||||
fmt.Fprintf(f, "#%s %s\n", label.Color, label.Name)
|
||||
}
|
||||
} else {
|
||||
for _, label := range labels {
|
||||
values = append(
|
||||
values,
|
||||
[]string{
|
||||
strconv.FormatInt(label.ID, 10),
|
||||
label.Color,
|
||||
label.Name,
|
||||
label.Description,
|
||||
},
|
||||
)
|
||||
}
|
||||
Output(outputValue, headers, values)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdLabelCreate represents a sub command of labels to create label.
|
||||
var CmdLabelCreate = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a label in repository",
|
||||
Description: `Create a label in repository`,
|
||||
Action: runLabelCreate,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "label name",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "color",
|
||||
Usage: "label color value",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "description",
|
||||
Usage: "label description",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "file",
|
||||
Usage: "indicate a label file",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func splitLabelLine(line string) (string, string, string) {
|
||||
fields := strings.SplitN(line, ";", 2)
|
||||
var color, name, description string
|
||||
if len(fields) < 1 {
|
||||
return "", "", ""
|
||||
} else if len(fields) >= 2 {
|
||||
description = strings.TrimSpace(fields[1])
|
||||
}
|
||||
fields = strings.Fields(fields[0])
|
||||
if len(fields) <= 0 {
|
||||
return "", "", ""
|
||||
}
|
||||
color = fields[0]
|
||||
if len(fields) == 2 {
|
||||
name = fields[1]
|
||||
} else if len(fields) > 2 {
|
||||
name = strings.Join(fields[1:], " ")
|
||||
}
|
||||
return color, name, description
|
||||
}
|
||||
|
||||
func runLabelCreate(ctx *cli.Context) error {
|
||||
login, owner, repo := initCommand()
|
||||
|
||||
labelFile := ctx.String("file")
|
||||
var err error
|
||||
if len(labelFile) == 0 {
|
||||
_, err = login.Client().CreateLabel(owner, repo, gitea.CreateLabelOption{
|
||||
Name: ctx.String("name"),
|
||||
Color: ctx.String("color"),
|
||||
Description: ctx.String("description"),
|
||||
})
|
||||
} else {
|
||||
f, err := os.Open(labelFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
var i = 1
|
||||
// FIXME: if Gitea's API support create multiple labels once, we should move to that API.
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
color, name, description := splitLabelLine(line)
|
||||
if color == "" || name == "" {
|
||||
log.Printf("Line %d ignored because lack of enough fields: %s\n", i, line)
|
||||
} else {
|
||||
_, err = login.Client().CreateLabel(owner, repo, gitea.CreateLabelOption{
|
||||
Name: name,
|
||||
Color: color,
|
||||
Description: description,
|
||||
})
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdLabelUpdate represents a sub command of labels to update label.
|
||||
var CmdLabelUpdate = cli.Command{
|
||||
Name: "update",
|
||||
Usage: "Update a label in repository",
|
||||
Description: `Update a label in repository`,
|
||||
Action: runLabelUpdate,
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "id",
|
||||
Usage: "label id",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "label name",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "color",
|
||||
Usage: "label color value",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "description",
|
||||
Usage: "label description",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runLabelUpdate(ctx *cli.Context) error {
|
||||
login, owner, repo := initCommand()
|
||||
|
||||
id := ctx.Int64("id")
|
||||
var pName, pColor, pDescription *string
|
||||
name := ctx.String("name")
|
||||
if name != "" {
|
||||
pName = &name
|
||||
}
|
||||
|
||||
color := ctx.String("color")
|
||||
if color != "" {
|
||||
pColor = &color
|
||||
}
|
||||
|
||||
description := ctx.String("description")
|
||||
if description != "" {
|
||||
pDescription = &description
|
||||
}
|
||||
|
||||
var err error
|
||||
_, err = login.Client().EditLabel(owner, repo, id, gitea.EditLabelOption{
|
||||
Name: pName,
|
||||
Color: pColor,
|
||||
Description: pDescription,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdLabelDelete represents a sub command of labels to delete label.
|
||||
var CmdLabelDelete = cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete a label in repository",
|
||||
Description: `Delete a label in repository`,
|
||||
Action: runLabelCreate,
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "id",
|
||||
Usage: "label id",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runLabelDelete(ctx *cli.Context) error {
|
||||
login, owner, repo := initCommand()
|
||||
|
||||
err := login.Client().DeleteLabel(owner, repo, ctx.Int64("id"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
if ctx.Args().Len() == 1 {
|
||||
return runLabelsDetails(ctx)
|
||||
}
|
||||
return labels.RunLabelsList(ctx)
|
||||
}
|
||||
|
||||
func runLabelsDetails(ctx *cli.Context) error {
|
||||
log.Fatal("Not yet implemented.")
|
||||
return nil
|
||||
}
|
||||
|
109
cmd/labels/create.go
Normal file
109
cmd/labels/create.go
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package labels
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdLabelCreate represents a sub command of labels to create label.
|
||||
var CmdLabelCreate = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a label",
|
||||
Description: `Create a label`,
|
||||
Action: runLabelCreate,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "label name",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "color",
|
||||
Usage: "label color value",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "description",
|
||||
Usage: "label description",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "file",
|
||||
Usage: "indicate a label file",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runLabelCreate(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
labelFile := ctx.String("file")
|
||||
var err error
|
||||
if len(labelFile) == 0 {
|
||||
_, _, err = login.Client().CreateLabel(owner, repo, gitea.CreateLabelOption{
|
||||
Name: ctx.String("name"),
|
||||
Color: ctx.String("color"),
|
||||
Description: ctx.String("description"),
|
||||
})
|
||||
} else {
|
||||
f, err := os.Open(labelFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
var i = 1
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
color, name, description := splitLabelLine(line)
|
||||
if color == "" || name == "" {
|
||||
log.Printf("Line %d ignored because lack of enough fields: %s\n", i, line)
|
||||
} else {
|
||||
_, _, err = login.Client().CreateLabel(owner, repo, gitea.CreateLabelOption{
|
||||
Name: name,
|
||||
Color: color,
|
||||
Description: description,
|
||||
})
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitLabelLine(line string) (string, string, string) {
|
||||
fields := strings.SplitN(line, ";", 2)
|
||||
var color, name, description string
|
||||
if len(fields) < 1 {
|
||||
return "", "", ""
|
||||
} else if len(fields) >= 2 {
|
||||
description = strings.TrimSpace(fields[1])
|
||||
}
|
||||
fields = strings.Fields(fields[0])
|
||||
if len(fields) <= 0 {
|
||||
return "", "", ""
|
||||
}
|
||||
color = fields[0]
|
||||
if len(fields) == 2 {
|
||||
name = fields[1]
|
||||
} else if len(fields) > 2 {
|
||||
name = strings.Join(fields[1:], " ")
|
||||
}
|
||||
return color, name, description
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
package labels
|
||||
|
||||
import (
|
||||
"bufio"
|
39
cmd/labels/delete.go
Normal file
39
cmd/labels/delete.go
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package labels
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdLabelDelete represents a sub command of labels to delete label.
|
||||
var CmdLabelDelete = cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete a label",
|
||||
Description: `Delete a label`,
|
||||
Action: runLabelDelete,
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "id",
|
||||
Usage: "label id",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runLabelDelete(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
_, err := login.Client().DeleteLabel(owner, repo, ctx.Int64("id"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
52
cmd/labels/list.go
Normal file
52
cmd/labels/list.go
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package labels
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
"code.gitea.io/tea/modules/task"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdLabelsList represents a sub command of labels to list labels
|
||||
var CmdLabelsList = cli.Command{
|
||||
Name: "ls",
|
||||
Aliases: []string{"list"},
|
||||
Usage: "List labels",
|
||||
Description: "List labels",
|
||||
Action: RunLabelsList,
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "save",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Save all the labels as a file",
|
||||
},
|
||||
&flags.PaginationPageFlag,
|
||||
&flags.PaginationLimitFlag,
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
||||
|
||||
// RunLabelsList list labels.
|
||||
func RunLabelsList(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
labels, _, err := login.Client().ListRepoLabels(owner, repo, gitea.ListLabelsOptions{ListOptions: flags.GetListOptions(ctx)})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if ctx.IsSet("save") {
|
||||
return task.LabelsExport(labels, ctx.String("save"))
|
||||
}
|
||||
|
||||
print.LabelsList(labels, flags.GlobalOutputValue)
|
||||
return nil
|
||||
}
|
75
cmd/labels/update.go
Normal file
75
cmd/labels/update.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package labels
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdLabelUpdate represents a sub command of labels to update label.
|
||||
var CmdLabelUpdate = cli.Command{
|
||||
Name: "update",
|
||||
Usage: "Update a label",
|
||||
Description: `Update a label`,
|
||||
Action: runLabelUpdate,
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "id",
|
||||
Usage: "label id",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "label name",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "color",
|
||||
Usage: "label color value",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "description",
|
||||
Usage: "label description",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runLabelUpdate(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
id := ctx.Int64("id")
|
||||
var pName, pColor, pDescription *string
|
||||
name := ctx.String("name")
|
||||
if name != "" {
|
||||
pName = &name
|
||||
}
|
||||
|
||||
color := ctx.String("color")
|
||||
if color != "" {
|
||||
pColor = &color
|
||||
}
|
||||
|
||||
description := ctx.String("description")
|
||||
if description != "" {
|
||||
pDescription = &description
|
||||
}
|
||||
|
||||
var err error
|
||||
_, _, err = login.Client().EditLabel(owner, repo, id, gitea.EditLabelOption{
|
||||
Name: pName,
|
||||
Color: pColor,
|
||||
Description: pDescription,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
142
cmd/login.go
142
cmd/login.go
@ -1,135 +1,51 @@
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/cmd/login"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdLogin represents to login a gitea server.
|
||||
var CmdLogin = cli.Command{
|
||||
Name: "login",
|
||||
Usage: "Log in a Gitea server",
|
||||
Description: `Log in a Gitea server`,
|
||||
Action: runLoginList,
|
||||
Subcommands: []cli.Command{
|
||||
cmdLoginList,
|
||||
cmdLoginAdd,
|
||||
Name: "logins",
|
||||
Aliases: []string{"login"},
|
||||
Usage: "Log in to a Gitea server",
|
||||
Description: `Log in to a Gitea server`,
|
||||
ArgsUsage: "[<login name>]",
|
||||
Action: runLogins,
|
||||
Subcommands: []*cli.Command{
|
||||
&login.CmdLoginList,
|
||||
&login.CmdLoginAdd,
|
||||
&login.CmdLoginEdit,
|
||||
&login.CmdLoginDelete,
|
||||
&login.CmdLoginSetDefault,
|
||||
},
|
||||
}
|
||||
|
||||
// CmdLogin represents to login a gitea server.
|
||||
var cmdLoginAdd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "Add a Login of a Gitea server",
|
||||
Description: `Add a Login of a Gitea server`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Usage: "Name for the gitea login",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "url, u",
|
||||
Value: "https://try.gitea.io",
|
||||
EnvVar: "GITEA_SERVER_URL",
|
||||
Usage: "Gitea server URL",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "token, t",
|
||||
Value: "",
|
||||
EnvVar: "GITEA_SERVER_TOKEN",
|
||||
Usage: "token for operating the Gitea login",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "insecure, i",
|
||||
Usage: "insecure visit gitea server",
|
||||
},
|
||||
},
|
||||
Action: runLoginAdd,
|
||||
func runLogins(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() == 1 {
|
||||
return runLoginDetail(ctx.Args().First())
|
||||
}
|
||||
return login.RunLoginList(ctx)
|
||||
}
|
||||
|
||||
func runLoginAdd(ctx *cli.Context) error {
|
||||
if !ctx.IsSet("url") {
|
||||
log.Fatal("You have to input Gitea server URL")
|
||||
}
|
||||
|
||||
if !ctx.IsSet("token") {
|
||||
log.Fatal("No token found")
|
||||
}
|
||||
|
||||
if !ctx.IsSet("name") {
|
||||
log.Fatal("You have to set a name for the login")
|
||||
}
|
||||
|
||||
err := loadConfig(yamlConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal("load config file failed", yamlConfigPath)
|
||||
}
|
||||
|
||||
client := gitea.NewClient(ctx.String("url"), ctx.String("token"))
|
||||
if ctx.Bool("insecure") {
|
||||
cookieJar, _ := cookiejar.New(nil)
|
||||
|
||||
client.SetHTTPClient(&http.Client{
|
||||
Jar: cookieJar,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
})
|
||||
}
|
||||
u, err := client.GetMyUserInfo()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("Login successful! Login name", u.UserName)
|
||||
|
||||
err = addLogin(Login{
|
||||
Name: ctx.String("name"),
|
||||
URL: ctx.String("url"),
|
||||
Token: ctx.String("token"),
|
||||
Insecure: ctx.Bool("insecure"),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = saveConfig(yamlConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdLogin represents to login a gitea server.
|
||||
var cmdLoginList = cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "List all Logins of Gitea servers",
|
||||
Description: `List all Logins of Gitea servers`,
|
||||
Action: runLoginList,
|
||||
}
|
||||
|
||||
func runLoginList(ctx *cli.Context) error {
|
||||
err := loadConfig(yamlConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal("load config file failed", yamlConfigPath)
|
||||
}
|
||||
|
||||
fmt.Printf("Name\tURL\tSSHHost\n")
|
||||
for _, l := range config.Logins {
|
||||
fmt.Printf("%s\t%s\t%s\n", l.Name, l.URL, l.GetSSHHost())
|
||||
func runLoginDetail(name string) error {
|
||||
l := config.GetLoginByName(name)
|
||||
if l == nil {
|
||||
fmt.Printf("Login '%s' do not exist\n\n", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
print.LoginDetails(l, flags.GlobalOutputValue)
|
||||
return nil
|
||||
}
|
||||
|
81
cmd/login/add.go
Normal file
81
cmd/login/add.go
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package login
|
||||
|
||||
import (
|
||||
"code.gitea.io/tea/modules/interact"
|
||||
"code.gitea.io/tea/modules/task"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdLoginAdd represents to login a gitea server.
|
||||
var CmdLoginAdd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "Add a Gitea login",
|
||||
Description: `Add a Gitea login, without args it will create one interactively`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "Login name",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "url",
|
||||
Aliases: []string{"u"},
|
||||
Value: "https://gitea.com",
|
||||
EnvVars: []string{"GITEA_SERVER_URL"},
|
||||
Usage: "Server URL",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "token",
|
||||
Aliases: []string{"t"},
|
||||
Value: "",
|
||||
EnvVars: []string{"GITEA_SERVER_TOKEN"},
|
||||
Usage: "Access token. Can be obtained from Settings > Applications",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "user",
|
||||
Value: "",
|
||||
EnvVars: []string{"GITEA_SERVER_USER"},
|
||||
Usage: "User for basic auth (will create token)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "password",
|
||||
Aliases: []string{"pwd"},
|
||||
Value: "",
|
||||
EnvVars: []string{"GITEA_SERVER_PASSWORD"},
|
||||
Usage: "Password for basic auth (will create token)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "ssh-key",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Path to a SSH key to use, overrides auto-discovery",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "insecure",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "Disable TLS verification",
|
||||
},
|
||||
},
|
||||
Action: runLoginAdd,
|
||||
}
|
||||
|
||||
func runLoginAdd(ctx *cli.Context) error {
|
||||
// if no args create login interactive
|
||||
if ctx.NumFlags() == 0 {
|
||||
return interact.CreateLogin()
|
||||
}
|
||||
|
||||
// else use args to add login
|
||||
return task.CreateLogin(
|
||||
ctx.String("name"),
|
||||
ctx.String("token"),
|
||||
ctx.String("user"),
|
||||
ctx.String("password"),
|
||||
ctx.String("ssh-key"),
|
||||
ctx.String("url"),
|
||||
ctx.Bool("insecure"))
|
||||
}
|
38
cmd/login/default.go
Normal file
38
cmd/login/default.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 login
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdLoginSetDefault represents to login a gitea server.
|
||||
var CmdLoginSetDefault = cli.Command{
|
||||
Name: "default",
|
||||
Usage: "Get or Set Default Login",
|
||||
Description: `Get or Set Default Login`,
|
||||
ArgsUsage: "<Login>",
|
||||
Action: runLoginSetDefault,
|
||||
Flags: []cli.Flag{&flags.OutputFlag},
|
||||
}
|
||||
|
||||
func runLoginSetDefault(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() == 0 {
|
||||
l, err := config.GetDefaultLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Default Login: %s\n", l.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
name := ctx.Args().First()
|
||||
return config.SetDefaultLogin(name)
|
||||
}
|
44
cmd/login/delete.go
Normal file
44
cmd/login/delete.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 login
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"code.gitea.io/tea/modules/config"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdLoginDelete is a command to delete a login
|
||||
var CmdLoginDelete = cli.Command{
|
||||
Name: "delete",
|
||||
Aliases: []string{"rm"},
|
||||
Usage: "Remove a Gitea login",
|
||||
Description: `Remove a Gitea login`,
|
||||
ArgsUsage: "<login name>",
|
||||
Action: RunLoginDelete,
|
||||
}
|
||||
|
||||
// RunLoginDelete runs the action of a login delete command
|
||||
func RunLoginDelete(ctx *cli.Context) error {
|
||||
logins, err := config.GetLogins()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var name string
|
||||
|
||||
if len(ctx.Args().First()) != 0 {
|
||||
name = ctx.Args().First()
|
||||
} else if len(logins) == 1 {
|
||||
name = logins[0].Name
|
||||
} else {
|
||||
return errors.New("Please specify a login name")
|
||||
}
|
||||
|
||||
return config.DeleteLogin(name)
|
||||
}
|
26
cmd/login/edit.go
Normal file
26
cmd/login/edit.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 login
|
||||
|
||||
import (
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdLoginEdit represents to login a gitea server.
|
||||
var CmdLoginEdit = cli.Command{
|
||||
Name: "edit",
|
||||
Usage: "Edit Gitea logins",
|
||||
Description: `Edit Gitea logins`,
|
||||
Action: runLoginEdit,
|
||||
Flags: []cli.Flag{&flags.OutputFlag},
|
||||
}
|
||||
|
||||
func runLoginEdit(_ *cli.Context) error {
|
||||
return open.Start(config.GetConfigPath())
|
||||
}
|
36
cmd/login/list.go
Normal file
36
cmd/login/list.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package login
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdLoginList represents to login a gitea server.
|
||||
var CmdLoginList = cli.Command{
|
||||
Name: "ls",
|
||||
Aliases: []string{"list"},
|
||||
Usage: "List Gitea logins",
|
||||
Description: `List Gitea logins`,
|
||||
Action: RunLoginList,
|
||||
Flags: []cli.Flag{&flags.OutputFlag},
|
||||
}
|
||||
|
||||
// RunLoginList list all logins
|
||||
func RunLoginList(_ *cli.Context) error {
|
||||
logins, err := config.GetLogins()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
print.LoginsList(logins, flags.GlobalOutputValue)
|
||||
return nil
|
||||
}
|
@ -1,15 +1,13 @@
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"code.gitea.io/tea/cmd/login"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdLogout represents to logout a gitea server.
|
||||
@ -17,44 +15,6 @@ var CmdLogout = cli.Command{
|
||||
Name: "logout",
|
||||
Usage: "Log out from a Gitea server",
|
||||
Description: `Log out from a Gitea server`,
|
||||
Action: runLogout,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Usage: "name wants to log out",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runLogout(ctx *cli.Context) error {
|
||||
var name string
|
||||
if len(os.Args) == 3 {
|
||||
name = os.Args[2]
|
||||
} else if ctx.IsSet("name") {
|
||||
name = ctx.String("name")
|
||||
} else {
|
||||
return errors.New("need log out server name")
|
||||
}
|
||||
|
||||
err := loadConfig(yamlConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal("load config file failed", yamlConfigPath)
|
||||
}
|
||||
|
||||
var idx = -1
|
||||
for i, l := range config.Logins {
|
||||
if l.Name == name {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx > -1 {
|
||||
config.Logins = append(config.Logins[:idx], config.Logins[idx+1:]...)
|
||||
err = saveConfig(yamlConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal("save config file failed", yamlConfigPath)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
ArgsUsage: "<login name>",
|
||||
Action: login.RunLoginDelete,
|
||||
}
|
||||
|
53
cmd/milestones.go
Normal file
53
cmd/milestones.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/cmd/milestones"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdMilestones represents to operate repositories milestones.
|
||||
var CmdMilestones = cli.Command{
|
||||
Name: "milestones",
|
||||
Aliases: []string{"milestone", "ms"},
|
||||
Usage: "List and create milestones",
|
||||
Description: `List and create milestones`,
|
||||
ArgsUsage: "[<milestone name>]",
|
||||
Action: runMilestones,
|
||||
Subcommands: []*cli.Command{
|
||||
&milestones.CmdMilestonesList,
|
||||
&milestones.CmdMilestonesCreate,
|
||||
&milestones.CmdMilestonesClose,
|
||||
&milestones.CmdMilestonesDelete,
|
||||
&milestones.CmdMilestonesReopen,
|
||||
&milestones.CmdMilestonesIssues,
|
||||
},
|
||||
Flags: flags.AllDefaultFlags,
|
||||
}
|
||||
|
||||
func runMilestones(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() == 1 {
|
||||
return runMilestoneDetail(ctx.Args().First())
|
||||
}
|
||||
return milestones.RunMilestonesList(ctx)
|
||||
}
|
||||
|
||||
func runMilestoneDetail(name string) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
|
||||
milestone, _, err := client.GetMilestoneByName(owner, repo, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print.MilestoneDetails(milestone)
|
||||
return nil
|
||||
}
|
32
cmd/milestones/close.go
Normal file
32
cmd/milestones/close.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package milestones
|
||||
|
||||
import (
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdMilestonesClose represents a sub command of milestones to close an milestone
|
||||
var CmdMilestonesClose = cli.Command{
|
||||
Name: "close",
|
||||
Usage: "Change state of an milestone to 'closed'",
|
||||
Description: `Change state of an milestone to 'closed'`,
|
||||
ArgsUsage: "<milestone name>",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
if ctx.Bool("force") {
|
||||
return deleteMilestone(ctx)
|
||||
}
|
||||
return editMilestoneStatus(ctx, true)
|
||||
},
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "force",
|
||||
Aliases: []string{"f"},
|
||||
Usage: "delete milestone",
|
||||
},
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
69
cmd/milestones/create.go
Normal file
69
cmd/milestones/create.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package milestones
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// CmdMilestonesCreate represents a sub command of milestones to create milestone
|
||||
var CmdMilestonesCreate = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create an milestone on repository",
|
||||
Description: `Create an milestone on repository`,
|
||||
Action: runMilestonesCreate,
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "title",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "milestone title to create",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "description",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "milestone description to create",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "state",
|
||||
Usage: "set milestone state (default is open)",
|
||||
DefaultText: "open",
|
||||
},
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
||||
|
||||
func runMilestonesCreate(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
title := ctx.String("title")
|
||||
if len(title) == 0 {
|
||||
fmt.Printf("Title is required\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
state := gitea.StateOpen
|
||||
if ctx.String("state") == "closed" {
|
||||
state = gitea.StateClosed
|
||||
}
|
||||
|
||||
mile, _, err := login.Client().CreateMilestone(owner, repo, gitea.CreateMilestoneOption{
|
||||
Title: title,
|
||||
Description: ctx.String("description"),
|
||||
State: state,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
print.MilestoneDetails(mile)
|
||||
return nil
|
||||
}
|
31
cmd/milestones/delete.go
Normal file
31
cmd/milestones/delete.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package milestones
|
||||
|
||||
import (
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdMilestonesDelete represents a sub command of milestones to delete an milestone
|
||||
var CmdMilestonesDelete = cli.Command{
|
||||
Name: "delete",
|
||||
Aliases: []string{"rm"},
|
||||
Usage: "delete a milestone",
|
||||
Description: "delete a milestone",
|
||||
ArgsUsage: "<milestone name>",
|
||||
Action: deleteMilestone,
|
||||
Flags: flags.AllDefaultFlags,
|
||||
}
|
||||
|
||||
func deleteMilestone(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
|
||||
_, err := client.DeleteMilestoneByName(owner, repo, ctx.Args().First())
|
||||
return err
|
||||
}
|
171
cmd/milestones/issues.go
Normal file
171
cmd/milestones/issues.go
Normal file
@ -0,0 +1,171 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package milestones
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdMilestonesIssues represents a sub command of milestones to manage issue/pull of an milestone
|
||||
var CmdMilestonesIssues = cli.Command{
|
||||
Name: "issues",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "manage issue/pull of an milestone",
|
||||
Description: "manage issue/pull of an milestone",
|
||||
ArgsUsage: "<milestone name>",
|
||||
Action: runMilestoneIssueList,
|
||||
Subcommands: []*cli.Command{
|
||||
&CmdMilestoneAddIssue,
|
||||
&CmdMilestoneRemoveIssue,
|
||||
},
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "state",
|
||||
Usage: "Filter by issue state (all|open|closed)",
|
||||
DefaultText: "open",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "kind",
|
||||
Usage: "Filter by kind (issue|pull)",
|
||||
},
|
||||
&flags.PaginationPageFlag,
|
||||
&flags.PaginationLimitFlag,
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
||||
|
||||
// CmdMilestoneAddIssue represents a sub command of milestone issues to add an issue/pull to an milestone
|
||||
var CmdMilestoneAddIssue = cli.Command{
|
||||
Name: "add",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Add an issue/pull to an milestone",
|
||||
Description: "Add an issue/pull to an milestone",
|
||||
ArgsUsage: "<milestone name> <issue/pull index>",
|
||||
Action: runMilestoneIssueAdd,
|
||||
Flags: flags.AllDefaultFlags,
|
||||
}
|
||||
|
||||
// CmdMilestoneRemoveIssue represents a sub command of milestones to remove an issue/pull from an milestone
|
||||
var CmdMilestoneRemoveIssue = cli.Command{
|
||||
Name: "remove",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "Remove an issue/pull to an milestone",
|
||||
Description: "Remove an issue/pull to an milestone",
|
||||
ArgsUsage: "<milestone name> <issue/pull index>",
|
||||
Action: runMilestoneIssueRemove,
|
||||
Flags: flags.AllDefaultFlags,
|
||||
}
|
||||
|
||||
func runMilestoneIssueList(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
|
||||
state := gitea.StateOpen
|
||||
switch ctx.String("state") {
|
||||
case "all":
|
||||
state = gitea.StateAll
|
||||
case "closed":
|
||||
state = gitea.StateClosed
|
||||
}
|
||||
|
||||
kind := gitea.IssueTypeAll
|
||||
switch ctx.String("kind") {
|
||||
case "issue":
|
||||
kind = gitea.IssueTypeIssue
|
||||
case "pull":
|
||||
kind = gitea.IssueTypePull
|
||||
}
|
||||
|
||||
if ctx.Args().Len() != 1 {
|
||||
return fmt.Errorf("Must specify milestone name")
|
||||
}
|
||||
|
||||
milestone := ctx.Args().First()
|
||||
// make sure milestone exist
|
||||
_, _, err := client.GetMilestoneByName(owner, repo, milestone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issues, _, err := client.ListRepoIssues(owner, repo, gitea.ListIssueOption{
|
||||
ListOptions: flags.GetListOptions(ctx),
|
||||
Milestones: []string{milestone},
|
||||
Type: kind,
|
||||
State: state,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print.IssuesPullsList(issues, flags.GlobalOutputValue)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runMilestoneIssueAdd(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
if ctx.Args().Len() != 2 {
|
||||
return fmt.Errorf("need two arguments")
|
||||
}
|
||||
|
||||
mileName := ctx.Args().Get(0)
|
||||
issueIndex := ctx.Args().Get(1)
|
||||
idx, err := utils.ArgToIndex(issueIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make sure milestone exist
|
||||
mile, _, err := client.GetMilestoneByName(owner, repo, mileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = client.EditIssue(owner, repo, idx, gitea.EditIssueOption{
|
||||
Milestone: &mile.ID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func runMilestoneIssueRemove(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
if ctx.Args().Len() != 2 {
|
||||
return fmt.Errorf("need two arguments")
|
||||
}
|
||||
|
||||
mileName := ctx.Args().Get(0)
|
||||
issueIndex := ctx.Args().Get(1)
|
||||
idx, err := utils.ArgToIndex(issueIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issue, _, err := client.GetIssue(owner, repo, idx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if issue.Milestone == nil {
|
||||
return fmt.Errorf("issue is not assigned to a milestone")
|
||||
}
|
||||
|
||||
if issue.Milestone.Title != mileName {
|
||||
return fmt.Errorf("issue is not assigned to this milestone")
|
||||
}
|
||||
|
||||
zero := int64(0)
|
||||
_, _, err = client.EditIssue(owner, repo, idx, gitea.EditIssueOption{
|
||||
Milestone: &zero,
|
||||
})
|
||||
return err
|
||||
}
|
59
cmd/milestones/list.go
Normal file
59
cmd/milestones/list.go
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package milestones
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// CmdMilestonesList represents a sub command of milestones to list milestones
|
||||
var CmdMilestonesList = cli.Command{
|
||||
Name: "ls",
|
||||
Aliases: []string{"list"},
|
||||
Usage: "List milestones of the repository",
|
||||
Description: `List milestones of the repository`,
|
||||
Action: RunMilestonesList,
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "state",
|
||||
Usage: "Filter by milestone state (all|open|closed)",
|
||||
DefaultText: "open",
|
||||
},
|
||||
&flags.PaginationPageFlag,
|
||||
&flags.PaginationLimitFlag,
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
||||
|
||||
// RunMilestonesList list milestones
|
||||
func RunMilestonesList(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
state := gitea.StateOpen
|
||||
switch ctx.String("state") {
|
||||
case "all":
|
||||
state = gitea.StateAll
|
||||
case "closed":
|
||||
state = gitea.StateClosed
|
||||
}
|
||||
|
||||
milestones, _, err := login.Client().ListRepoMilestones(owner, repo, gitea.ListMilestoneOption{
|
||||
ListOptions: flags.GetListOptions(ctx),
|
||||
State: state,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
print.MilestonesList(milestones, flags.GlobalOutputValue, state)
|
||||
return nil
|
||||
}
|
42
cmd/milestones/reopen.go
Normal file
42
cmd/milestones/reopen.go
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package milestones
|
||||
|
||||
import (
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdMilestonesReopen represents a sub command of milestones to open an milestone
|
||||
var CmdMilestonesReopen = cli.Command{
|
||||
Name: "reopen",
|
||||
Aliases: []string{"open"},
|
||||
Usage: "Change state of an milestone to 'open'",
|
||||
Description: `Change state of an milestone to 'open'`,
|
||||
ArgsUsage: "<milestone name>",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
return editMilestoneStatus(ctx, false)
|
||||
},
|
||||
Flags: flags.AllDefaultFlags,
|
||||
}
|
||||
|
||||
func editMilestoneStatus(ctx *cli.Context, close bool) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
|
||||
state := gitea.StateOpen
|
||||
if close {
|
||||
state = gitea.StateClosed
|
||||
}
|
||||
_, _, err := client.EditMilestoneByName(owner, repo, ctx.Args().First(), gitea.EditMilestoneOption{
|
||||
State: &state,
|
||||
Title: ctx.Args().First(),
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
82
cmd/notifications.go
Normal file
82
cmd/notifications.go
Normal file
@ -0,0 +1,82 @@
|
||||
// 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 (
|
||||
"log"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// CmdNotifications is the main command to operate with notifications
|
||||
var CmdNotifications = cli.Command{
|
||||
Name: "notifications",
|
||||
Aliases: []string{"notification", "notif"},
|
||||
Usage: "Show notifications",
|
||||
Description: "Show notifications, by default based of the current repo and unread one",
|
||||
Action: runNotifications,
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "all",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "show all notifications of related gitea instance",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
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
|
||||
}
|
83
cmd/open.go
Normal file
83
cmd/open.go
Normal file
@ -0,0 +1,83 @@
|
||||
// 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 (
|
||||
"log"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
local_git "code.gitea.io/tea/modules/git"
|
||||
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdOpen represents a sub command of issues to open issue on the web browser
|
||||
var CmdOpen = cli.Command{
|
||||
Name: "open",
|
||||
Usage: "Open something of the repository on web browser",
|
||||
Description: `Open something of the repository on web browser`,
|
||||
Action: runOpen,
|
||||
Flags: append([]cli.Flag{}, flags.LoginRepoFlags...),
|
||||
}
|
||||
|
||||
func runOpen(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
var suffix string
|
||||
number := ctx.Args().Get(0)
|
||||
switch {
|
||||
case strings.EqualFold(number, "issues"):
|
||||
suffix = "issues"
|
||||
case strings.EqualFold(number, "pulls"):
|
||||
suffix = "pulls"
|
||||
case strings.EqualFold(number, "releases"):
|
||||
suffix = "releases"
|
||||
case strings.EqualFold(number, "commits"):
|
||||
repo, err := local_git.RepoForWorkdir()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
b, err := repo.Head()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
name := b.Name()
|
||||
switch {
|
||||
case name.IsBranch():
|
||||
suffix = "commits/branch/" + name.Short()
|
||||
case name.IsTag():
|
||||
suffix = "commits/tag/" + name.Short()
|
||||
}
|
||||
case strings.EqualFold(number, "branches"):
|
||||
suffix = "branches"
|
||||
case strings.EqualFold(number, "wiki"):
|
||||
suffix = "wiki"
|
||||
case strings.EqualFold(number, "activity"):
|
||||
suffix = "activity"
|
||||
case strings.EqualFold(number, "settings"):
|
||||
suffix = "settings"
|
||||
case strings.EqualFold(number, "labels"):
|
||||
suffix = "labels"
|
||||
case strings.EqualFold(number, "milestones"):
|
||||
suffix = "milestones"
|
||||
case number != "":
|
||||
suffix = "issues/" + number
|
||||
default:
|
||||
suffix = number
|
||||
}
|
||||
|
||||
u := path.Join(login.URL, owner, repo, suffix)
|
||||
err := open.Run(u)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
40
cmd/organizations.go
Normal file
40
cmd/organizations.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"code.gitea.io/tea/cmd/organizations"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdOrgs represents handle organization
|
||||
var CmdOrgs = cli.Command{
|
||||
Name: "organizations",
|
||||
Aliases: []string{"organization", "org"},
|
||||
Usage: "List, create, delete organizations",
|
||||
Description: "Show organization details",
|
||||
ArgsUsage: "[<organization>]",
|
||||
Action: runOrganizations,
|
||||
Subcommands: []*cli.Command{
|
||||
&organizations.CmdOrganizationList,
|
||||
&organizations.CmdOrganizationDelete,
|
||||
},
|
||||
}
|
||||
|
||||
func runOrganizations(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() == 1 {
|
||||
return runOrganizationDetail(ctx.Args().First())
|
||||
}
|
||||
return organizations.RunOrganizationList(ctx)
|
||||
}
|
||||
|
||||
func runOrganizationDetail(path string) error {
|
||||
|
||||
log.Fatal("Not yet implemented.")
|
||||
return nil
|
||||
}
|
43
cmd/organizations/delete.go
Normal file
43
cmd/organizations/delete.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package organizations
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdOrganizationDelete represents a sub command of organizations to delete a given user organization
|
||||
var CmdOrganizationDelete = cli.Command{
|
||||
Name: "delete",
|
||||
Aliases: []string{"rm"},
|
||||
Usage: "Delete users Organizations",
|
||||
Description: "Delete users organizations",
|
||||
ArgsUsage: "<organization name>",
|
||||
Action: RunOrganizationDelete,
|
||||
}
|
||||
|
||||
// RunOrganizationDelete delete user organization
|
||||
func RunOrganizationDelete(ctx *cli.Context) error {
|
||||
login, _, _ := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
client := login.Client()
|
||||
|
||||
if ctx.Args().Len() < 1 {
|
||||
log.Fatal("You have to specify the organization name you want to delete.")
|
||||
return nil
|
||||
}
|
||||
|
||||
response, err := client.DeleteOrg(ctx.Args().First())
|
||||
if response != nil && response.StatusCode == 404 {
|
||||
log.Fatal("The given organization does not exist.")
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
45
cmd/organizations/list.go
Normal file
45
cmd/organizations/list.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 organizations
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// CmdOrganizationList represents a sub command of organizations to list users organizations
|
||||
var CmdOrganizationList = cli.Command{
|
||||
Name: "ls",
|
||||
Aliases: []string{"list"},
|
||||
Usage: "List Organizations",
|
||||
Description: "List users organizations",
|
||||
Action: RunOrganizationList,
|
||||
Flags: append([]cli.Flag{
|
||||
&flags.PaginationPageFlag,
|
||||
&flags.PaginationLimitFlag,
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
||||
|
||||
// RunOrganizationList list user organizations
|
||||
func RunOrganizationList(ctx *cli.Context) error {
|
||||
login, _, _ := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
client := login.Client()
|
||||
|
||||
userOrganizations, _, err := client.ListUserOrgs(login.User, gitea.ListOrgsOptions{ListOptions: flags.GetListOptions(ctx)})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
print.OrganizationsList(userOrganizations, flags.GlobalOutputValue)
|
||||
|
||||
return nil
|
||||
}
|
82
cmd/pulls.go
82
cmd/pulls.go
@ -5,68 +5,60 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/cmd/pulls"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdPulls represents to login a gitea server.
|
||||
// CmdPulls is the main command to operate on PRs
|
||||
var CmdPulls = cli.Command{
|
||||
Name: "pulls",
|
||||
Usage: "Operate with pulls of the repository",
|
||||
Description: `Operate with pulls of the repository`,
|
||||
Aliases: []string{"pull", "pr"},
|
||||
Usage: "List, create, checkout and clean pull requests",
|
||||
Description: `List, create, checkout and clean pull requests`,
|
||||
ArgsUsage: "[<pull index>]",
|
||||
Action: runPulls,
|
||||
Flags: AllDefaultFlags,
|
||||
Flags: flags.IssuePRFlags,
|
||||
Subcommands: []*cli.Command{
|
||||
&pulls.CmdPullsList,
|
||||
&pulls.CmdPullsCheckout,
|
||||
&pulls.CmdPullsClean,
|
||||
&pulls.CmdPullsCreate,
|
||||
},
|
||||
}
|
||||
|
||||
func runPulls(ctx *cli.Context) error {
|
||||
login, owner, repo := initCommand()
|
||||
|
||||
prs, err := login.Client().ListRepoPullRequests(owner, repo, gitea.ListPullRequestsOptions{
|
||||
Page: 0,
|
||||
State: string(gitea.StateOpen),
|
||||
})
|
||||
if ctx.Args().Len() == 1 {
|
||||
return runPullDetail(ctx.Args().First())
|
||||
}
|
||||
return pulls.RunPullsList(ctx)
|
||||
}
|
||||
|
||||
func runPullDetail(index string) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
idx, err := utils.ArgToIndex(index)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
headers := []string{
|
||||
"Index",
|
||||
"Name",
|
||||
"Updated",
|
||||
"Title",
|
||||
client := login.Client()
|
||||
pr, _, err := client.GetPullRequest(owner, repo, idx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var values [][]string
|
||||
|
||||
if len(prs) == 0 {
|
||||
Output(outputValue, headers, values)
|
||||
return nil
|
||||
reviews, _, err := client.ListPullReviews(owner, repo, idx, gitea.ListPullReviewsOptions{})
|
||||
if err != nil {
|
||||
fmt.Printf("error while loading reviews: %v\n", err)
|
||||
}
|
||||
|
||||
for _, pr := range prs {
|
||||
if pr == nil {
|
||||
continue
|
||||
}
|
||||
name := pr.Poster.FullName
|
||||
if len(name) == 0 {
|
||||
name = pr.Poster.UserName
|
||||
}
|
||||
values = append(
|
||||
values,
|
||||
[]string{
|
||||
strconv.FormatInt(pr.Index, 10),
|
||||
name,
|
||||
pr.Updated.Format("2006-01-02 15:04:05"),
|
||||
pr.Title,
|
||||
},
|
||||
)
|
||||
}
|
||||
Output(outputValue, headers, values)
|
||||
|
||||
print.PullDetails(pr, reviews)
|
||||
return nil
|
||||
}
|
||||
|
40
cmd/pulls/checkout.go
Normal file
40
cmd/pulls/checkout.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 (
|
||||
"log"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/interact"
|
||||
"code.gitea.io/tea/modules/task"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdPullsCheckout is a command to locally checkout the given PR
|
||||
var CmdPullsCheckout = cli.Command{
|
||||
Name: "checkout",
|
||||
Usage: "Locally check out the given PR",
|
||||
Description: `Locally check out the given PR`,
|
||||
Action: runPullsCheckout,
|
||||
ArgsUsage: "<pull index>",
|
||||
Flags: flags.AllDefaultFlags,
|
||||
}
|
||||
|
||||
func runPullsCheckout(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
if ctx.Args().Len() != 1 {
|
||||
log.Fatal("Must specify a PR index")
|
||||
}
|
||||
idx, err := utils.ArgToIndex(ctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return task.PullCheckout(login, owner, repo, idx, interact.PromptPassword)
|
||||
}
|
46
cmd/pulls/clean.go
Normal file
46
cmd/pulls/clean.go
Normal file
@ -0,0 +1,46 @@
|
||||
// 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/config"
|
||||
"code.gitea.io/tea/modules/interact"
|
||||
"code.gitea.io/tea/modules/task"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdPullsClean removes the remote and local feature branches, if a PR is merged.
|
||||
var CmdPullsClean = cli.Command{
|
||||
Name: "clean",
|
||||
Usage: "Deletes local & remote feature-branches for a closed pull request",
|
||||
Description: `Deletes local & remote feature-branches for a closed pull request`,
|
||||
ArgsUsage: "<pull index>",
|
||||
Action: runPullsClean,
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "ignore-sha",
|
||||
Usage: "Find the local branch by name instead of commit hash (less precise)",
|
||||
},
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
||||
|
||||
func runPullsClean(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
if ctx.Args().Len() != 1 {
|
||||
return fmt.Errorf("Must specify a PR index")
|
||||
}
|
||||
|
||||
idx, err := utils.ArgToIndex(ctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return task.PullClean(login, owner, repo, idx, ctx.Bool("ignore-sha"), interact.PromptPassword)
|
||||
}
|
63
cmd/pulls/create.go
Normal file
63
cmd/pulls/create.go
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package pulls
|
||||
|
||||
import (
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/interact"
|
||||
"code.gitea.io/tea/modules/task"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdPullsCreate creates a pull request
|
||||
var CmdPullsCreate = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a pull-request",
|
||||
Description: "Create a pull-request",
|
||||
Action: runPullsCreate,
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "head",
|
||||
Usage: "Set head branch (default is current one)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "base",
|
||||
Aliases: []string{"b"},
|
||||
Usage: "Set base branch (default is default branch)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "title",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "Set title of pull (default is head branch name)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "description",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Set body of new pull",
|
||||
},
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
||||
|
||||
func runPullsCreate(ctx *cli.Context) error {
|
||||
login, ownerArg, repoArg := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
// no args -> interactive mode
|
||||
if ctx.NumFlags() == 0 {
|
||||
return interact.CreatePull(login, ownerArg, repoArg)
|
||||
}
|
||||
|
||||
// else use args to create PR
|
||||
return task.CreatePull(
|
||||
login,
|
||||
ownerArg,
|
||||
repoArg,
|
||||
ctx.String("base"),
|
||||
ctx.String("head"),
|
||||
ctx.String("title"),
|
||||
ctx.String("description"),
|
||||
)
|
||||
}
|
52
cmd/pulls/list.go
Normal file
52
cmd/pulls/list.go
Normal file
@ -0,0 +1,52 @@
|
||||
// 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 (
|
||||
"log"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// CmdPullsList represents a sub command of issues to list pulls
|
||||
var CmdPullsList = cli.Command{
|
||||
Name: "ls",
|
||||
Aliases: []string{"list"},
|
||||
Usage: "List pull requests of the repository",
|
||||
Description: `List pull requests of the repository`,
|
||||
Action: RunPullsList,
|
||||
Flags: flags.IssuePRFlags,
|
||||
}
|
||||
|
||||
// RunPullsList return list of pulls
|
||||
func RunPullsList(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
state := gitea.StateOpen
|
||||
switch ctx.String("state") {
|
||||
case "all":
|
||||
state = gitea.StateAll
|
||||
case "open":
|
||||
state = gitea.StateOpen
|
||||
case "closed":
|
||||
state = gitea.StateClosed
|
||||
}
|
||||
|
||||
prs, _, err := login.Client().ListRepoPullRequests(owner, repo, gitea.ListPullRequestsOptions{
|
||||
State: state,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
print.PullsList(prs, flags.GlobalOutputValue)
|
||||
return nil
|
||||
}
|
142
cmd/releases.go
142
cmd/releases.go
@ -5,139 +5,25 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/cmd/releases"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdReleases represents to login a gitea server.
|
||||
// ToDo: ReleaseDetails
|
||||
var CmdReleases = cli.Command{
|
||||
Name: "releases",
|
||||
Usage: "Operate with releases of the repository",
|
||||
Description: `Operate with releases of the repository`,
|
||||
Action: runReleases,
|
||||
Subcommands: []cli.Command{
|
||||
CmdReleaseCreate,
|
||||
Aliases: []string{"release"},
|
||||
Usage: "Manage releases",
|
||||
Description: "Manage releases",
|
||||
Action: releases.RunReleasesList,
|
||||
Subcommands: []*cli.Command{
|
||||
&releases.CmdReleaseList,
|
||||
&releases.CmdReleaseCreate,
|
||||
&releases.CmdReleaseDelete,
|
||||
&releases.CmdReleaseEdit,
|
||||
},
|
||||
Flags: AllDefaultFlags,
|
||||
}
|
||||
|
||||
func runReleases(ctx *cli.Context) error {
|
||||
login, owner, repo := initCommand()
|
||||
|
||||
releases, err := login.Client().ListReleases(owner, repo)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
headers := []string{
|
||||
"Tag-Name",
|
||||
"Title",
|
||||
"Published At",
|
||||
"Tar URL",
|
||||
}
|
||||
|
||||
var values [][]string
|
||||
|
||||
if len(releases) == 0 {
|
||||
Output(outputValue, headers, values)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, release := range releases {
|
||||
values = append(
|
||||
values,
|
||||
[]string{
|
||||
release.TagName,
|
||||
release.Title,
|
||||
release.PublishedAt.Format("2006-01-02 15:04:05"),
|
||||
release.TarURL,
|
||||
},
|
||||
)
|
||||
}
|
||||
Output(outputValue, headers, values)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdReleaseCreate represents a sub command of Release to create release.
|
||||
var CmdReleaseCreate = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a release in repository",
|
||||
Description: `Create a release in repository`,
|
||||
Action: runReleaseCreate,
|
||||
Flags: append([]cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "tag",
|
||||
Usage: "release tag name",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "target",
|
||||
Usage: "release target refs, branch name or commit id",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "title, t",
|
||||
Usage: "release title to create",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "note, n",
|
||||
Usage: "release note to create",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "draft, d",
|
||||
Usage: "the release is a draft",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "prerelease, p",
|
||||
Usage: "the release is a prerelease",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "asset, a",
|
||||
Usage: "a list of files to attach to the release",
|
||||
},
|
||||
}, LoginRepoFlags...),
|
||||
}
|
||||
|
||||
func runReleaseCreate(ctx *cli.Context) error {
|
||||
login, owner, repo := initCommand()
|
||||
|
||||
release, err := login.Client().CreateRelease(owner, repo, gitea.CreateReleaseOption{
|
||||
TagName: ctx.String("tag"),
|
||||
Target: ctx.String("target"),
|
||||
Title: ctx.String("title"),
|
||||
Note: ctx.String("note"),
|
||||
IsDraft: ctx.Bool("draft"),
|
||||
IsPrerelease: ctx.Bool("prerelease"),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if err.Error() == "409 Conflict" {
|
||||
log.Fatal("error: There already is a release for this tag")
|
||||
}
|
||||
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, asset := range ctx.StringSlice("asset") {
|
||||
var file *os.File
|
||||
|
||||
if file, err = os.Open(asset); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
filePath := filepath.Base(asset)
|
||||
|
||||
if _, err = login.Client().CreateReleaseAttachment(owner, repo, release.ID, file, filePath); err != nil {
|
||||
file.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
Flags: flags.AllDefaultFlags,
|
||||
}
|
||||
|
102
cmd/releases/create.go
Normal file
102
cmd/releases/create.go
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package releases
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdReleaseCreate represents a sub command of Release to create release
|
||||
var CmdReleaseCreate = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a release",
|
||||
Description: `Create a release`,
|
||||
Action: runReleaseCreate,
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "tag",
|
||||
Usage: "Tag name",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "target",
|
||||
Usage: "Target refs, branch name or commit id",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "title",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "Release title",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "note",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "Release notes",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "draft",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Is a draft",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "prerelease",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Is a pre-release",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "asset",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "List of files to attach",
|
||||
},
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
||||
|
||||
func runReleaseCreate(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
release, resp, err := login.Client().CreateRelease(owner, repo, gitea.CreateReleaseOption{
|
||||
TagName: ctx.String("tag"),
|
||||
Target: ctx.String("target"),
|
||||
Title: ctx.String("title"),
|
||||
Note: ctx.String("note"),
|
||||
IsDraft: ctx.Bool("draft"),
|
||||
IsPrerelease: ctx.Bool("prerelease"),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if resp != nil && resp.StatusCode == http.StatusConflict {
|
||||
fmt.Println("error: There already is a release for this tag")
|
||||
return nil
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, asset := range ctx.StringSlice("asset") {
|
||||
var file *os.File
|
||||
|
||||
if file, err = os.Open(asset); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
filePath := filepath.Base(asset)
|
||||
|
||||
if _, _, err = login.Client().CreateReleaseAttachment(owner, repo, release.ID, file, filePath); err != nil {
|
||||
file.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
70
cmd/releases/delete.go
Normal file
70
cmd/releases/delete.go
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package releases
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdReleaseDelete represents a sub command of Release to delete a release
|
||||
var CmdReleaseDelete = cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete a release",
|
||||
Description: `Delete a release`,
|
||||
ArgsUsage: "<release tag>",
|
||||
Action: runReleaseDelete,
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "confirm",
|
||||
Aliases: []string{"y"},
|
||||
Usage: "Confirm deletion (required)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "delete-tag",
|
||||
Usage: "Also delete the git tag for this release",
|
||||
},
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
||||
|
||||
func runReleaseDelete(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
|
||||
tag := ctx.Args().First()
|
||||
if len(tag) == 0 {
|
||||
fmt.Println("Release tag needed to delete")
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ctx.Bool("confirm") {
|
||||
fmt.Println("Are you sure? Please confirm with -y or --confirm.")
|
||||
return nil
|
||||
}
|
||||
|
||||
release, err := getReleaseByTag(owner, repo, tag, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if release == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = client.DeleteRelease(owner, repo, release.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.Bool("delete-tag") {
|
||||
_, err = client.DeleteReleaseTag(owner, repo, tag)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
94
cmd/releases/edit.go
Normal file
94
cmd/releases/edit.go
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package releases
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdReleaseEdit represents a sub command of Release to edit releases
|
||||
var CmdReleaseEdit = cli.Command{
|
||||
Name: "edit",
|
||||
Usage: "Edit a release",
|
||||
Description: `Edit a release`,
|
||||
ArgsUsage: "<release tag>",
|
||||
Action: runReleaseEdit,
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "tag",
|
||||
Usage: "Change Tag",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "target",
|
||||
Usage: "Change Target",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "title",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "Change Title",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "note",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "Change Notes",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "draft",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Mark as Draft [True/false]",
|
||||
DefaultText: "true",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "prerelease",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Mark as Pre-Release [True/false]",
|
||||
DefaultText: "true",
|
||||
},
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
||||
|
||||
func runReleaseEdit(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
|
||||
tag := ctx.Args().First()
|
||||
if len(tag) == 0 {
|
||||
fmt.Println("Release tag needed to edit")
|
||||
return nil
|
||||
}
|
||||
|
||||
release, err := getReleaseByTag(owner, repo, tag, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if release == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var isDraft, isPre *bool
|
||||
if ctx.IsSet("draft") {
|
||||
isDraft = gitea.OptionalBool(strings.ToLower(ctx.String("draft"))[:1] == "t")
|
||||
}
|
||||
if ctx.IsSet("prerelease") {
|
||||
isPre = gitea.OptionalBool(strings.ToLower(ctx.String("prerelease"))[:1] == "t")
|
||||
}
|
||||
|
||||
_, _, err = client.EditRelease(owner, repo, release.ID, gitea.EditReleaseOption{
|
||||
TagName: ctx.String("tag"),
|
||||
Target: ctx.String("target"),
|
||||
Title: ctx.String("title"),
|
||||
Note: ctx.String("note"),
|
||||
IsDraft: isDraft,
|
||||
IsPrerelease: isPre,
|
||||
})
|
||||
return err
|
||||
}
|
61
cmd/releases/list.go
Normal file
61
cmd/releases/list.go
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package releases
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// CmdReleaseList represents a sub command of Release to list releases
|
||||
var CmdReleaseList = cli.Command{
|
||||
Name: "ls",
|
||||
Aliases: []string{"list"},
|
||||
Usage: "List Releases",
|
||||
Description: "List Releases",
|
||||
Action: RunReleasesList,
|
||||
Flags: append([]cli.Flag{
|
||||
&flags.PaginationPageFlag,
|
||||
&flags.PaginationLimitFlag,
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
||||
|
||||
// RunReleasesList list releases
|
||||
func RunReleasesList(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
releases, _, err := login.Client().ListReleases(owner, repo, gitea.ListReleasesOptions{ListOptions: flags.GetListOptions(ctx)})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
print.ReleasesList(releases, flags.GlobalOutputValue)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getReleaseByTag(owner, repo, tag string, client *gitea.Client) (*gitea.Release, error) {
|
||||
rl, _, err := client.ListReleases(owner, repo, gitea.ListReleasesOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rl) == 0 {
|
||||
fmt.Println("Repo does not have any release")
|
||||
return nil, nil
|
||||
}
|
||||
for _, r := range rl {
|
||||
if r.TagName == tag {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
fmt.Println("Release tag does not exist")
|
||||
return nil, nil
|
||||
}
|
56
cmd/repos.go
Normal file
56
cmd/repos.go
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/cmd/repos"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdRepos represents to login a gitea server.
|
||||
var CmdRepos = cli.Command{
|
||||
Name: "repos",
|
||||
Aliases: []string{"repo"},
|
||||
Usage: "Show repository details",
|
||||
Description: "Show repository details",
|
||||
ArgsUsage: "[<repo owner>/<repo name>]",
|
||||
Action: runRepos,
|
||||
Subcommands: []*cli.Command{
|
||||
&repos.CmdReposList,
|
||||
&repos.CmdReposSearch,
|
||||
&repos.CmdRepoCreate,
|
||||
},
|
||||
Flags: repos.CmdReposListFlags,
|
||||
}
|
||||
|
||||
func runRepos(ctx *cli.Context) error {
|
||||
if ctx.Args().Len() == 1 {
|
||||
return runRepoDetail(ctx.Args().First())
|
||||
}
|
||||
return repos.RunReposList(ctx)
|
||||
}
|
||||
|
||||
func runRepoDetail(path string) error {
|
||||
login, ownerFallback, _ := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
repoOwner, repoName := utils.GetOwnerAndRepo(path, ownerFallback)
|
||||
repo, _, err := client.GetRepo(repoOwner, repoName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
topics, _, err := client.ListRepoTopics(repo.Owner.UserName, repo.Name, gitea.ListRepoTopicsOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print.RepoDetails(repo, topics)
|
||||
return nil
|
||||
}
|
120
cmd/repos/create.go
Normal file
120
cmd/repos/create.go
Normal file
@ -0,0 +1,120 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdRepoCreate represents a sub command of repos to create one
|
||||
var CmdRepoCreate = cli.Command{
|
||||
Name: "create",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Create a repository",
|
||||
Description: "Create a repository",
|
||||
Action: runRepoCreate,
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{""},
|
||||
Required: true,
|
||||
Usage: "name of new repo",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "owner",
|
||||
Aliases: []string{"O"},
|
||||
Required: false,
|
||||
Usage: "name of repo owner",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "private",
|
||||
Required: false,
|
||||
Value: false,
|
||||
Usage: "make repo private",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "description",
|
||||
Aliases: []string{"desc"},
|
||||
Required: false,
|
||||
Usage: "add description to repo",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "init",
|
||||
Required: false,
|
||||
Value: false,
|
||||
Usage: "initialize repo",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "labels",
|
||||
Required: false,
|
||||
Usage: "name of label set to add",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "gitignores",
|
||||
Aliases: []string{"git"},
|
||||
Required: false,
|
||||
Usage: "list of gitignore templates (need --init)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "license",
|
||||
Required: false,
|
||||
Usage: "add license (need --init)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "readme",
|
||||
Required: false,
|
||||
Usage: "use readme template (need --init)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "branch",
|
||||
Required: false,
|
||||
Usage: "use custom default branch (need --init)",
|
||||
},
|
||||
}, flags.LoginOutputFlags...),
|
||||
}
|
||||
|
||||
func runRepoCreate(ctx *cli.Context) error {
|
||||
login, _, _ := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
var (
|
||||
repo *gitea.Repository
|
||||
err error
|
||||
)
|
||||
opts := gitea.CreateRepoOption{
|
||||
Name: ctx.String("name"),
|
||||
Description: ctx.String("description"),
|
||||
Private: ctx.Bool("private"),
|
||||
AutoInit: ctx.Bool("init"),
|
||||
IssueLabels: ctx.String("labels"),
|
||||
Gitignores: ctx.String("gitignores"),
|
||||
License: ctx.String("license"),
|
||||
Readme: ctx.String("readme"),
|
||||
DefaultBranch: ctx.String("branch"),
|
||||
}
|
||||
if len(ctx.String("owner")) != 0 {
|
||||
repo, _, err = client.CreateOrgRepo(ctx.String("owner"), opts)
|
||||
} else {
|
||||
repo, _, err = client.CreateRepo(opts)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
topics, _, err := client.ListRepoTopics(repo.Owner.UserName, repo.Name, gitea.ListRepoTopicsOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
print.RepoDetails(repo, topics)
|
||||
|
||||
fmt.Printf("%s\n", repo.HTMLURL)
|
||||
return nil
|
||||
}
|
54
cmd/repos/flags.go
Normal file
54
cmd/repos/flags.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 repos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/modules/print"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"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{
|
||||
Name: "type",
|
||||
Aliases: []string{"T"},
|
||||
Required: false,
|
||||
Usage: "Filter by type: fork, mirror, source",
|
||||
}
|
||||
|
||||
func getTypeFilter(ctx *cli.Context) (filter gitea.RepoType, err error) {
|
||||
t := ctx.String("type")
|
||||
filter = gitea.RepoTypeNone
|
||||
switch t {
|
||||
case "":
|
||||
filter = gitea.RepoTypeNone
|
||||
case "fork":
|
||||
filter = gitea.RepoTypeFork
|
||||
case "mirror":
|
||||
filter = gitea.RepoTypeMirror
|
||||
case "source":
|
||||
filter = gitea.RepoTypeSource
|
||||
default:
|
||||
err = fmt.Errorf("invalid repo type '%s'. valid: fork, mirror, source", t)
|
||||
}
|
||||
return
|
||||
}
|
108
cmd/repos/list.go
Normal file
108
cmd/repos/list.go
Normal file
@ -0,0 +1,108 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repos
|
||||
|
||||
import (
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdReposListFlags contains all flags needed for repo listing
|
||||
var CmdReposListFlags = append([]cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "watched",
|
||||
Aliases: []string{"w"},
|
||||
Required: false,
|
||||
Usage: "List your watched repos instead",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "starred",
|
||||
Aliases: []string{"s"},
|
||||
Required: false,
|
||||
Usage: "List your starred repos instead",
|
||||
},
|
||||
&printFieldsFlag,
|
||||
&typeFilterFlag,
|
||||
&flags.PaginationPageFlag,
|
||||
&flags.PaginationLimitFlag,
|
||||
}, flags.LoginOutputFlags...)
|
||||
|
||||
// CmdReposList represents a sub command of repos to list them
|
||||
var CmdReposList = cli.Command{
|
||||
Name: "ls",
|
||||
Aliases: []string{"list"},
|
||||
Usage: "List repositories you have access to",
|
||||
Description: "List repositories you have access to",
|
||||
Action: RunReposList,
|
||||
Flags: CmdReposListFlags,
|
||||
}
|
||||
|
||||
// RunReposList list repositories
|
||||
func RunReposList(ctx *cli.Context) error {
|
||||
login, _, _ := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
|
||||
typeFilter, err := getTypeFilter(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var rps []*gitea.Repository
|
||||
if ctx.Bool("starred") {
|
||||
user, _, err := client.GetMyUserInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rps, _, err = client.SearchRepos(gitea.SearchRepoOptions{
|
||||
ListOptions: flags.GetListOptions(ctx),
|
||||
StarredByUserID: user.ID,
|
||||
})
|
||||
} else if ctx.Bool("watched") {
|
||||
rps, _, err = client.GetMyWatchedRepos() // TODO: this does not expose pagination..
|
||||
} else {
|
||||
rps, _, err = client.ListMyRepos(gitea.ListReposOptions{
|
||||
ListOptions: flags.GetListOptions(ctx),
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reposFiltered := rps
|
||||
if typeFilter != gitea.RepoTypeNone {
|
||||
reposFiltered = filterReposByType(rps, typeFilter)
|
||||
}
|
||||
|
||||
print.ReposList(reposFiltered, flags.GlobalOutputValue, getFields(ctx))
|
||||
return nil
|
||||
}
|
||||
|
||||
func filterReposByType(repos []*gitea.Repository, t gitea.RepoType) []*gitea.Repository {
|
||||
var filtered []*gitea.Repository
|
||||
for _, r := range repos {
|
||||
switch t {
|
||||
case gitea.RepoTypeFork:
|
||||
if !r.Fork {
|
||||
continue
|
||||
}
|
||||
case gitea.RepoTypeMirror:
|
||||
if !r.Mirror {
|
||||
continue
|
||||
}
|
||||
case gitea.RepoTypeSource:
|
||||
if r.Fork || r.Mirror {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
filtered = append(filtered, r)
|
||||
}
|
||||
return filtered
|
||||
}
|
128
cmd/repos/search.go
Normal file
128
cmd/repos/search.go
Normal file
@ -0,0 +1,128 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repos
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// CmdReposSearch represents a sub command of repos to find them
|
||||
var CmdReposSearch = cli.Command{
|
||||
Name: "search",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Find any repo on an Gitea instance",
|
||||
Description: "Find any repo on an Gitea instance",
|
||||
ArgsUsage: "[<search term>]",
|
||||
Action: runReposSearch,
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
// TODO: it might be nice to search for topics as an ADDITIONAL filter.
|
||||
// for that, we'd probably need to make multiple queries and UNION the results.
|
||||
Name: "topic",
|
||||
Aliases: []string{"t"},
|
||||
Required: false,
|
||||
Usage: "Search for term in repo topics instead of name",
|
||||
},
|
||||
&typeFilterFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "owner",
|
||||
Aliases: []string{"O"},
|
||||
Required: false,
|
||||
Usage: "Filter by owner",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "private",
|
||||
Required: false,
|
||||
Usage: "Filter private repos (true|false)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "archived",
|
||||
Required: false,
|
||||
Usage: "Filter archived repos (true|false)",
|
||||
},
|
||||
&printFieldsFlag,
|
||||
&flags.PaginationPageFlag,
|
||||
&flags.PaginationLimitFlag,
|
||||
}, flags.LoginOutputFlags...),
|
||||
}
|
||||
|
||||
func runReposSearch(ctx *cli.Context) error {
|
||||
login, _, _ := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
|
||||
var ownerID int64
|
||||
if ctx.IsSet("owner") {
|
||||
// test if owner is a organisation
|
||||
org, _, err := client.GetOrg(ctx.String("owner"))
|
||||
if err != nil {
|
||||
// HACK: the client does not return a response on 404, so we can't check res.StatusCode
|
||||
if err.Error() != "404 Not Found" {
|
||||
log.Fatal("could not find owner: ", err)
|
||||
}
|
||||
|
||||
// if owner is no org, its a user
|
||||
user, _, err := client.GetUserInfo(ctx.String("owner"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ownerID = user.ID
|
||||
} else {
|
||||
ownerID = org.ID
|
||||
}
|
||||
}
|
||||
|
||||
var isArchived *bool
|
||||
if ctx.IsSet("archived") {
|
||||
archived := strings.ToLower(ctx.String("archived"))[:1] == "t"
|
||||
isArchived = &archived
|
||||
}
|
||||
|
||||
var isPrivate *bool
|
||||
if ctx.IsSet("private") {
|
||||
private := strings.ToLower(ctx.String("private"))[:1] == "t"
|
||||
isPrivate = &private
|
||||
}
|
||||
|
||||
mode, err := getTypeFilter(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var keyword string
|
||||
if ctx.Args().Present() {
|
||||
keyword = strings.Join(ctx.Args().Slice(), " ")
|
||||
}
|
||||
|
||||
user, _, err := client.GetMyUserInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rps, _, err := client.SearchRepos(gitea.SearchRepoOptions{
|
||||
ListOptions: flags.GetListOptions(ctx),
|
||||
OwnerID: ownerID,
|
||||
IsPrivate: isPrivate,
|
||||
IsArchived: isArchived,
|
||||
Type: mode,
|
||||
Keyword: keyword,
|
||||
KeywordInDescription: true,
|
||||
KeywordIsTopic: ctx.Bool("topic"),
|
||||
PrioritizedByOwnerID: user.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print.ReposList(rps, flags.GlobalOutputValue, getFields(ctx))
|
||||
return nil
|
||||
}
|
32
cmd/times.go
Normal file
32
cmd/times.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.gitea.io/tea/cmd/times"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdTrackedTimes represents the command to operate repositories' times.
|
||||
var CmdTrackedTimes = cli.Command{
|
||||
Name: "times",
|
||||
Aliases: []string{"time"},
|
||||
Usage: "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
|
||||
times might be listed.`,
|
||||
ArgsUsage: "[username | #issue]",
|
||||
Action: runTrackedTimes,
|
||||
Subcommands: []*cli.Command{
|
||||
×.CmdTrackedTimesAdd,
|
||||
×.CmdTrackedTimesDelete,
|
||||
×.CmdTrackedTimesReset,
|
||||
×.CmdTrackedTimesList,
|
||||
},
|
||||
}
|
||||
|
||||
func runTrackedTimes(ctx *cli.Context) error {
|
||||
return times.RunTimesList(ctx)
|
||||
}
|
59
cmd/times/add.go
Normal file
59
cmd/times/add.go
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package times
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdTrackedTimesAdd represents a sub command of times to add time to an issue
|
||||
var CmdTrackedTimesAdd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "Track spent time on an issue",
|
||||
UsageText: "tea times add <issue> <duration>",
|
||||
Description: `Track spent time on an issue
|
||||
Example:
|
||||
tea times add 1 1h25m
|
||||
`,
|
||||
Action: runTrackedTimesAdd,
|
||||
Flags: flags.LoginRepoFlags,
|
||||
}
|
||||
|
||||
func runTrackedTimesAdd(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
|
||||
if ctx.Args().Len() < 2 {
|
||||
return fmt.Errorf("No issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||
}
|
||||
|
||||
issue, err := utils.ArgToIndex(ctx.Args().First())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
duration, err := time.ParseDuration(strings.Join(ctx.Args().Tail(), ""))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, _, err = login.Client().AddTime(owner, repo, issue, gitea.AddTimeOption{
|
||||
Time: int64(duration.Seconds()),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
53
cmd/times/delete.go
Normal file
53
cmd/times/delete.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package times
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdTrackedTimesDelete is a sub command of CmdTrackedTimes, and removes time from an issue
|
||||
var CmdTrackedTimesDelete = cli.Command{
|
||||
Name: "delete",
|
||||
Aliases: []string{"rm"},
|
||||
Usage: "Delete a single tracked time on an issue",
|
||||
UsageText: "tea times delete <issue> <time ID>",
|
||||
Action: runTrackedTimesDelete,
|
||||
Flags: flags.LoginRepoFlags,
|
||||
}
|
||||
|
||||
func runTrackedTimesDelete(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
|
||||
if ctx.Args().Len() < 2 {
|
||||
return fmt.Errorf("No issue or time ID specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||
}
|
||||
|
||||
issue, err := utils.ArgToIndex(ctx.Args().First())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
timeID, err := strconv.ParseInt(ctx.Args().Get(1), 10, 64)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = client.DeleteTime(owner, repo, issue, timeID)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
97
cmd/times/list.go
Normal file
97
cmd/times/list.go
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package times
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/araddon/dateparse"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdTrackedTimesList represents a sub command of times to list them
|
||||
var CmdTrackedTimesList = cli.Command{
|
||||
Name: "ls",
|
||||
Aliases: []string{"list"},
|
||||
Action: RunTimesList,
|
||||
Usage: "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
|
||||
times might be listed.`,
|
||||
ArgsUsage: "[username | #issue]",
|
||||
|
||||
Flags: append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "from",
|
||||
Aliases: []string{"f"},
|
||||
Usage: "Show only times tracked after this date",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "until",
|
||||
Aliases: []string{"u"},
|
||||
Usage: "Show only times tracked before this date",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "total",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "Print the total duration at the end",
|
||||
},
|
||||
}, flags.AllDefaultFlags...),
|
||||
}
|
||||
|
||||
// RunTimesList list repositories
|
||||
func RunTimesList(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
|
||||
var times []*gitea.TrackedTime
|
||||
var err error
|
||||
|
||||
user := ctx.Args().First()
|
||||
fmt.Println(ctx.Command.ArgsUsage)
|
||||
if user == "" {
|
||||
// get all tracked times on the repo
|
||||
times, _, err = client.GetRepoTrackedTimes(owner, repo)
|
||||
} else if strings.HasPrefix(user, "#") {
|
||||
// get all tracked times on the specified issue
|
||||
issue, err := 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
|
||||
if ctx.String("from") != "" {
|
||||
from, err = dateparse.ParseLocal(ctx.String("from"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if ctx.String("until") != "" {
|
||||
until, err = dateparse.ParseLocal(ctx.String("until"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
print.TrackedTimesList(times, flags.GlobalOutputValue, from, until, ctx.Bool("total"))
|
||||
return nil
|
||||
}
|
47
cmd/times/reset.go
Normal file
47
cmd/times/reset.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package times
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CmdTrackedTimesReset is a subcommand of CmdTrackedTimes, and
|
||||
// clears all tracked times on an issue.
|
||||
var CmdTrackedTimesReset = cli.Command{
|
||||
Name: "reset",
|
||||
Usage: "Reset tracked time on an issue",
|
||||
UsageText: "tea times reset <issue>",
|
||||
Action: runTrackedTimesReset,
|
||||
Flags: flags.LoginRepoFlags,
|
||||
}
|
||||
|
||||
func runTrackedTimesReset(ctx *cli.Context) error {
|
||||
login, owner, repo := config.InitCommand(flags.GlobalRepoValue, flags.GlobalLoginValue, flags.GlobalRemoteValue)
|
||||
client := login.Client()
|
||||
|
||||
if ctx.Args().Len() != 1 {
|
||||
return fmt.Errorf("No issue specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||
}
|
||||
|
||||
issue, err := utils.ArgToIndex(ctx.Args().First())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = client.ResetIssueTime(owner, repo, issue)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
41
go.mod
41
go.mod
@ -1,14 +1,37 @@
|
||||
module code.gitea.io/tea
|
||||
|
||||
go 1.12
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
code.gitea.io/sdk/gitea v0.0.0-20191013013401-e41e9ea72caa
|
||||
github.com/go-gitea/yaml v0.0.0-20170812160011-eb3733d160e7
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.1
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/urfave/cli v1.20.0
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
code.gitea.io/gitea-vet v0.2.1
|
||||
code.gitea.io/sdk/gitea v0.13.1-0.20201209180822-68eec69f472e
|
||||
github.com/AlecAivazis/survey/v2 v2.2.2
|
||||
github.com/Microsoft/go-winio v0.4.15 // indirect
|
||||
github.com/adrg/xdg v0.2.2
|
||||
github.com/alecthomas/chroma v0.8.1 // indirect
|
||||
github.com/araddon/dateparse v0.0.0-20201001162425-8aadafed4dc4
|
||||
github.com/charmbracelet/glamour v0.2.0
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/go-git/go-git/v5 v5.2.0
|
||||
github.com/imdario/mergo v0.3.11 // indirect
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.4 // indirect
|
||||
github.com/muesli/reflow v0.2.0 // indirect
|
||||
github.com/muesli/termenv v0.7.4
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
github.com/xanzy/ssh-agent v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect
|
||||
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf // indirect
|
||||
golang.org/x/text v0.3.4 // indirect
|
||||
golang.org/x/tools v0.0.0-20201105220310-78b158585360 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
|
||||
)
|
||||
|
235
go.sum
235
go.sum
@ -1,88 +1,257 @@
|
||||
code.gitea.io/sdk/gitea v0.0.0-20191013013401-e41e9ea72caa h1:KgpwNF1StxPXMfCD9M++jvCUPUqHPAbuvQn1q3sWtqw=
|
||||
code.gitea.io/sdk/gitea v0.0.0-20191013013401-e41e9ea72caa/go.mod h1:8IxkM1gyiwEjfO0m47bcmr3u3foR15+LoVub43hCHd0=
|
||||
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/sdk/gitea v0.13.1-0.20201209180822-68eec69f472e h1:oJOoT5TGbSYRNGUhEiiEz3MqFjU6wELN0/liCZ3RmVg=
|
||||
code.gitea.io/sdk/gitea v0.13.1-0.20201209180822-68eec69f472e/go.mod h1:89WiyOX1KEcvjP66sRHdu0RafojGo60bT9UqW17VbWs=
|
||||
github.com/AlecAivazis/survey/v2 v2.2.2 h1:1I4qBrNsHQE+91tQCqVlfrKe9DEL65949d1oKZWVELY=
|
||||
github.com/AlecAivazis/survey/v2 v2.2.2/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
|
||||
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.15 h1:qkLXKzb1QoVatRyd/YlXZ/Kg0m5K3SPuoD82jjSOaBc=
|
||||
github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
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/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo=
|
||||
github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/chroma v0.7.3 h1:NfdAERMy+esYQs8OXk0I868/qDxxCEo7FMz1WIqMAeI=
|
||||
github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||
github.com/alecthomas/chroma v0.8.1 h1:ym20sbvyC6RXz45u4qDglcgr8E313oPROshcuCHqiEE=
|
||||
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/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/araddon/dateparse v0.0.0-20201001162425-8aadafed4dc4 h1:OkS1BqB3CzLtGRznRyvriSY8jeaVk2CrDn2ZiRQgMUI=
|
||||
github.com/araddon/dateparse v0.0.0-20201001162425-8aadafed4dc4/go.mod h1:hMAUZFIkk4B1FouGxqlogyMyU6BwY/UiVmmbbzz9Up8=
|
||||
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/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/charmbracelet/glamour v0.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 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-gitea/yaml v0.0.0-20170812160011-eb3733d160e7 h1:/FEVbfrJ50yBk73Lyq1oCZ4VaCc0g1xd9xLHjz+Znf8=
|
||||
github.com/go-gitea/yaml v0.0.0-20170812160011-eb3733d160e7/go.mod h1:WjJPyqjAk/UMv+Fk/ZRjEOh5SXszSALnSzKqICd7pNg=
|
||||
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/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
|
||||
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
|
||||
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/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI=
|
||||
github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
|
||||
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/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/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/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
|
||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
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/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/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/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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
|
||||
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
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.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
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-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
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/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDEq1axMbGg=
|
||||
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/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||
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/termenv v0.6.0 h1:zxvzTBmo4ZcxhNGGWeMz+Tttm51eF5bmPjfy4MCRYlk=
|
||||
github.com/muesli/termenv v0.6.0/go.mod h1:SohX91w6swWA4AYU+QmPx+aSgXhWO0juiyID9UZmbpA=
|
||||
github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8=
|
||||
github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
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/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
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/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
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/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
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/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/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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
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/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/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/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/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=
|
||||
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-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/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-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
||||
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-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf h1:kt3wY1Lu5MJAnKTfoMR52Cu4gwvna4VTzNOiT8tY73s=
|
||||
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
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-20201105220310-78b158585360 h1:/9CzsU8hOpnSUCtem1vfWNgsVeCTgkMdx+VE5YIYxnU=
|
||||
golang.org/x/tools v0.0.0-20201105220310-78b158585360/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
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-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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
||||
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/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
34
main.go
34
main.go
@ -11,35 +11,35 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/cmd"
|
||||
"code.gitea.io/tea/modules/setting"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Version holds the current Gitea version
|
||||
var Version = "0.1.0-dev"
|
||||
// Version holds the current tea version
|
||||
var Version = "development"
|
||||
|
||||
// Tags holds the build tags used
|
||||
var Tags = ""
|
||||
|
||||
func init() {
|
||||
setting.AppVer = Version
|
||||
setting.AppBuiltWith = formatBuiltWith(Tags)
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "Tea"
|
||||
app.Name = "tea"
|
||||
app.Usage = "Command line tool to interact with Gitea"
|
||||
app.Description = ``
|
||||
app.Version = Version + formatBuiltWith(Tags)
|
||||
app.Commands = []cli.Command{
|
||||
cmd.CmdLogin,
|
||||
cmd.CmdLogout,
|
||||
cmd.CmdIssues,
|
||||
cmd.CmdPulls,
|
||||
cmd.CmdReleases,
|
||||
cmd.CmdLabels,
|
||||
app.Commands = []*cli.Command{
|
||||
&cmd.CmdLogin,
|
||||
&cmd.CmdLogout,
|
||||
&cmd.CmdIssues,
|
||||
&cmd.CmdPulls,
|
||||
&cmd.CmdReleases,
|
||||
&cmd.CmdRepos,
|
||||
&cmd.CmdLabels,
|
||||
&cmd.CmdTrackedTimes,
|
||||
&cmd.CmdOpen,
|
||||
&cmd.CmdNotifications,
|
||||
&cmd.CmdMilestones,
|
||||
&cmd.CmdOrgs,
|
||||
}
|
||||
app.EnableBashCompletion = true
|
||||
err := app.Run(os.Args)
|
||||
|
130
modules/config/command.go
Normal file
130
modules/config/command.go
Normal file
@ -0,0 +1,130 @@
|
||||
// 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")
|
||||
}
|
86
modules/config/config.go
Normal file
86
modules/config/config.go
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// LocalConfig represents local configurations
|
||||
type LocalConfig struct {
|
||||
Logins []Login `yaml:"logins"`
|
||||
}
|
||||
|
||||
var (
|
||||
// config contain if loaded local tea config
|
||||
config LocalConfig
|
||||
loadConfigOnce sync.Once
|
||||
)
|
||||
|
||||
// GetConfigPath return path to tea config file
|
||||
func GetConfigPath() string {
|
||||
configFilePath, err := xdg.ConfigFile("tea/config.yml")
|
||||
|
||||
var exists bool
|
||||
if err != nil {
|
||||
exists = false
|
||||
} else {
|
||||
exists, _ = utils.PathExists(configFilePath)
|
||||
}
|
||||
|
||||
// fallback to old config if no new one exists
|
||||
if !exists {
|
||||
file := filepath.Join(xdg.Home, ".tea", "tea.yml")
|
||||
exists, _ = utils.PathExists(file)
|
||||
if exists {
|
||||
return file
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("unable to get or create config file")
|
||||
}
|
||||
|
||||
return configFilePath
|
||||
}
|
||||
|
||||
// loadConfig load config from file
|
||||
func loadConfig() (err error) {
|
||||
loadConfigOnce.Do(func() {
|
||||
ymlPath := GetConfigPath()
|
||||
exist, _ := utils.FileExist(ymlPath)
|
||||
if exist {
|
||||
bs, err := ioutil.ReadFile(ymlPath)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to read config file: %s", ymlPath)
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(bs, &config)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to parse contents of config file: %s", ymlPath)
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// saveConfig save config to file
|
||||
func saveConfig() error {
|
||||
ymlPath := GetConfigPath()
|
||||
bs, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(ymlPath, bs, 0660)
|
||||
}
|
180
modules/config/login.go
Normal file
180
modules/config/login.go
Normal file
@ -0,0 +1,180 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// Login represents a login to a gitea server, you even could add multiple logins for one gitea server
|
||||
type Login struct {
|
||||
Name string `yaml:"name"`
|
||||
URL string `yaml:"url"`
|
||||
Token string `yaml:"token"`
|
||||
Default bool `yaml:"default"`
|
||||
SSHHost string `yaml:"ssh_host"`
|
||||
// optional path to the private key
|
||||
SSHKey string `yaml:"ssh_key"`
|
||||
Insecure bool `yaml:"insecure"`
|
||||
// User is username from gitea
|
||||
User string `yaml:"user"`
|
||||
// Created is auto created unix timestamp
|
||||
Created int64 `yaml:"created"`
|
||||
}
|
||||
|
||||
// GetLogins return all login available by config
|
||||
func GetLogins() ([]Login, error) {
|
||||
if err := loadConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config.Logins, nil
|
||||
}
|
||||
|
||||
// GetDefaultLogin return the default login
|
||||
func GetDefaultLogin() (*Login, error) {
|
||||
if err := loadConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(config.Logins) == 0 {
|
||||
return nil, errors.New("No available login")
|
||||
}
|
||||
for _, l := range config.Logins {
|
||||
if l.Default {
|
||||
return &l, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &config.Logins[0], nil
|
||||
}
|
||||
|
||||
// SetDefaultLogin set the default login by name (case insensitive)
|
||||
func SetDefaultLogin(name string) error {
|
||||
if err := loadConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loginExist := false
|
||||
for i := range config.Logins {
|
||||
config.Logins[i].Default = false
|
||||
if strings.ToLower(config.Logins[i].Name) == strings.ToLower(name) {
|
||||
config.Logins[i].Default = true
|
||||
loginExist = true
|
||||
}
|
||||
}
|
||||
|
||||
if !loginExist {
|
||||
return fmt.Errorf("login '%s' not found", name)
|
||||
}
|
||||
|
||||
return saveConfig()
|
||||
}
|
||||
|
||||
// GetLoginByName get login by name (case insensitive)
|
||||
func GetLoginByName(name string) *Login {
|
||||
err := loadConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, l := range config.Logins {
|
||||
if strings.ToLower(l.Name) == strings.ToLower(name) {
|
||||
return &l
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLoginByToken get login by token
|
||||
func GetLoginByToken(token string) *Login {
|
||||
err := loadConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, l := range config.Logins {
|
||||
if l.Token == token {
|
||||
return &l
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteLogin delete a login by name from config
|
||||
func DeleteLogin(name string) error {
|
||||
var idx = -1
|
||||
for i, l := range config.Logins {
|
||||
if l.Name == name {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == -1 {
|
||||
return fmt.Errorf("can not delete login '%s', does not exist", name)
|
||||
}
|
||||
|
||||
config.Logins = append(config.Logins[:idx], config.Logins[idx+1:]...)
|
||||
|
||||
return saveConfig()
|
||||
}
|
||||
|
||||
// AddLogin save a login to config
|
||||
func AddLogin(login *Login) error {
|
||||
if err := loadConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// save login to global var
|
||||
config.Logins = append(config.Logins, *login)
|
||||
|
||||
// save login to config file
|
||||
return saveConfig()
|
||||
}
|
||||
|
||||
// Client returns a client to operate Gitea API
|
||||
func (l *Login) Client() *gitea.Client {
|
||||
httpClient := &http.Client{}
|
||||
if l.Insecure {
|
||||
cookieJar, _ := cookiejar.New(nil)
|
||||
|
||||
httpClient = &http.Client{
|
||||
Jar: cookieJar,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}}
|
||||
}
|
||||
|
||||
client, err := gitea.NewClient(l.URL,
|
||||
gitea.SetToken(l.Token),
|
||||
gitea.SetHTTPClient(httpClient),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// GetSSHHost returns SSH host name
|
||||
func (l *Login) GetSSHHost() string {
|
||||
if l.SSHHost != "" {
|
||||
return l.SSHHost
|
||||
}
|
||||
|
||||
u, err := url.Parse(l.URL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return u.Hostname()
|
||||
}
|
76
modules/git/auth.go
Normal file
76
modules/git/auth.go
Normal file
@ -0,0 +1,76 @@
|
||||
// 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 git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
git_transport "github.com/go-git/go-git/v5/plumbing/transport"
|
||||
gogit_http "github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
gogit_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type pwCallback = func(string) (string, error)
|
||||
|
||||
// GetAuthForURL returns the appropriate AuthMethod to be used in Push() / Pull()
|
||||
// operations depending on the protocol, and prompts the user for credentials if
|
||||
// necessary.
|
||||
func GetAuthForURL(remoteURL *url.URL, authToken, keyFile string, passwordCallback pwCallback) (git_transport.AuthMethod, error) {
|
||||
switch remoteURL.Scheme {
|
||||
case "http", "https":
|
||||
// gitea supports push/pull via app token as username.
|
||||
return &gogit_http.BasicAuth{Password: "", Username: authToken}, nil
|
||||
|
||||
case "ssh":
|
||||
// try to select right key via ssh-agent. if it fails, try to read a key manually
|
||||
user := remoteURL.User.Username()
|
||||
auth, err := gogit_ssh.DefaultAuthBuilder(user)
|
||||
if err != nil {
|
||||
signer, err2 := readSSHPrivKey(keyFile, passwordCallback)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
auth = &gogit_ssh.PublicKeys{User: user, Signer: signer}
|
||||
}
|
||||
return auth, nil
|
||||
}
|
||||
return nil, fmt.Errorf("don't know how to handle url scheme %v", remoteURL.Scheme)
|
||||
}
|
||||
|
||||
func readSSHPrivKey(keyFile string, passwordCallback pwCallback) (sig ssh.Signer, err error) {
|
||||
if keyFile != "" {
|
||||
keyFile, err = utils.AbsPathWithExpansion(keyFile)
|
||||
} else {
|
||||
keyFile, err = utils.AbsPathWithExpansion("~/.ssh/id_rsa")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshKey, err := ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err = ssh.ParsePrivateKey(sshKey)
|
||||
if _, ok := err.(*ssh.PassphraseMissingError); ok && passwordCallback != nil {
|
||||
// allow for up to 3 password attempts
|
||||
for i := 0; i < 3; i++ {
|
||||
var pass string
|
||||
pass, err = passwordCallback(keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err = ssh.ParsePrivateKeyWithPassphrase(sshKey, []byte(pass))
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return sig, err
|
||||
}
|
233
modules/git/branch.go
Normal file
233
modules/git/branch.go
Normal file
@ -0,0 +1,233 @@
|
||||
// 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 git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
git_config "github.com/go-git/go-git/v5/config"
|
||||
git_plumbing "github.com/go-git/go-git/v5/plumbing"
|
||||
git_transport "github.com/go-git/go-git/v5/plumbing/transport"
|
||||
)
|
||||
|
||||
// TeaCreateBranch creates a new branch in the repo, tracking from another branch.
|
||||
func (r TeaRepo) TeaCreateBranch(localBranchName, remoteBranchName, remoteName string) error {
|
||||
// save in .git/config to assign remote for future pulls
|
||||
localBranchRefName := git_plumbing.NewBranchReferenceName(localBranchName)
|
||||
err := r.CreateBranch(&git_config.Branch{
|
||||
Name: localBranchName,
|
||||
Merge: git_plumbing.NewBranchReferenceName(remoteBranchName),
|
||||
Remote: remoteName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// serialize the branch to .git/refs/heads
|
||||
remoteBranchRefName := git_plumbing.NewRemoteReferenceName(remoteName, remoteBranchName)
|
||||
remoteBranchRef, err := r.Storer.Reference(remoteBranchRefName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localHashRef := git_plumbing.NewHashReference(localBranchRefName, remoteBranchRef.Hash())
|
||||
return r.Storer.SetReference(localHashRef)
|
||||
}
|
||||
|
||||
// TeaCheckout checks out the given branch in the worktree.
|
||||
func (r TeaRepo) TeaCheckout(branchName string) error {
|
||||
tree, err := r.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localBranchRefName := git_plumbing.NewBranchReferenceName(branchName)
|
||||
return tree.Checkout(&git.CheckoutOptions{Branch: localBranchRefName})
|
||||
}
|
||||
|
||||
// TeaDeleteBranch removes the given branch locally, and if `remoteBranch` is
|
||||
// not empty deletes it at it's remote repo.
|
||||
func (r TeaRepo) TeaDeleteBranch(branch *git_config.Branch, remoteBranch string, auth git_transport.AuthMethod) error {
|
||||
err := r.DeleteBranch(branch.Name)
|
||||
// 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)
|
||||
if err != nil && err.Error() != "branch not found" {
|
||||
return err
|
||||
}
|
||||
err = r.Storer.RemoveReference(git_plumbing.NewBranchReferenceName(branch.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if remoteBranch != "" {
|
||||
// delete remote branch via git protocol:
|
||||
// an empty source in the refspec means remote deletion to git 🙃
|
||||
refspec := fmt.Sprintf(":%s", git_plumbing.NewBranchReferenceName(remoteBranch))
|
||||
err = r.Push(&git.PushOptions{
|
||||
RemoteName: branch.Remote,
|
||||
RefSpecs: []git_config.RefSpec{git_config.RefSpec(refspec)},
|
||||
Prune: true,
|
||||
Auth: auth,
|
||||
})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// TeaFindBranchBySha returns a branch that is at the the given SHA and syncs to the
|
||||
// given remote repo.
|
||||
func (r TeaRepo) TeaFindBranchBySha(sha, repoURL string) (b *git_config.Branch, err error) {
|
||||
// find remote matching our repoURL
|
||||
remote, err := r.GetRemote(repoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if remote == nil {
|
||||
return nil, fmt.Errorf("No remote found for '%s'", repoURL)
|
||||
}
|
||||
remoteName := remote.Config().Name
|
||||
|
||||
// check if the given remote has our branch (.git/refs/remotes/<remoteName>/*)
|
||||
iter, err := r.References()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer iter.Close()
|
||||
var remoteRefName git_plumbing.ReferenceName
|
||||
var localRefName git_plumbing.ReferenceName
|
||||
err = iter.ForEach(func(ref *git_plumbing.Reference) error {
|
||||
if ref.Name().IsRemote() {
|
||||
name := ref.Name().Short()
|
||||
if ref.Hash().String() == sha && strings.HasPrefix(name, remoteName) {
|
||||
remoteRefName = ref.Name()
|
||||
}
|
||||
}
|
||||
|
||||
if ref.Name().IsBranch() && ref.Hash().String() == sha {
|
||||
localRefName = ref.Name()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if remoteRefName == "" || localRefName == "" {
|
||||
// no remote tracking branch found, so a potential local branch
|
||||
// can't be a match either
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b = &git_config.Branch{
|
||||
Remote: remoteName,
|
||||
Name: localRefName.Short(),
|
||||
Merge: localRefName,
|
||||
}
|
||||
return b, b.Validate()
|
||||
}
|
||||
|
||||
// TeaFindBranchByName returns a branch that is at the the given local and
|
||||
// remote names and syncs to the given remote repo. This method is less precise
|
||||
// than TeaFindBranchBySha(), but may be desirable if local and remote branch
|
||||
// have diverged.
|
||||
func (r TeaRepo) TeaFindBranchByName(branchName, repoURL string) (b *git_config.Branch, err error) {
|
||||
// find remote matching our repoURL
|
||||
remote, err := r.GetRemote(repoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if remote == nil {
|
||||
return nil, fmt.Errorf("No remote found for '%s'", repoURL)
|
||||
}
|
||||
remoteName := remote.Config().Name
|
||||
|
||||
// check if the given remote has our branch (.git/refs/remotes/<remoteName>/*)
|
||||
iter, err := r.References()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer iter.Close()
|
||||
var remoteRefName git_plumbing.ReferenceName
|
||||
var localRefName git_plumbing.ReferenceName
|
||||
var remoteSearchingName = fmt.Sprintf("%s/%s", remoteName, branchName)
|
||||
err = iter.ForEach(func(ref *git_plumbing.Reference) error {
|
||||
if ref.Name().IsRemote() && ref.Name().Short() == remoteSearchingName {
|
||||
remoteRefName = ref.Name()
|
||||
}
|
||||
n := ref.Name()
|
||||
if n.IsBranch() && n.Short() == branchName {
|
||||
localRefName = n
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if remoteRefName == "" || localRefName == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b = &git_config.Branch{
|
||||
Remote: remoteName,
|
||||
Name: localRefName.Short(),
|
||||
Merge: localRefName,
|
||||
}
|
||||
return b, b.Validate()
|
||||
}
|
||||
|
||||
// TeaFindBranchRemote gives the first remote that has a branch with the same name or sha,
|
||||
// depending on what is passed in.
|
||||
// This function is needed, as git does not always define branches in .git/config with remote entries.
|
||||
func (r TeaRepo) TeaFindBranchRemote(branchName, hash string) (*git.Remote, error) {
|
||||
remotes, err := r.Remotes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(remotes) == 0:
|
||||
return nil, nil
|
||||
case len(remotes) == 1:
|
||||
return remotes[0], nil
|
||||
}
|
||||
|
||||
// check if the given remote has our branch (.git/refs/remotes/<remoteName>/*)
|
||||
iter, err := r.References()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer iter.Close()
|
||||
|
||||
var match *git.Remote
|
||||
err = iter.ForEach(func(ref *git_plumbing.Reference) error {
|
||||
if ref.Name().IsRemote() {
|
||||
names := strings.SplitN(ref.Name().Short(), "/", 2)
|
||||
remote := names[0]
|
||||
branch := names[1]
|
||||
hashMatch := hash != "" && hash == ref.Hash().String()
|
||||
nameMatch := branchName != "" && branchName == branch
|
||||
if hashMatch || nameMatch {
|
||||
match, err = r.Remote(remote)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return match, err
|
||||
}
|
||||
|
||||
// TeaGetCurrentBranchName return the name of the branch witch is currently active
|
||||
func (r TeaRepo) TeaGetCurrentBranchName() (string, error) {
|
||||
localHead, err := r.Head()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !localHead.Name().IsBranch() {
|
||||
return "", fmt.Errorf("active ref is no branch")
|
||||
}
|
||||
|
||||
return strings.TrimPrefix(localHead.Name().String(), "refs/heads/"), nil
|
||||
}
|
76
modules/git/remote.go
Normal file
76
modules/git/remote.go
Normal file
@ -0,0 +1,76 @@
|
||||
// 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 git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
git_config "github.com/go-git/go-git/v5/config"
|
||||
)
|
||||
|
||||
// GetRemote tries to match a Remote of the repo via the given URL.
|
||||
// Matching is based on the normalized URL, accepting different protocols.
|
||||
func (r TeaRepo) GetRemote(remoteURL string) (*git.Remote, error) {
|
||||
repoURL, err := ParseURL(remoteURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remotes, err := r.Remotes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range remotes {
|
||||
for _, u := range r.Config().URLs {
|
||||
remoteURL, err := ParseURL(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if remoteURL.Host == repoURL.Host && remoteURL.Path == repoURL.Path {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetOrCreateRemote tries to match a Remote of the repo via the given URL.
|
||||
// If no match is found, a new Remote with `newRemoteName` is created.
|
||||
// Matching is based on the normalized URL, accepting different protocols.
|
||||
func (r TeaRepo) GetOrCreateRemote(remoteURL, newRemoteName string) (*git.Remote, error) {
|
||||
localRemote, err := r.GetRemote(remoteURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if no match found, create a new remote
|
||||
if localRemote == nil {
|
||||
localRemote, err = r.CreateRemote(&git_config.RemoteConfig{
|
||||
Name: newRemoteName,
|
||||
URLs: []string{remoteURL},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return localRemote, nil
|
||||
}
|
||||
|
||||
// TeaRemoteURL returns the first url entry for the given remote name
|
||||
func (r TeaRepo) TeaRemoteURL(name string) (auth *url.URL, err error) {
|
||||
remote, err := r.Remote(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
urls := remote.Config().URLs
|
||||
if len(urls) == 0 {
|
||||
return nil, fmt.Errorf("remote %s has no URL configured", name)
|
||||
}
|
||||
return ParseURL(remote.Config().URLs[0])
|
||||
}
|
35
modules/git/repo.go
Normal file
35
modules/git/repo.go
Normal file
@ -0,0 +1,35 @@
|
||||
// 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 git
|
||||
|
||||
import (
|
||||
"github.com/go-git/go-git/v5"
|
||||
)
|
||||
|
||||
// TeaRepo is a go-git Repository, with an extended high level interface.
|
||||
type TeaRepo struct {
|
||||
*git.Repository
|
||||
}
|
||||
|
||||
// RepoForWorkdir tries to open the git repository in the local directory
|
||||
// for reading or modification.
|
||||
func RepoForWorkdir() (*TeaRepo, error) {
|
||||
return RepoFromPath("")
|
||||
}
|
||||
|
||||
// RepoFromPath tries to open the git repository by path
|
||||
func RepoFromPath(path string) (*TeaRepo, error) {
|
||||
if len(path) == 0 {
|
||||
path = "./"
|
||||
}
|
||||
repo, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{
|
||||
DetectDotGit: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TeaRepo{repo}, nil
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
@ -36,6 +40,11 @@ func (p *URLParser) Parse(rawURL string) (u *url.URL, err error) {
|
||||
u.Path = strings.TrimPrefix(u.Path, "/")
|
||||
}
|
||||
|
||||
// .git suffix is optional and breaks normalization
|
||||
if strings.HasSuffix(u.Path, ".git") {
|
||||
u.Path = strings.TrimSuffix(u.Path, ".git")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
91
modules/interact/login.go
Normal file
91
modules/interact/login.go
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package interact
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/modules/task"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
)
|
||||
|
||||
// CreateLogin create an login interactive
|
||||
func CreateLogin() error {
|
||||
var name, token, user, passwd, sshKey, giteaURL string
|
||||
var insecure = false
|
||||
|
||||
promptI := &survey.Input{Message: "URL of Gitea instance: "}
|
||||
if err := survey.AskOne(promptI, &giteaURL, survey.WithValidator(survey.Required)); err != nil {
|
||||
return err
|
||||
}
|
||||
giteaURL = strings.TrimSuffix(strings.TrimSpace(giteaURL), "/")
|
||||
if len(giteaURL) == 0 {
|
||||
fmt.Println("URL is required!")
|
||||
return nil
|
||||
}
|
||||
|
||||
name, err := task.GenerateLoginName(giteaURL, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
promptI = &survey.Input{Message: "Name of new Login [" + name + "]: "}
|
||||
if err := survey.AskOne(promptI, &name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var hasToken bool
|
||||
promptYN := &survey.Confirm{
|
||||
Message: "Do you have an access token?",
|
||||
Default: false,
|
||||
}
|
||||
if err = survey.AskOne(promptYN, &hasToken); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hasToken {
|
||||
promptI = &survey.Input{Message: "Token: "}
|
||||
if err := survey.AskOne(promptI, &token, survey.WithValidator(survey.Required)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
promptI = &survey.Input{Message: "Username: "}
|
||||
if err = survey.AskOne(promptI, &user, survey.WithValidator(survey.Required)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
promptPW := &survey.Password{Message: "Password: "}
|
||||
if err = survey.AskOne(promptPW, &passwd, survey.WithValidator(survey.Required)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var optSettings bool
|
||||
promptYN = &survey.Confirm{
|
||||
Message: "Set Optional settings: ",
|
||||
Default: false,
|
||||
}
|
||||
if err = survey.AskOne(promptYN, &optSettings); err != nil {
|
||||
return err
|
||||
}
|
||||
if optSettings {
|
||||
promptI = &survey.Input{Message: "SSH Key Path (leave empty for auto-discovery):"}
|
||||
if err := survey.AskOne(promptI, &sshKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
promptYN = &survey.Confirm{
|
||||
Message: "Allow Insecure connections: ",
|
||||
Default: false,
|
||||
}
|
||||
if err = survey.AskOne(promptYN, &insecure); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return task.CreateLogin(name, token, user, passwd, sshKey, giteaURL, insecure)
|
||||
}
|
16
modules/interact/prompts.go
Normal file
16
modules/interact/prompts.go
Normal file
@ -0,0 +1,16 @@
|
||||
// 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 (
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
)
|
||||
|
||||
// PromptPassword asks for a password and blocks until input was made.
|
||||
func PromptPassword(name string) (pass string, err error) {
|
||||
promptPW := &survey.Password{Message: name + " password:"}
|
||||
err = survey.AskOne(promptPW, &pass, survey.WithValidator(survey.Required))
|
||||
return
|
||||
}
|
133
modules/interact/pull_create.go
Normal file
133
modules/interact/pull_create.go
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package interact
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/git"
|
||||
"code.gitea.io/tea/modules/task"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
)
|
||||
|
||||
// CreatePull interactively creates a PR
|
||||
func CreatePull(login *config.Login, owner, repo string) error {
|
||||
var base, head, title, description string
|
||||
|
||||
// owner, repo
|
||||
owner, repo, err := promptRepoSlug(owner, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// base
|
||||
baseBranch, err := task.GetDefaultPRBase(login, owner, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
promptI := &survey.Input{Message: "Target branch [" + baseBranch + "]:"}
|
||||
if err := survey.AskOne(promptI, &base); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(base) == 0 {
|
||||
base = baseBranch
|
||||
}
|
||||
|
||||
// head
|
||||
localRepo, err := git.RepoForWorkdir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
promptOpts := survey.WithValidator(survey.Required)
|
||||
headOwner, headBranch, err := task.GetDefaultPRHead(localRepo)
|
||||
if err == nil {
|
||||
promptOpts = nil
|
||||
}
|
||||
var headOwnerInput, headBranchInput string
|
||||
promptI = &survey.Input{Message: "Source repo owner [" + headOwner + "]:"}
|
||||
if err := survey.AskOne(promptI, &headOwnerInput); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(headOwnerInput) != 0 {
|
||||
headOwner = headOwnerInput
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// description
|
||||
promptM := &survey.Multiline{Message: "PR description:"}
|
||||
if err := survey.AskOne(promptM, &description); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return task.CreatePull(
|
||||
login,
|
||||
owner,
|
||||
repo,
|
||||
base,
|
||||
head,
|
||||
title,
|
||||
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
|
||||
}
|
91
modules/print/issue.go
Normal file
91
modules/print/issue.go
Normal file
@ -0,0 +1,91 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// IssueDetails print an issue rendered to stdout
|
||||
func IssueDetails(issue *gitea.Issue) {
|
||||
outputMarkdown(fmt.Sprintf(
|
||||
"# #%d %s (%s)\n@%s created %s\n\n%s\n",
|
||||
issue.Index,
|
||||
issue.Title,
|
||||
issue.State,
|
||||
issue.Poster.UserName,
|
||||
FormatTime(issue.Created),
|
||||
issue.Body,
|
||||
))
|
||||
}
|
||||
|
||||
// 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
|
||||
// TODO combine with IssuesList
|
||||
func IssuesPullsList(issues []*gitea.Issue, output string) {
|
||||
t := tableWithHeader(
|
||||
"Index",
|
||||
"State",
|
||||
"Kind",
|
||||
"Author",
|
||||
"Updated",
|
||||
"Title",
|
||||
)
|
||||
|
||||
for _, issue := range issues {
|
||||
name := issue.Poster.FullName
|
||||
if len(name) == 0 {
|
||||
name = issue.Poster.UserName
|
||||
}
|
||||
kind := "Issue"
|
||||
if issue.PullRequest != nil {
|
||||
kind = "Pull"
|
||||
}
|
||||
t.addRow(
|
||||
strconv.FormatInt(issue.Index, 10),
|
||||
string(issue.State),
|
||||
kind,
|
||||
name,
|
||||
FormatTime(issue.Updated),
|
||||
issue.Title,
|
||||
)
|
||||
}
|
||||
|
||||
t.print(output)
|
||||
}
|
37
modules/print/label.go
Normal file
37
modules/print/label.go
Normal file
@ -0,0 +1,37 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
// LabelsList prints a listing of labels
|
||||
func LabelsList(labels []*gitea.Label, output string) {
|
||||
t := tableWithHeader(
|
||||
"Index",
|
||||
"Color",
|
||||
"Name",
|
||||
"Description",
|
||||
)
|
||||
|
||||
p := termenv.ColorProfile()
|
||||
|
||||
for _, label := range labels {
|
||||
color := termenv.String(label.Color)
|
||||
|
||||
t.addRow(
|
||||
strconv.FormatInt(label.ID, 10),
|
||||
fmt.Sprint(color.Background(p.Color("#"+label.Color))),
|
||||
label.Name,
|
||||
label.Description,
|
||||
)
|
||||
}
|
||||
t.print(output)
|
||||
}
|
55
modules/print/login.go
Normal file
55
modules/print/login.go
Normal file
@ -0,0 +1,55 @@
|
||||
// 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"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/tea/modules/config"
|
||||
)
|
||||
|
||||
// LoginDetails print login entry to stdout
|
||||
func LoginDetails(login *config.Login, output string) {
|
||||
in := fmt.Sprintf("# %s\n\n[@%s](%s/%s)\n",
|
||||
login.Name,
|
||||
login.User,
|
||||
strings.TrimSuffix(login.URL, "/"),
|
||||
login.User,
|
||||
)
|
||||
if len(login.SSHKey) != 0 {
|
||||
in += fmt.Sprintf("\nSSH Key: '%s' via %s\n",
|
||||
login.SSHKey,
|
||||
login.SSHHost,
|
||||
)
|
||||
}
|
||||
in += fmt.Sprintf("\nCreated: %s", time.Unix(login.Created, 0).Format(time.RFC822))
|
||||
|
||||
outputMarkdown(in)
|
||||
}
|
||||
|
||||
// LoginsList prints a listing of logins
|
||||
func LoginsList(logins []config.Login, output string) {
|
||||
t := tableWithHeader(
|
||||
"Name",
|
||||
"URL",
|
||||
"SSHHost",
|
||||
"User",
|
||||
"Default",
|
||||
)
|
||||
|
||||
for _, l := range logins {
|
||||
t.addRow(
|
||||
l.Name,
|
||||
l.URL,
|
||||
l.GetSSHHost(),
|
||||
l.User,
|
||||
fmt.Sprint(l.Default),
|
||||
)
|
||||
}
|
||||
|
||||
t.print(output)
|
||||
}
|
24
modules/print/markdown.go
Normal file
24
modules/print/markdown.go
Normal file
@ -0,0 +1,24 @@
|
||||
// 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"
|
||||
|
||||
"github.com/charmbracelet/glamour"
|
||||
)
|
||||
|
||||
// outputMarkdown prints markdown to stdout, formatted for terminals.
|
||||
// If the input could not be parsed, it is printed unformatted, the error
|
||||
// is returned anyway.
|
||||
func outputMarkdown(markdown string) error {
|
||||
out, err := glamour.Render(markdown, "auto")
|
||||
if err != nil {
|
||||
fmt.Printf(markdown)
|
||||
return err
|
||||
}
|
||||
fmt.Print(out)
|
||||
return nil
|
||||
}
|
63
modules/print/milestone.go
Normal file
63
modules/print/milestone.go
Normal file
@ -0,0 +1,63 @@
|
||||
// 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"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// MilestoneDetails print an milestone formatted to stdout
|
||||
func MilestoneDetails(milestone *gitea.Milestone) {
|
||||
fmt.Printf("%s\n",
|
||||
milestone.Title,
|
||||
)
|
||||
if len(milestone.Description) != 0 {
|
||||
fmt.Printf("\n%s\n", milestone.Description)
|
||||
}
|
||||
if milestone.Deadline != nil && !milestone.Deadline.IsZero() {
|
||||
fmt.Printf("\nDeadline: %s\n", FormatTime(*milestone.Deadline))
|
||||
}
|
||||
}
|
||||
|
||||
// MilestonesList prints a listing of milestones
|
||||
func MilestonesList(miles []*gitea.Milestone, output string, state gitea.StateType) {
|
||||
headers := []string{
|
||||
"Title",
|
||||
}
|
||||
if state == gitea.StateAll {
|
||||
headers = append(headers, "State")
|
||||
}
|
||||
headers = append(headers,
|
||||
"Open/Closed Issues",
|
||||
"DueDate",
|
||||
)
|
||||
|
||||
t := table{headers: headers}
|
||||
|
||||
for _, m := range miles {
|
||||
var deadline = ""
|
||||
|
||||
if m.Deadline != nil && !m.Deadline.IsZero() {
|
||||
deadline = FormatTime(*m.Deadline)
|
||||
}
|
||||
|
||||
item := []string{
|
||||
m.Title,
|
||||
}
|
||||
if state == gitea.StateAll {
|
||||
item = append(item, string(m.State))
|
||||
}
|
||||
item = append(item,
|
||||
fmt.Sprintf("%d/%d", m.OpenIssues, m.ClosedIssues),
|
||||
deadline,
|
||||
)
|
||||
t.addRowSlice(item)
|
||||
}
|
||||
|
||||
t.sort(0, true)
|
||||
t.print(output)
|
||||
}
|
51
modules/print/notification.go
Normal file
51
modules/print/notification.go
Normal file
@ -0,0 +1,51 @@
|
||||
// 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 (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// NotificationsList prints a listing of notification threads
|
||||
func NotificationsList(news []*gitea.NotificationThread, output string, showRepository bool) {
|
||||
headers := []string{
|
||||
"Type",
|
||||
"Index",
|
||||
"Title",
|
||||
}
|
||||
if showRepository {
|
||||
headers = append(headers, "Repository")
|
||||
}
|
||||
|
||||
t := table{headers: headers}
|
||||
|
||||
for _, n := range news {
|
||||
if n.Subject == nil {
|
||||
continue
|
||||
}
|
||||
// if pull or Issue get Index
|
||||
var index string
|
||||
if n.Subject.Type == "Issue" || n.Subject.Type == "Pull" {
|
||||
index = n.Subject.URL
|
||||
urlParts := strings.Split(n.Subject.URL, "/")
|
||||
if len(urlParts) != 0 {
|
||||
index = urlParts[len(urlParts)-1]
|
||||
}
|
||||
index = "#" + index
|
||||
}
|
||||
|
||||
item := []string{n.Subject.Type, index, n.Subject.Title}
|
||||
if showRepository {
|
||||
item = append(item, n.Repository.FullName)
|
||||
}
|
||||
t.addRowSlice(item)
|
||||
}
|
||||
|
||||
if t.Len() != 0 {
|
||||
t.print(output)
|
||||
}
|
||||
}
|
39
modules/print/organization.go
Normal file
39
modules/print/organization.go
Normal file
@ -0,0 +1,39 @@
|
||||
// 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"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// OrganizationsList prints a listing of the organizations
|
||||
func OrganizationsList(organizations []*gitea.Organization, output string) {
|
||||
if len(organizations) == 0 {
|
||||
fmt.Println("No organizations found")
|
||||
return
|
||||
}
|
||||
|
||||
t := tableWithHeader(
|
||||
"Name",
|
||||
"FullName",
|
||||
"Website",
|
||||
"Location",
|
||||
"Description",
|
||||
)
|
||||
|
||||
for _, org := range organizations {
|
||||
t.addRow(
|
||||
org.UserName,
|
||||
org.FullName,
|
||||
org.Website,
|
||||
org.Location,
|
||||
org.Description,
|
||||
)
|
||||
}
|
||||
|
||||
t.print(output)
|
||||
}
|
35
modules/print/print.go
Normal file
35
modules/print/print.go
Normal file
@ -0,0 +1,35 @@
|
||||
// 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")
|
||||
}
|
95
modules/print/pull.go
Normal file
95
modules/print/pull.go
Normal file
@ -0,0 +1,95 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// PullDetails print an pull rendered to stdout
|
||||
func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview) {
|
||||
base := pr.Base.Name
|
||||
head := pr.Head.Name
|
||||
if pr.Head.RepoID != pr.Base.RepoID {
|
||||
if pr.Head.Repository != nil {
|
||||
head = pr.Head.Repository.Owner.UserName + ":" + head
|
||||
} else {
|
||||
head = "delete:" + head
|
||||
}
|
||||
}
|
||||
|
||||
out := fmt.Sprintf(
|
||||
"# #%d %s (%s)\n@%s created %s\t**%s** <- **%s**\n\n%s\n",
|
||||
pr.Index,
|
||||
pr.Title,
|
||||
pr.State,
|
||||
pr.Poster.UserName,
|
||||
FormatTime(*pr.Created),
|
||||
base,
|
||||
head,
|
||||
pr.Body,
|
||||
)
|
||||
|
||||
if len(reviews) != 0 {
|
||||
out += "\n"
|
||||
revMap := make(map[string]gitea.ReviewStateType)
|
||||
for _, review := range reviews {
|
||||
switch review.State {
|
||||
case gitea.ReviewStateApproved,
|
||||
gitea.ReviewStateRequestChanges,
|
||||
gitea.ReviewStateRequestReview:
|
||||
revMap[review.Reviewer.UserName] = review.State
|
||||
}
|
||||
}
|
||||
for k, v := range revMap {
|
||||
out += fmt.Sprintf("\n @%s: %s", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if pr.State == gitea.StateOpen && pr.Mergeable {
|
||||
out += "\nNo Conflicts"
|
||||
}
|
||||
|
||||
outputMarkdown(out)
|
||||
}
|
||||
|
||||
// PullsList prints a listing of pulls
|
||||
func PullsList(prs []*gitea.PullRequest, output string) {
|
||||
t := tableWithHeader(
|
||||
"Index",
|
||||
"Title",
|
||||
"State",
|
||||
"Author",
|
||||
"Milestone",
|
||||
"Updated",
|
||||
)
|
||||
|
||||
for _, pr := range prs {
|
||||
if pr == nil {
|
||||
continue
|
||||
}
|
||||
author := pr.Poster.FullName
|
||||
if len(author) == 0 {
|
||||
author = pr.Poster.UserName
|
||||
}
|
||||
mile := ""
|
||||
if pr.Milestone != nil {
|
||||
mile = pr.Milestone.Title
|
||||
}
|
||||
t.addRow(
|
||||
strconv.FormatInt(pr.Index, 10),
|
||||
pr.Title,
|
||||
string(pr.State),
|
||||
author,
|
||||
mile,
|
||||
FormatTime(*pr.Updated),
|
||||
)
|
||||
}
|
||||
|
||||
t.print(output)
|
||||
}
|
38
modules/print/release.go
Normal file
38
modules/print/release.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 print
|
||||
|
||||
import (
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// ReleasesList prints a listing of releases
|
||||
func ReleasesList(releases []*gitea.Release, output string) {
|
||||
t := tableWithHeader(
|
||||
"Tag-Name",
|
||||
"Title",
|
||||
"Published At",
|
||||
"Status",
|
||||
"Tar URL",
|
||||
)
|
||||
|
||||
for _, release := range releases {
|
||||
status := "released"
|
||||
if release.IsDraft {
|
||||
status = "draft"
|
||||
} else if release.IsPrerelease {
|
||||
status = "prerelease"
|
||||
}
|
||||
t.addRow(
|
||||
release.TagName,
|
||||
release.Title,
|
||||
FormatTime(release.PublishedAt),
|
||||
status,
|
||||
release.TarURL,
|
||||
)
|
||||
}
|
||||
|
||||
t.print(output)
|
||||
}
|
163
modules/print/repo.go
Normal file
163
modules/print/repo.go
Normal file
@ -0,0 +1,163 @@
|
||||
// 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"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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
|
||||
func ReposList(repos []*gitea.Repository, output string, fields []string) {
|
||||
if len(repos) == 0 {
|
||||
fmt.Println("No repositories found")
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// RepoDetails print an repo formatted to stdout
|
||||
func RepoDetails(repo *gitea.Repository, topics []string) {
|
||||
title := "# " + repo.FullName
|
||||
if repo.Mirror {
|
||||
title += " (mirror)"
|
||||
}
|
||||
if repo.Fork {
|
||||
title += " (fork)"
|
||||
}
|
||||
if repo.Archived {
|
||||
title += " (archived)"
|
||||
}
|
||||
if repo.Empty {
|
||||
title += " (empty)"
|
||||
}
|
||||
title += "\n"
|
||||
|
||||
var desc string
|
||||
if len(repo.Description) != 0 {
|
||||
desc = fmt.Sprintf("*%s*\n\n", repo.Description)
|
||||
}
|
||||
|
||||
stats := fmt.Sprintf(
|
||||
"Issues: %d, Stars: %d, Forks: %d, Size: %s\n",
|
||||
repo.OpenIssues,
|
||||
repo.Stars,
|
||||
repo.Forks,
|
||||
formatSize(int64(repo.Size)),
|
||||
)
|
||||
|
||||
// NOTE: for mirrors, this is the time the mirror was last fetched..
|
||||
updated := fmt.Sprintf(
|
||||
"Updated: %s (%s ago)\n",
|
||||
repo.Updated.Format("2006-01-02 15:04"),
|
||||
time.Now().Sub(repo.Updated).Truncate(time.Minute),
|
||||
)
|
||||
|
||||
urls := fmt.Sprintf(
|
||||
"- Browse:\t%s\n- Clone:\t%s\n",
|
||||
repo.HTMLURL,
|
||||
repo.SSHURL,
|
||||
)
|
||||
if len(repo.Website) != 0 {
|
||||
urls += fmt.Sprintf("- Web:\t%s\n", repo.Website)
|
||||
}
|
||||
|
||||
perm := fmt.Sprintf(
|
||||
"- Permission:\t%s\n",
|
||||
fieldFormatters["permission"](repo),
|
||||
)
|
||||
|
||||
var tops string
|
||||
if len(topics) != 0 {
|
||||
tops = fmt.Sprintf("- Topics:\t%s\n", strings.Join(topics, ", "))
|
||||
}
|
||||
|
||||
outputMarkdown(fmt.Sprintf(
|
||||
"%s%s\n%s\n%s%s%s%s",
|
||||
title,
|
||||
desc,
|
||||
stats,
|
||||
updated,
|
||||
urls,
|
||||
perm,
|
||||
tops,
|
||||
))
|
||||
}
|
@ -1,48 +1,75 @@
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
package print
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
var (
|
||||
showLog bool
|
||||
)
|
||||
// table provides infrastructure to easily print (sorted) lists in different formats
|
||||
type table struct {
|
||||
headers []string
|
||||
values [][]string
|
||||
sortDesc bool // used internally by sortable interface
|
||||
sortColumn uint // ↑
|
||||
}
|
||||
|
||||
const outputUsage = "Specify output format - table (default), csv, simple, tsv or yaml."
|
||||
func tableWithHeader(header ...string) table {
|
||||
return table{headers: header}
|
||||
}
|
||||
|
||||
// Println println content according the flag
|
||||
func Println(a ...interface{}) {
|
||||
if showLog {
|
||||
fmt.Println(a...)
|
||||
// it's the callers responsibility to ensure row length is equal to header length!
|
||||
func (t *table) addRow(row ...string) {
|
||||
t.addRowSlice(row)
|
||||
}
|
||||
|
||||
// it's the callers responsibility to ensure row length is equal to header length!
|
||||
func (t *table) addRowSlice(row []string) {
|
||||
t.values = append(t.values, row)
|
||||
}
|
||||
|
||||
func (t *table) sort(column uint, desc bool) {
|
||||
t.sortColumn = column
|
||||
t.sortDesc = desc
|
||||
sort.Stable(t) // stable to allow multiple calls to sort
|
||||
}
|
||||
|
||||
// sortable interface
|
||||
func (t table) Len() int { return len(t.values) }
|
||||
func (t table) Swap(i, j int) { t.values[i], t.values[j] = t.values[j], t.values[i] }
|
||||
func (t table) Less(i, j int) bool {
|
||||
const column = 0
|
||||
if t.sortDesc {
|
||||
i, j = j, i
|
||||
}
|
||||
return t.values[i][t.sortColumn] < t.values[j][t.sortColumn]
|
||||
}
|
||||
|
||||
// Printf printf content according the flag
|
||||
func Printf(format string, a ...interface{}) {
|
||||
if showLog {
|
||||
fmt.Printf(format, a...)
|
||||
func (t *table) print(output string) {
|
||||
switch {
|
||||
case output == "" || output == "table":
|
||||
outputtable(t.headers, t.values)
|
||||
case output == "csv":
|
||||
outputdsv(t.headers, t.values, ",")
|
||||
case output == "simple":
|
||||
outputsimple(t.headers, t.values)
|
||||
case output == "tsv":
|
||||
outputdsv(t.headers, t.values, "\t")
|
||||
case output == "yaml":
|
||||
outputyaml(t.headers, t.values)
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// Error println content as an error information
|
||||
func Error(a ...interface{}) {
|
||||
fmt.Println(a...)
|
||||
}
|
||||
|
||||
// Errorf printf content as an error information
|
||||
func Errorf(format string, a ...interface{}) {
|
||||
fmt.Printf(format, a...)
|
||||
}
|
||||
|
||||
// outputtable prints structured data as table
|
||||
func outputtable(headers []string, values [][]string) {
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
@ -92,22 +119,3 @@ func outputyaml(headers []string, values [][]string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output provides general function to convert given information
|
||||
// into several outputs
|
||||
func Output(output string, headers []string, values [][]string) {
|
||||
switch {
|
||||
case output == "" || output == "table":
|
||||
outputtable(headers, values)
|
||||
case output == "csv":
|
||||
outputdsv(headers, values, ",")
|
||||
case output == "simple":
|
||||
outputsimple(headers, values)
|
||||
case output == "tsv":
|
||||
outputdsv(headers, values, "\t")
|
||||
case output == "yaml":
|
||||
outputyaml(headers, values)
|
||||
default:
|
||||
Errorf("unknown output type '" + output + "', available types are:\n- csv: comma-separated values\n- simple: space-separated values\n- table: auto-aligned table format (default)\n- tsv: tab-separated values\n- yaml: YAML format\n")
|
||||
}
|
||||
}
|
55
modules/print/times.go
Normal file
55
modules/print/times.go
Normal file
@ -0,0 +1,55 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"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
|
||||
func TrackedTimesList(times []*gitea.TrackedTime, outputType string, from, until time.Time, printTotal bool) {
|
||||
tab := tableWithHeader(
|
||||
"Created",
|
||||
"Issue",
|
||||
"User",
|
||||
"Duration",
|
||||
)
|
||||
var totalDuration int64
|
||||
|
||||
for _, t := range times {
|
||||
if !from.IsZero() && from.After(t.Created) {
|
||||
continue
|
||||
}
|
||||
if !until.IsZero() && until.Before(t.Created) {
|
||||
continue
|
||||
}
|
||||
|
||||
totalDuration += t.Time
|
||||
tab.addRow(
|
||||
FormatTime(t.Created),
|
||||
"#"+strconv.FormatInt(t.Issue.Index, 10),
|
||||
t.UserName,
|
||||
formatDuration(t.Time, outputType),
|
||||
)
|
||||
}
|
||||
|
||||
if printTotal {
|
||||
tab.addRow("TOTAL", "", "", formatDuration(totalDuration, outputType))
|
||||
}
|
||||
tab.print(outputType)
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package setting
|
||||
|
||||
// App related variables
|
||||
var (
|
||||
AppVer string
|
||||
AppBuiltWith string
|
||||
)
|
29
modules/task/labels_export.go
Normal file
29
modules/task/labels_export.go
Normal file
@ -0,0 +1,29 @@
|
||||
// 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"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// LabelsExport save list of labels to disc
|
||||
func LabelsExport(labels []*gitea.Label, path string) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
for _, label := range labels {
|
||||
if _, err := fmt.Fprintf(f, "#%s %s\n", label.Color, label.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
142
modules/task/login_create.go
Normal file
142
modules/task/login_create.go
Normal file
@ -0,0 +1,142 @@
|
||||
// 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"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// CreateLogin create a login to be stored in config
|
||||
func CreateLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bool) error {
|
||||
// checks ...
|
||||
// ... if we have a url
|
||||
if len(giteaURL) == 0 {
|
||||
log.Fatal("You have to input Gitea server URL")
|
||||
}
|
||||
|
||||
// ... if there already exist a login with same name
|
||||
if login := config.GetLoginByName(name); login != nil {
|
||||
return fmt.Errorf("login name '%s' has already been used", login.Name)
|
||||
}
|
||||
// ... if we already use this token
|
||||
if login := config.GetLoginByToken(token); login != nil {
|
||||
return fmt.Errorf("token already been used, delete login '%s' first", login.Name)
|
||||
}
|
||||
|
||||
// .. if we have enough information to authenticate
|
||||
if len(token) == 0 && (len(user)+len(passwd)) == 0 {
|
||||
log.Fatal("No token set")
|
||||
} else if len(user) != 0 && len(passwd) == 0 {
|
||||
log.Fatal("No password set")
|
||||
} else if len(user) == 0 && len(passwd) != 0 {
|
||||
log.Fatal("No user set")
|
||||
}
|
||||
|
||||
// Normalize URL
|
||||
serverURL, err := utils.NormalizeURL(giteaURL)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to parse URL", err)
|
||||
}
|
||||
|
||||
login := config.Login{
|
||||
Name: name,
|
||||
URL: serverURL.String(),
|
||||
Token: token,
|
||||
Insecure: insecure,
|
||||
SSHKey: sshKey,
|
||||
Created: time.Now().Unix(),
|
||||
}
|
||||
|
||||
client := login.Client()
|
||||
|
||||
if len(token) == 0 {
|
||||
login.Token, err = generateToken(client, user, passwd)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify if authentication works and get user info
|
||||
u, _, err := client.GetMyUserInfo()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
login.User = u.UserName
|
||||
|
||||
if len(login.Name) == 0 {
|
||||
login.Name, err = GenerateLoginName(giteaURL, login.User)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// we do not have a method to get SSH config from api,
|
||||
// so we just use the hostname
|
||||
login.SSHHost = serverURL.Hostname()
|
||||
|
||||
if len(sshKey) == 0 {
|
||||
login.SSHKey, err = findSSHKey(client)
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: problem while finding a SSH key: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = config.AddLogin(&login)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Login as %s on %s successful. Added this login as %s\n", login.User, login.URL, login.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateToken creates a new token when given BasicAuth credentials
|
||||
func generateToken(client *gitea.Client, user, pass string) (string, error) {
|
||||
gitea.SetBasicAuth(user, pass)(client)
|
||||
|
||||
host, _ := os.Hostname()
|
||||
tl, _, err := client.ListAccessTokens(gitea.ListAccessTokensOptions{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tokenName := host + "-tea"
|
||||
|
||||
for i := range tl {
|
||||
if tl[i].Name == tokenName {
|
||||
tokenName += time.Now().Format("2006-01-02_15-04-05")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
t, _, err := client.CreateAccessToken(gitea.CreateAccessTokenOption{Name: tokenName})
|
||||
return t.Token, err
|
||||
}
|
||||
|
||||
// GenerateLoginName generates a name string based on instance URL & adds username if the result is not unique
|
||||
func GenerateLoginName(url, user string) (string, error) {
|
||||
parsedURL, err := utils.NormalizeURL(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := parsedURL.Host
|
||||
|
||||
// append user name if login name already exists
|
||||
if len(user) != 0 {
|
||||
if login := config.GetLoginByName(name); login != nil {
|
||||
return name + "_" + user, nil
|
||||
}
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
79
modules/task/login_ssh.go
Normal file
79
modules/task/login_ssh.go
Normal file
@ -0,0 +1,79 @@
|
||||
// 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 (
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// findSSHKey retrieves the ssh keys registered in gitea, and tries to find
|
||||
// a matching private key in ~/.ssh/. If no match is found, path is empty.
|
||||
func findSSHKey(client *gitea.Client) (string, error) {
|
||||
// get keys registered on gitea instance
|
||||
keys, _, err := client.ListMyPublicKeys(gitea.ListPublicKeysOptions{})
|
||||
if err != nil || len(keys) == 0 {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// enumerate ~/.ssh/*.pub files
|
||||
glob, err := utils.AbsPathWithExpansion("~/.ssh/*.pub")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
localPubkeyPaths, err := filepath.Glob(glob)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// parse each local key with present privkey & compare fingerprints to online keys
|
||||
for _, pubkeyPath := range localPubkeyPaths {
|
||||
var pubkeyFile []byte
|
||||
pubkeyFile, err = ioutil.ReadFile(pubkeyPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
fields := strings.Split(string(pubkeyFile), " ")
|
||||
if len(fields) < 2 { // first word is key type, second word is key material
|
||||
continue
|
||||
}
|
||||
|
||||
var keymaterial []byte
|
||||
keymaterial, err = base64.StdEncoding.DecodeString(fields[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var pubkey ssh.PublicKey
|
||||
pubkey, err = ssh.ParsePublicKey(keymaterial)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
privkeyPath := strings.TrimSuffix(pubkeyPath, ".pub")
|
||||
var exists bool
|
||||
exists, err = utils.FileExist(privkeyPath)
|
||||
if err != nil || !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
// if pubkey fingerprints match, return path to corresponding privkey.
|
||||
fingerprint := ssh.FingerprintSHA256(pubkey)
|
||||
for _, key := range keys {
|
||||
if fingerprint == key.Fingerprint {
|
||||
return privkeyPath, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
82
modules/task/pull_checkout.go
Normal file
82
modules/task/pull_checkout.go
Normal file
@ -0,0 +1,82 @@
|
||||
// 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/tea/modules/config"
|
||||
local_git "code.gitea.io/tea/modules/git"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
)
|
||||
|
||||
// PullCheckout checkout current workdir to the head branch of specified pull request
|
||||
func PullCheckout(login *config.Login, repoOwner, repoName string, index int64, callback func(string) (string, error)) error {
|
||||
client := login.Client()
|
||||
|
||||
localRepo, err := local_git.RepoForWorkdir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// fetch PR source-localRepo & -branch from gitea
|
||||
pr, _, err := client.GetPullRequest(repoOwner, repoName, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteURL := pr.Head.Repository.CloneURL
|
||||
if len(login.SSHKey) != 0 {
|
||||
// login.SSHKey is nonempty, if user specified a key manually or we automatically
|
||||
// found a matching private key on this machine during login creation.
|
||||
// this means, we are very likely to have a working ssh setup.
|
||||
remoteURL = pr.Head.Repository.SSHURL
|
||||
}
|
||||
|
||||
// try to find a matching existing branch, otherwise return branch in pulls/ namespace
|
||||
localBranchName := fmt.Sprintf("pulls/%v-%v", index, pr.Head.Ref)
|
||||
if b, _ := localRepo.TeaFindBranchBySha(pr.Head.Sha, remoteURL); b != nil {
|
||||
localBranchName = b.Name
|
||||
}
|
||||
|
||||
newRemoteName := fmt.Sprintf("pulls/%v", pr.Head.Repository.Owner.UserName)
|
||||
|
||||
// verify related remote is in local repo, otherwise add it
|
||||
localRemote, err := localRepo.GetOrCreateRemote(remoteURL, newRemoteName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localRemoteName := localRemote.Config().Name
|
||||
|
||||
// get auth & fetch remote via its configured protocol
|
||||
url, err := localRepo.TeaRemoteURL(localRemoteName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auth, err := local_git.GetAuthForURL(url, login.Token, login.SSHKey, callback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Fetching PR %v (head %s:%s) from remote '%s'\n", index, url, pr.Head.Ref, localRemoteName)
|
||||
err = localRemote.Fetch(&git.FetchOptions{Auth: auth})
|
||||
if err == git.NoErrAlreadyUpToDate {
|
||||
fmt.Println(err)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// checkout local branch
|
||||
err = localRepo.TeaCreateBranch(localBranchName, pr.Head.Ref, localRemoteName)
|
||||
if err == nil {
|
||||
fmt.Printf("Created branch '%s'\n", localBranchName)
|
||||
} else if err == git.ErrBranchExists {
|
||||
fmt.Println("There may be changes since you last checked out, run `git pull` to get them.")
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return localRepo.TeaCheckout(localBranchName)
|
||||
}
|
86
modules/task/pull_clean.go
Normal file
86
modules/task/pull_clean.go
Normal file
@ -0,0 +1,86 @@
|
||||
// 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/tea/modules/config"
|
||||
local_git "code.gitea.io/tea/modules/git"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
git_config "github.com/go-git/go-git/v5/config"
|
||||
)
|
||||
|
||||
// PullClean deletes local & remote feature-branches for a closed pull
|
||||
func PullClean(login *config.Login, repoOwner, repoName string, index int64, ignoreSHA bool, callback func(string) (string, error)) error {
|
||||
client := login.Client()
|
||||
|
||||
repo, _, err := client.GetRepo(repoOwner, repoName)
|
||||
defaultBranch := repo.DefaultBranch
|
||||
if len(defaultBranch) == 0 {
|
||||
defaultBranch = "master"
|
||||
}
|
||||
|
||||
// fetch PR source-repo & -branch from gitea
|
||||
pr, _, err := client.GetPullRequest(repoOwner, repoName, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pr.State == gitea.StateOpen {
|
||||
return fmt.Errorf("PR is still open, won't delete branches")
|
||||
}
|
||||
|
||||
// IDEA: abort if PR.Head.Repository.CloneURL does not match login.URL?
|
||||
|
||||
r, err := local_git.RepoForWorkdir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// find a branch with matching sha or name, that has a remote matching the repo url
|
||||
var branch *git_config.Branch
|
||||
if ignoreSHA {
|
||||
branch, err = r.TeaFindBranchByName(pr.Head.Ref, pr.Head.Repository.CloneURL)
|
||||
} else {
|
||||
branch, err = r.TeaFindBranchBySha(pr.Head.Sha, pr.Head.Repository.CloneURL)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if branch == nil {
|
||||
if ignoreSHA {
|
||||
return fmt.Errorf("Remote branch %s not found in local repo", pr.Head.Ref)
|
||||
}
|
||||
return fmt.Errorf(`Remote branch %s not found in local repo.
|
||||
Either you don't track this PR, or the local branch has diverged from the remote.
|
||||
If you still want to continue & are sure you don't loose any important commits,
|
||||
call me again with the --ignore-sha flag`, pr.Head.Ref)
|
||||
}
|
||||
|
||||
// prepare deletion of local branch:
|
||||
headRef, err := r.Head()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if headRef.Name().Short() == branch.Name {
|
||||
fmt.Printf("Checking out '%s' to delete local branch '%s'\n", defaultBranch, branch.Name)
|
||||
if err = r.TeaCheckout(defaultBranch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// remove local & remote branch
|
||||
fmt.Printf("Deleting local branch %s and remote branch %s\n", branch.Name, pr.Head.Ref)
|
||||
url, err := r.TeaRemoteURL(branch.Remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auth, err := local_git.GetAuthForURL(url, login.Token, login.SSHKey, callback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.TeaDeleteBranch(branch, pr.Head.Ref, auth)
|
||||
}
|
151
modules/task/pull_create.go
Normal file
151
modules/task/pull_create.go
Normal file
@ -0,0 +1,151 @@
|
||||
// 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"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
local_git "code.gitea.io/tea/modules/git"
|
||||
"code.gitea.io/tea/modules/print"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
)
|
||||
|
||||
// CreatePull creates a PR in the given repo and prints the result
|
||||
func CreatePull(login *config.Login, repoOwner, repoName, base, head, title, description string) error {
|
||||
|
||||
// open local git repo
|
||||
localRepo, err := local_git.RepoForWorkdir()
|
||||
if err != nil {
|
||||
log.Fatal("could not open local repo: ", err)
|
||||
}
|
||||
|
||||
// push if possible
|
||||
log.Println("git push")
|
||||
err = localRepo.Push(&git.PushOptions{})
|
||||
if err != nil && err != git.NoErrAlreadyUpToDate {
|
||||
log.Printf("Error occurred during 'git push':\n%s\n", err.Error())
|
||||
}
|
||||
|
||||
// default is default branch
|
||||
if len(base) == 0 {
|
||||
base, err = GetDefaultPRBase(login, repoOwner, repoName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// default is current one
|
||||
if len(head) == 0 {
|
||||
headOwner, headBranch, err := GetDefaultPRHead(localRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
head = GetHeadSpec(headOwner, headBranch, repoOwner)
|
||||
}
|
||||
|
||||
// head & base may not be the same
|
||||
if head == base {
|
||||
return fmt.Errorf("can't create PR from %s to %s", head, base)
|
||||
}
|
||||
|
||||
// default is head branch name
|
||||
if len(title) == 0 {
|
||||
title = GetDefaultPRTitle(head)
|
||||
}
|
||||
// title is required
|
||||
if len(title) == 0 {
|
||||
return fmt.Errorf("Title is required")
|
||||
}
|
||||
|
||||
pr, _, err := login.Client().CreatePullRequest(repoOwner, repoName, gitea.CreatePullRequestOption{
|
||||
Head: head,
|
||||
Base: base,
|
||||
Title: title,
|
||||
Body: description,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("could not create PR from %s to %s:%s: %s", head, repoOwner, base, err)
|
||||
}
|
||||
|
||||
print.PullDetails(pr, nil)
|
||||
|
||||
fmt.Println(pr.HTMLURL)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetDefaultPRBase retrieves the default base branch for the given repo
|
||||
func GetDefaultPRBase(login *config.Login, owner, repo string) (string, error) {
|
||||
meta, _, err := login.Client().GetRepo(owner, repo)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not fetch repo meta: %s", err)
|
||||
}
|
||||
return meta.DefaultBranch, nil
|
||||
}
|
||||
|
||||
// GetDefaultPRHead uses the currently checked out branch, checks if
|
||||
// a remote currently holds the commit it points to, extracts the owner
|
||||
// from its URL, and assembles the result to a valid head spec for gitea.
|
||||
func GetDefaultPRHead(localRepo *local_git.TeaRepo) (owner, branch string, err error) {
|
||||
headBranch, err := localRepo.Head()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sha := headBranch.Hash().String()
|
||||
|
||||
remote, err := localRepo.TeaFindBranchRemote("", sha)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not determine remote for current branch: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if remote == nil {
|
||||
// if no remote branch is found for the local hash, we abort:
|
||||
// user has probably not configured a remote for the local branch,
|
||||
// or local branch does not represent remote state.
|
||||
err = fmt.Errorf("no matching remote found for this branch. try git push -u <remote> <branch>")
|
||||
return
|
||||
}
|
||||
|
||||
branch, err = localRepo.TeaGetCurrentBranchName()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
url, err := local_git.ParseURL(remote.Config().URLs[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
owner, _ = utils.GetOwnerAndRepo(strings.TrimLeft(url.Path, "/"), "")
|
||||
return
|
||||
}
|
||||
|
||||
// GetHeadSpec creates a head string as expected by gitea API
|
||||
func GetHeadSpec(owner, branch, baseOwner string) string {
|
||||
if len(owner) != 0 && owner != baseOwner {
|
||||
return fmt.Sprintf("%s:%s", owner, branch)
|
||||
}
|
||||
return branch
|
||||
}
|
||||
|
||||
// GetDefaultPRTitle transforms a string like a branchname to a readable text
|
||||
func GetDefaultPRTitle(head string) string {
|
||||
title := head
|
||||
if strings.Contains(title, ":") {
|
||||
title = strings.SplitN(title, ":", 2)[1]
|
||||
}
|
||||
title = strings.Replace(title, "-", " ", -1)
|
||||
title = strings.Replace(title, "_", " ", -1)
|
||||
title = strings.Title(strings.ToLower(title))
|
||||
return title
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Home returns the home directory for the executing user.
|
||||
//
|
||||
// This uses an OS-specific method for discovering the home directory.
|
||||
// An error is returned if a home directory cannot be detected.
|
||||
func Home() (string, error) {
|
||||
user, err := user.Current()
|
||||
if nil == err {
|
||||
return user.HomeDir, nil
|
||||
}
|
||||
|
||||
// cross compile support
|
||||
if "windows" == runtime.GOOS {
|
||||
return homeWindows()
|
||||
}
|
||||
|
||||
// Unix-like system, so just assume Unix
|
||||
return homeUnix()
|
||||
}
|
||||
|
||||
func homeUnix() (string, error) {
|
||||
// First prefer the HOME environmental variable
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home, nil
|
||||
}
|
||||
|
||||
// If that fails, try getent
|
||||
var stdout bytes.Buffer
|
||||
cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid()))
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
// If the error is ErrNotFound, we ignore it. Otherwise, return it.
|
||||
if err != exec.ErrNotFound {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
if passwd := strings.TrimSpace(stdout.String()); passwd != "" {
|
||||
// username:password:uid:gid:gecos:home:shell
|
||||
passwdParts := strings.SplitN(passwd, ":", 7)
|
||||
if len(passwdParts) > 5 {
|
||||
return passwdParts[5], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If all else fails, try the shell
|
||||
stdout.Reset()
|
||||
cmd = exec.Command("sh", "-c", "cd && pwd")
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result := strings.TrimSpace(stdout.String())
|
||||
if result == "" {
|
||||
return "", errors.New("blank output when reading home directory")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func homeWindows() (string, error) {
|
||||
// First prefer the HOME environmental variable
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home, nil
|
||||
}
|
||||
|
||||
drive := os.Getenv("HOMEDRIVE")
|
||||
path := os.Getenv("HOMEPATH")
|
||||
home := drive + path
|
||||
if drive == "" || path == "" {
|
||||
home = os.Getenv("USERPROFILE")
|
||||
}
|
||||
if home == "" {
|
||||
return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank")
|
||||
}
|
||||
|
||||
return home, nil
|
||||
}
|
41
modules/utils/parse.go
Normal file
41
modules/utils/parse.go
Normal file
@ -0,0 +1,41 @@
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ArgToIndex take issue/pull index as string and return int64
|
||||
func ArgToIndex(arg string) (int64, error) {
|
||||
if strings.HasPrefix(arg, "#") {
|
||||
arg = arg[1:]
|
||||
}
|
||||
return strconv.ParseInt(arg, 10, 64)
|
||||
}
|
||||
|
||||
// NormalizeURL normalizes the input with a protocol
|
||||
func NormalizeURL(raw string) (*url.URL, error) {
|
||||
var prefix string
|
||||
if !strings.HasPrefix(raw, "http") {
|
||||
prefix = "https://"
|
||||
}
|
||||
return url.Parse(strings.TrimSuffix(prefix+raw, "/"))
|
||||
}
|
||||
|
||||
// GetOwnerAndRepo return repoOwner and repoName
|
||||
// based on relative path and default owner (if not in path)
|
||||
func GetOwnerAndRepo(repoPath, user string) (string, string) {
|
||||
if len(repoPath) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
p := strings.Split(repoPath, "/")
|
||||
if len(p) >= 2 {
|
||||
return p[0], p[1]
|
||||
}
|
||||
return user, repoPath
|
||||
}
|
55
modules/utils/path.go
Normal file
55
modules/utils/path.go
Normal file
@ -0,0 +1,55 @@
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PathExists returns whether the given file or directory exists or not
|
||||
func PathExists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
// FileExist returns whether the given file exists or not
|
||||
func FileExist(fileName string) (bool, error) {
|
||||
f, err := os.Stat(fileName)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if f.IsDir() {
|
||||
return false, errors.New("A directory with the same name exists")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// AbsPathWithExpansion expand path beginning with "~/" to absolute path
|
||||
func AbsPathWithExpansion(p string) (string, error) {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if p == "~" {
|
||||
return u.HomeDir, nil
|
||||
} else if strings.HasPrefix(p, "~/") {
|
||||
return filepath.Join(u.HomeDir, p[2:]), nil
|
||||
} else {
|
||||
return filepath.Abs(p)
|
||||
}
|
||||
}
|
30
vendor/code.gitea.io/gitea-vet/.changelog.yml
generated
vendored
Normal file
30
vendor/code.gitea.io/gitea-vet/.changelog.yml
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# The full repository name
|
||||
repo: gitea/gitea-vet
|
||||
|
||||
# Service type (gitea or github)
|
||||
service: gitea
|
||||
|
||||
# Base URL for Gitea instance if using gitea service type (optional)
|
||||
base-url: https://gitea.com
|
||||
|
||||
# Changelog groups and which labeled PRs to add to each group
|
||||
groups:
|
||||
-
|
||||
name: BREAKING
|
||||
labels:
|
||||
- breaking
|
||||
-
|
||||
name: FEATURES
|
||||
labels:
|
||||
- feature
|
||||
-
|
||||
name: BUGFIXES
|
||||
labels:
|
||||
- bug
|
||||
-
|
||||
name: ENHANCEMENTS
|
||||
labels:
|
||||
- enhancement
|
||||
|
||||
# regex indicating which labels to skip for the changelog
|
||||
skip-labels: skip-changelog|backport\/.+
|
45
vendor/code.gitea.io/gitea-vet/.drone.yml
generated
vendored
Normal file
45
vendor/code.gitea.io/gitea-vet/.drone.yml
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: compliance
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- pull_request
|
||||
|
||||
steps:
|
||||
- name: check
|
||||
pull: always
|
||||
image: golang:1.14
|
||||
environment:
|
||||
GOPROXY: https://goproxy.cn
|
||||
commands:
|
||||
- make build
|
||||
- make lint
|
||||
- make vet
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: build-master
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- push
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
pull: always
|
||||
image: techknowlogick/xgo:latest
|
||||
environment:
|
||||
GOPROXY: https://goproxy.cn
|
||||
commands:
|
||||
- make build
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user