зеркало из https://github.com/go-gitea/tea.git
init project
This commit is contained in:
Коммит
9d5cda4bfe
|
@ -0,0 +1,271 @@
|
||||||
|
# Contribution Guidelines
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Contribution Guidelines](#contribution-guidelines)
|
||||||
|
- [Introduction](#introduction)
|
||||||
|
- [Bug reports](#bug-reports)
|
||||||
|
- [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)
|
||||||
|
- [Release Cycle](#release-cycle)
|
||||||
|
- [Maintainers](#maintainers)
|
||||||
|
- [Owners](#owners)
|
||||||
|
- [Versions](#versions)
|
||||||
|
- [Copyright](#copyright)
|
||||||
|
|
||||||
|
## 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/).
|
||||||
|
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/)
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
To show us that the issue you are having is in Gitea itself, please
|
||||||
|
write clear, concise instructions so we can reproduce the behavior—
|
||||||
|
even if it seems obvious. The more detailed and specific you are,
|
||||||
|
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
|
||||||
|
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)!
|
||||||
|
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
|
||||||
|
of the issue that it is a proposal.
|
||||||
|
|
||||||
|
This process gives everyone a chance to validate the design, helps
|
||||||
|
prevent duplication of effort, and ensures that the idea fits inside
|
||||||
|
the goals for the project and tools. It also checks that the design is
|
||||||
|
sound before code is written; the code review tool is not the place for
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Vendoring
|
||||||
|
|
||||||
|
We keep a cached copy of dependencies within the `vendor/` directory,
|
||||||
|
managing updates via [dep](https://github.com/golang/dep).
|
||||||
|
|
||||||
|
Pull requests should only include `vendor/` updates if they are part of
|
||||||
|
the same change, be it a bugfix or a feature addition.
|
||||||
|
|
||||||
|
The `vendor/` update needs to be justified as part of the PR description,
|
||||||
|
and must be verified by the reviewers and/or merger to always reference
|
||||||
|
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.
|
||||||
|
|
||||||
|
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;
|
||||||
|
it has lots of useful tips for any project you may want to contribute.
|
||||||
|
Some of the key points:
|
||||||
|
|
||||||
|
* Make small pull requests. The smaller, the faster to review and the
|
||||||
|
more likely it will be merged soon.
|
||||||
|
* Don't make changes unrelated to your PR. Maybe there are typos on
|
||||||
|
some comments, maybe refactoring would be welcome on a function... but
|
||||||
|
if that is not related to your PR, please make *another* PR for that.
|
||||||
|
* Split big pull requests into multiple small ones. An incremental change
|
||||||
|
will be faster to review than a huge PR.
|
||||||
|
|
||||||
|
## Styleguide
|
||||||
|
|
||||||
|
For imports you should use the following format (_without_ the comments)
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
// stdlib
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
// local packages
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
|
// external packages
|
||||||
|
"github.com/foo/bar"
|
||||||
|
"gopkg.io/baz.v1"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sign-off your work
|
||||||
|
|
||||||
|
The sign-off is a simple line at the end of the explanation for the
|
||||||
|
patch. Your signature certifies that you wrote the patch or otherwise
|
||||||
|
have the right to pass it on as an open-source patch. The rules are
|
||||||
|
pretty simple: If you can certify [DCO](DCO), then you just add a line
|
||||||
|
to every git commit message:
|
||||||
|
|
||||||
|
```
|
||||||
|
Signed-off-by: Joe Smith <joe.smith@email.com>
|
||||||
|
```
|
||||||
|
|
||||||
|
Please use your real name; we really dislike pseudonyms or anonymous
|
||||||
|
contributions. We are in the open-source world without secrets. If you
|
||||||
|
set your `user.name` and `user.email` git configs, you can sign-off your
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
To make sure every PR is checked, we have [team
|
||||||
|
maintainers](MAINTAINERS). Every PR **MUST** be reviewed by at least
|
||||||
|
two maintainers (or owners) before it can get merged. A maintainer
|
||||||
|
should be a contributor of Gitea (or Gogs) and contributed at least
|
||||||
|
4 accepted PRs. A contributor should apply as a maintainer in the
|
||||||
|
[Discord](https://discord.gg/NsatcWJ) #develop channel. The owners
|
||||||
|
or the team maintainers may invite the contributor. A maintainer
|
||||||
|
should spend some time on code reviews. If a maintainer has no
|
||||||
|
time to do that, they should apply to leave the maintainers team
|
||||||
|
and we will give them the honor of being a member of the [advisors
|
||||||
|
team](https://github.com/orgs/go-gitea/teams/advisors). Of course, if
|
||||||
|
an advisor has time to code review, we will gladly welcome them back
|
||||||
|
to the maintainers team. If a maintainer is inactive for more than 3
|
||||||
|
months and forgets to leave the maintainers team, the owners may move
|
||||||
|
him or her from the maintainers team to the advisors team.
|
||||||
|
For security reasons, Maintainers should use 2FA for their accounts and
|
||||||
|
if possible provide gpg signed commits.
|
||||||
|
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
|
||||||
|
https://help.github.com/articles/signing-commits-with-gpg/
|
||||||
|
|
||||||
|
## Owners
|
||||||
|
|
||||||
|
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
|
||||||
|
be the main owner, and the other two the assistant owners. When the new
|
||||||
|
owners have been elected, the old owners will give up ownership to the
|
||||||
|
newly elected owners. If an owner is unable to do so, the other owners
|
||||||
|
will assist in ceding ownership to the newly elected owners.
|
||||||
|
For security reasons, Owners or any account with write access (like a bot)
|
||||||
|
must use 2FA.
|
||||||
|
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
|
||||||
|
|
||||||
|
After the election, the new owners should proactively agree
|
||||||
|
with our [CONTRIBUTING](CONTRIBUTING.md) requirements in the
|
||||||
|
[Discord](https://discord.gg/NsatcWJ) #general channel. Below are the
|
||||||
|
words to speak:
|
||||||
|
|
||||||
|
```
|
||||||
|
I'm honored to having been elected an owner of Gitea, I agree with
|
||||||
|
[CONTRIBUTING](CONTRIBUTING.md). I will spend part of my time on Gitea
|
||||||
|
and lead the development of Gitea.
|
||||||
|
```
|
||||||
|
|
||||||
|
To honor the past owners, here's the history of the owners and the time
|
||||||
|
they served:
|
||||||
|
|
||||||
|
* 2016-11-04 ~ 2017-12-31
|
||||||
|
* [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
|
||||||
|
* [Thomas Boerger](https://github.com/tboerger) <thomas@webhippie.de>
|
||||||
|
* [Kim Carlbäcker](https://github.com/bkcsoft) <kim.carlbacker@gmail.com>
|
||||||
|
|
||||||
|
* 2018-01-01 ~ 2018-12-31
|
||||||
|
* [Lunny Xiao](https://github.com/lunny) <xiaolunwen@gmail.com>
|
||||||
|
* [Lauris Bukšis-Haberkorns](https://github.com/lafriks) <lauris@nix.lv>
|
||||||
|
* [Kim Carlbäcker](https://github.com/bkcsoft) <kim.carlbacker@gmail.com>
|
||||||
|
|
||||||
|
## Versions
|
||||||
|
|
||||||
|
Gitea has the `master` branch as a tip branch and has version branches
|
||||||
|
such as `release/v0.9`. `release/v0.9` is a release branch and we will
|
||||||
|
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,
|
||||||
|
after bringing the bug fix also to the master branch.
|
||||||
|
|
||||||
|
Since the `master` branch is a tip version, if you wish to use Gitea
|
||||||
|
in production, please download the latest release tag version. All the
|
||||||
|
branches will be protected via GitHub, all the PRs to every branch must
|
||||||
|
be reviewed by two maintainers and must pass the automatic tests.
|
||||||
|
|
||||||
|
## Copyright
|
||||||
|
|
||||||
|
Code that you contribute should use the standard copyright header:
|
||||||
|
|
||||||
|
```
|
||||||
|
// 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.
|
||||||
|
```
|
||||||
|
|
||||||
|
Files in the repository contain copyright from the year they are added
|
||||||
|
to the year they are last changed. If the copyright author is changed,
|
||||||
|
just paste the header below the old one.
|
|
@ -0,0 +1,36 @@
|
||||||
|
Developer Certificate of Origin
|
||||||
|
Version 1.1
|
||||||
|
|
||||||
|
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||||
|
660 York Street, Suite 102,
|
||||||
|
San Francisco, CA 94110 USA
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this
|
||||||
|
license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
Developer's Certificate of Origin 1.1
|
||||||
|
|
||||||
|
By making a contribution to this project, I certify that:
|
||||||
|
|
||||||
|
(a) The contribution was created in whole or in part by me and I
|
||||||
|
have the right to submit it under the open source license
|
||||||
|
indicated in the file; or
|
||||||
|
|
||||||
|
(b) The contribution is based upon previous work that, to the best
|
||||||
|
of my knowledge, is covered under an appropriate open source
|
||||||
|
license and I have the right under that license to submit that
|
||||||
|
work with modifications, whether created in whole or in part
|
||||||
|
by me, under the same open source license (unless I am
|
||||||
|
permitted to submit under a different license), as indicated
|
||||||
|
in the file; or
|
||||||
|
|
||||||
|
(c) The contribution was provided directly to me by some other
|
||||||
|
person who certified (a), (b) or (c) and I have not modified
|
||||||
|
it.
|
||||||
|
|
||||||
|
(d) I understand and agree that this project and the contribution
|
||||||
|
are public and that a record of the contribution (including all
|
||||||
|
personal information I submit with it, including my sign-off) is
|
||||||
|
maintained indefinitely and may be redistributed consistent with
|
||||||
|
this project or the open source license(s) involved.
|
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright (c) 2016 The Gitea Authors
|
||||||
|
Copyright (c) 2015 The Gogs Authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Gitea Command Line Tool for Go
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/go-gitea/tea
|
||||||
|
go install github.com/go-gitea/tea
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
First of all, you have to create a token on your personal settings -> application.
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone git@try.gitea.io:gitea/gitea.git
|
||||||
|
cd gitea
|
||||||
|
tea login add --name=try --url=https://try.gitea.io --token=xxxxxx
|
||||||
|
tea issues
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Fork -> Patch -> Push -> Pull Request
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
|
||||||
|
* [Maintainers](https://github.com/orgs/go-gitea/people)
|
||||||
|
* [Contributors](https://github.com/go-gitea/tea/graphs/contributors)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is under the MIT License. See the [LICENSE](LICENSE) file for the
|
||||||
|
full license text.
|
|
@ -0,0 +1,212 @@
|
||||||
|
// 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/git"
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
local_git "code.gitea.io/tea/modules/git"
|
||||||
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"github.com/go-gitea/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) GetSSHHost() string {
|
||||||
|
if l.SSHHost != "" {
|
||||||
|
return l.SSHHost
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(l.URL)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
cmd := git.NewCommand("remote", "get-url", "origin")
|
||||||
|
u, err := cmd.RunInDir(filepath.Dir(os.Args[0]))
|
||||||
|
if err != nil || len(u) == 0 {
|
||||||
|
return nil, "", errors.New("You have to indicated a repo or execute the command in a repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := local_git.ParseURL(strings.TrimSpace(u))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("Git remote URL parse failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range config.Logins {
|
||||||
|
if p.Scheme == "http" || 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 p.Scheme == "ssh" {
|
||||||
|
if l.GetSSHHost() == p.Host {
|
||||||
|
return &l, strings.TrimLeft(strings.TrimSuffix(p.Path, ".git"), "/"), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, "", errors.New("No Gitea login found")
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdIssues represents to login a gitea server.
|
||||||
|
var CmdIssues = cli.Command{
|
||||||
|
Name: "issues",
|
||||||
|
Usage: "Log in a Gitea server",
|
||||||
|
Description: `Log in a Gitea server`,
|
||||||
|
Action: runIssues,
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
CmdIssuesList,
|
||||||
|
CmdIssuesCreate,
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "login, l",
|
||||||
|
Usage: "Indicate one login",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "repo, r",
|
||||||
|
Usage: "Indicate one repository",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var CmdIssuesList = cli.Command{
|
||||||
|
Name: "ls",
|
||||||
|
Usage: "Log in a Gitea server",
|
||||||
|
Description: `Log in a Gitea server`,
|
||||||
|
Action: runIssuesList,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runIssues(ctx *cli.Context) error {
|
||||||
|
if len(os.Args) == 3 {
|
||||||
|
return runIssueDetail(ctx, os.Args[2])
|
||||||
|
}
|
||||||
|
return runIssuesList(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runIssueDetail(ctx *cli.Context, index string) error {
|
||||||
|
login, owner, repo := initCommand(ctx)
|
||||||
|
|
||||||
|
if strings.HasPrefix(index, "#") {
|
||||||
|
index = index[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, err := strconv.ParseInt(index, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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(ctx)
|
||||||
|
|
||||||
|
issues, err := login.Client().ListRepoIssues(owner, repo, gitea.ListIssueOption{
|
||||||
|
Page: 0,
|
||||||
|
State: string(gitea.StateOpen),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(issues) == 0 {
|
||||||
|
fmt.Println("No issues left")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, issue := range issues {
|
||||||
|
name := issue.Poster.FullName
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = issue.Poster.UserName
|
||||||
|
}
|
||||||
|
fmt.Printf("#%d\t%s\t%s\t%s\n", issue.Index, name, issue.Updated.Format("2006-01-02 15:04:05"), issue.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var CmdIssuesCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create an issue on repository",
|
||||||
|
Description: `Create an issue on repository`,
|
||||||
|
Action: runIssuesCreate,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "title, t",
|
||||||
|
Usage: "issue title to create",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "body, b",
|
||||||
|
Usage: "issue body to create",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func initCommand(ctx *cli.Context) (*Login, string, string) {
|
||||||
|
err := loadConfig(yamlConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("load config file failed", yamlConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var login *Login
|
||||||
|
if ctx.IsSet("login") {
|
||||||
|
login = getLoginByName(ctx.String("login"))
|
||||||
|
if login == nil {
|
||||||
|
log.Fatal("indicated login name", ctx.String("login"), "is not exist")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
login, err = getActiveLogin()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("get active login failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoPath string
|
||||||
|
if !ctx.IsSet("repo") {
|
||||||
|
login, repoPath, err = curGitRepoPath()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repoPath = ctx.String("repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, repo := splitRepo(repoPath)
|
||||||
|
return login, owner, repo
|
||||||
|
}
|
||||||
|
|
||||||
|
func runIssuesCreate(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand(ctx)
|
||||||
|
|
||||||
|
_, err := login.Client().CreateIssue(owner, repo, gitea.CreateIssueOption{
|
||||||
|
Title: ctx.String("title"),
|
||||||
|
Body: ctx.String("body"),
|
||||||
|
// TODO:
|
||||||
|
//Assignee string `json:"assignee"`
|
||||||
|
//Assignees []string `json:"assignees"`
|
||||||
|
//Deadline *time.Time `json:"due_date"`
|
||||||
|
//Milestone int64 `json:"milestone"`
|
||||||
|
//Labels []int64 `json:"labels"`
|
||||||
|
//Closed bool `json:"closed"`
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
showLog bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// Println println content according the flag
|
||||||
|
func Println(a ...interface{}) {
|
||||||
|
if showLog {
|
||||||
|
fmt.Println(a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf printf content according the flag
|
||||||
|
func Printf(format string, a ...interface{}) {
|
||||||
|
if showLog {
|
||||||
|
fmt.Printf(format, a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error println content as an error information
|
||||||
|
func Error(a ...interface{}) {
|
||||||
|
fmt.Println(a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf printf content as an error information
|
||||||
|
func Errorf(format string, a ...interface{}) {
|
||||||
|
fmt.Printf(format, a...)
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
// 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"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdLogin represents to login a gitea server.
|
||||||
|
var cmdLoginAdd = cli.Command{
|
||||||
|
Name: "add",
|
||||||
|
Usage: "Log in a Gitea server",
|
||||||
|
Description: `Log in 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 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: "Log in a Gitea server",
|
||||||
|
Description: `Log in a Gitea server`,
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
// 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 (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdLogout represents to logout a gitea server.
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdPulls represents to login a gitea server.
|
||||||
|
var CmdPulls = cli.Command{
|
||||||
|
Name: "pulls",
|
||||||
|
Usage: "Log in a Gitea server",
|
||||||
|
Description: `Log in a Gitea server`,
|
||||||
|
Action: runPulls,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "login, l",
|
||||||
|
Usage: "Indicate one login",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "repo, r",
|
||||||
|
Usage: "Indicate one repository",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPulls(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand(ctx)
|
||||||
|
|
||||||
|
prs, err := login.Client().ListRepoPullRequests(owner, repo, gitea.ListPullRequestsOptions{
|
||||||
|
Page: 0,
|
||||||
|
State: string(gitea.StateOpen),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(prs) == 0 {
|
||||||
|
fmt.Println("No pull requests left")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pr := range prs {
|
||||||
|
name := pr.Poster.FullName
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = pr.Poster.UserName
|
||||||
|
}
|
||||||
|
fmt.Printf("#%d\t%s\t%s\t%s\n", pr.Index, name, pr.Updated.Format("2006-01-02 15:04:05"), pr.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdReleases represents to login a gitea server.
|
||||||
|
var CmdReleases = cli.Command{
|
||||||
|
Name: "releases",
|
||||||
|
Usage: "Log in a Gitea server",
|
||||||
|
Description: `Log in a Gitea server`,
|
||||||
|
Action: runReleases,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "login, l",
|
||||||
|
Usage: "Indicate one login",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "repo, r",
|
||||||
|
Usage: "Indicate one repository",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runReleases(ctx *cli.Context) error {
|
||||||
|
login, owner, repo := initCommand(ctx)
|
||||||
|
|
||||||
|
releases, err := login.Client().ListReleases(owner, repo)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(releases) == 0 {
|
||||||
|
fmt.Println("No Releases")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, release := range releases {
|
||||||
|
fmt.Printf("#%s\t%s\t%s\t%s\n", release.TagName,
|
||||||
|
release.Title,
|
||||||
|
release.PublishedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
release.TarURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Tea is command line tool for Gitea.
|
||||||
|
package main // import "code.gitea.io/tea"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/tea/cmd"
|
||||||
|
"code.gitea.io/tea/modules/setting"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version holds the current Gitea version
|
||||||
|
var Version = "0.1.0-dev"
|
||||||
|
|
||||||
|
// 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.Usage = "Command line tool to interactive with Gitea"
|
||||||
|
app.Description = ``
|
||||||
|
app.Version = Version + formatBuiltWith(Tags)
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
cmd.CmdLogin,
|
||||||
|
cmd.CmdLogout,
|
||||||
|
cmd.CmdIssues,
|
||||||
|
cmd.CmdPulls,
|
||||||
|
cmd.CmdReleases,
|
||||||
|
}
|
||||||
|
err := app.Run(os.Args)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(4, "Failed to run app with %s: %v", os.Args, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatBuiltWith(Tags string) string {
|
||||||
|
if len(Tags) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return " built with: " + strings.Replace(Tags, " ", ", ", -1)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
protocolRe = regexp.MustCompile("^[a-zA-Z_+-]+://")
|
||||||
|
)
|
||||||
|
|
||||||
|
type URLParser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *URLParser) Parse(rawURL string) (u *url.URL, err error) {
|
||||||
|
if !protocolRe.MatchString(rawURL) &&
|
||||||
|
strings.Contains(rawURL, ":") &&
|
||||||
|
// not a Windows path
|
||||||
|
!strings.Contains(rawURL, "\\") {
|
||||||
|
rawURL = "ssh://" + strings.Replace(rawURL, ":", "/", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err = url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme == "git+ssh" {
|
||||||
|
u.Scheme = "ssh"
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(u.Path, "//") {
|
||||||
|
u.Path = strings.TrimPrefix(u.Path, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseURL(rawURL string) (u *url.URL, err error) {
|
||||||
|
p := &URLParser{}
|
||||||
|
return p.Parse(rawURL)
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
var (
|
||||||
|
AppVer string
|
||||||
|
AppBuiltWith string
|
||||||
|
)
|
|
@ -0,0 +1,95 @@
|
||||||
|
// 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
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче