mirror of
				https://gitea.com/gitea/tea.git
				synced 2025-10-31 01:05:26 +01:00 
			
		
		
		
	init project
This commit is contained in:
		
							
								
								
									
										271
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										36
									
								
								DCO
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								DCO
									
									
									
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										20
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										36
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -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. | ||||
							
								
								
									
										212
									
								
								cmd/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								cmd/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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") | ||||
| } | ||||
							
								
								
									
										179
									
								
								cmd/issues.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								cmd/issues.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										35
									
								
								cmd/log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								cmd/log.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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...) | ||||
| } | ||||
							
								
								
									
										135
									
								
								cmd/login.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								cmd/login.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										60
									
								
								cmd/logout.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								cmd/logout.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										60
									
								
								cmd/pulls.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								cmd/pulls.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										53
									
								
								cmd/releases.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								cmd/releases.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										55
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										43
									
								
								modules/git/url.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								modules/git/url.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										10
									
								
								modules/setting/setting.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								modules/setting/setting.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| ) | ||||
							
								
								
									
										95
									
								
								modules/utils/home.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								modules/utils/home.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Lunny Xiao
					Lunny Xiao