294 Commits

Author SHA1 Message Date
Matěj Cepl
63bc90ea52 feat(branches): add rename subcommand (#939)
Implements the 'branches rename' command to rename a branch in a repository.
This wraps the Gitea API endpoint PATCH /repos/{owner}/{repo}/branches/{branch}.

Usage: tea branches rename <old_branch_name> <new_branch_name>

Example: tea branches rename -r owner/repo main factory

This resolves issue #938.

Reviewed-on: https://gitea.com/gitea/tea/pulls/939
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Matěj Cepl <mcepl@cepl.eu>
Co-committed-by: Matěj Cepl <mcepl@cepl.eu>
2026-04-15 17:27:47 +00:00
Bo-Yi Wu
9e0a6203ae feat(pulls): add ci status field to pull request list (#956)
## Summary

- Add `"ci"` as a new selectable field for `tea pr list --fields`, allowing users to see CI status across multiple PRs at a glance
- Fetch CI status via `GetCombinedStatus` API **only when the `ci` field is explicitly requested** via `--fields`, avoiding unnecessary API calls in default usage
- Improve CI status display in both detail and list views:
  - **Detail view** (`tea pr <index>`): show each CI check with symbol, context name, description, and clickable link to CI run
  - **List view** (`tea pr list --fields ci`): show symbol + context name per CI check (e.g., `✓ lint,  build,  test`)
  - **Machine-readable output**: return raw state string (e.g., `success`, `pending`)
- Replace pending CI symbol from `⭮` to `` for better readability
- Extract `formatCIStatus` helper and reuse it in `PullDetails` to reduce code duplication
- Add comprehensive tests for CI status formatting and PR list integration

## Detail View Example

```
- CI:
  - ✓ [**lint**](https://ci.example.com/lint): Lint passed
  -  [**build**](https://ci.example.com/build): Build is running
  -  [**test**](https://ci.example.com/test): 3 tests failed
```

## List View Example

```
INDEX  TITLE       STATE  CI
123    Fix bug     open   ✓ lint,  build,  test
```

## Usage

```bash
# Show CI status column in list
tea pr list --fields index,title,state,ci

# Default output is unchanged (no CI column, no extra API calls)
tea pr list
```

## Files Changed

- `cmd/pulls/list.go` — conditionally fetch CI status per PR when `ci` field is selected
- `modules/print/pull.go` — add `ci` field, `formatCIStatus` helper, improve detail/list CI display
- `modules/print/pull_test.go` — comprehensive tests for CI status formatting

## Test plan

- [x] `go build ./...` passes
- [x] `go test ./...` passes (11 new tests)
- [x] `tea pr list` — default output unchanged, no extra API calls
- [x] `tea pr list --fields index,title,state,ci` — CI column with context names
- [x] `tea pr <index>` — CI section shows each check with name, description, and link
- [x] `tea pr list --fields ci -o csv` — machine-readable output shows raw state strings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: https://gitea.com/gitea/tea/pulls/956
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2026-04-10 17:29:15 +00:00
Bo-Yi Wu
84ecd16f9c fix(deps): update Go dependencies to latest versions (#955)
## Summary
- Upgrade all Go module dependencies to their latest versions
- Includes updates to charm.land, golang.org/x, goldmark, go-crypto, and other indirect dependencies
- Project builds cleanly with all updates

## Test plan
- [x] `go build ./...` passes
- [x] CI pipeline passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: https://gitea.com/gitea/tea/pulls/955
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2026-04-10 01:40:40 +00:00
Bo-Yi Wu
53e53e1067 feat(workflows): add dispatch, view, enable and disable subcommands (#952)
## Summary

- Add `tea actions workflows dispatch` to trigger `workflow_dispatch` events with `--ref`, `--input key=value`, and `--follow` for log tailing
- Add `tea actions workflows view` to show workflow details
- Add `tea actions workflows enable` and `disable` to toggle workflow state
- Rewrite `workflows list` to use the Workflow API instead of file listing
- Remove dead `WorkflowsList` print function that used `ContentsResponse`
- Update `CLI.md` and `example-workflows.md` with usage documentation and examples

## Motivation

Enable re-triggering specific workflows from the CLI, which is essential for AI-driven PR flows where a specific workflow needs to be re-run after pushing changes.

Leverages the 5 workflow API endpoints already supported by the Go SDK (v0.24.1) from go-gitea/gitea#33545:
- `ListRepoActionWorkflows`
- `GetRepoActionWorkflow`
- `DispatchRepoActionWorkflow` (with `returnRunDetails` support)
- `EnableRepoActionWorkflow`
- `DisableRepoActionWorkflow`

## New commands

\`\`\`
tea actions workflows
├── list          (rewritten to use Workflow API)
├── view <id>     (new)
├── dispatch <id> (new)
├── enable <id>   (new)
└── disable <id>  (new)
\`\`\`

### Usage examples

\`\`\`bash
# Dispatch workflow on current branch
tea actions workflows dispatch deploy.yml

# Dispatch with specific ref and inputs
tea actions workflows dispatch deploy.yml --ref main --input env=staging --input version=1.2.3

# Dispatch and follow logs
tea actions workflows dispatch ci.yml --ref feature/my-pr --follow

# View workflow details
tea actions workflows view deploy.yml

# Enable/disable workflows
tea actions workflows enable deploy.yml
tea actions workflows disable deploy.yml --confirm
\`\`\`

## Test plan

- [x] `go build ./...` passes
- [x] `go test ./...` passes
- [x] `go vet ./...` passes
- [x] `make lint` — 0 issues
- [x] `make docs-check` — CLI.md is up to date
- [x] Manual test: `tea actions workflows list` shows workflows from API
- [x] Manual test: `tea actions workflows dispatch <workflow> --ref main` triggers a run
- [x] Manual test: `tea actions workflows view <workflow>` shows details

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/952
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2026-04-09 20:03:33 +00:00
Renovate Bot
0489d8c275 fix(deps): update module golang.org/x/sys to v0.43.0 (#951)
Reviewed-on: https://gitea.com/gitea/tea/pulls/951
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-04-09 14:16:37 +00:00
Nicolas
f538c05282 refactor: code cleanup across codebase (#947)
## Summary

- Extract duplicate \`getReleaseByTag\` into shared \`cmd/releases/utils.go\`
- Replace \`log.Fatal\` calls with proper error returns in config and login commands; \`GetLoginByToken\`/\`GetLoginsByHost\`/\`GetLoginByHost\` now return errors
- Remove dead \`portChan\` channel in \`modules/auth/oauth.go\`
- Fix YAML integer detection to use \`strconv.ParseInt\` (correctly handles negatives and large ints)
- Fix \`path.go\` error handling to use \`errors.As\` + \`syscall.ENOTDIR\` instead of string comparison
- Extract repeated credential helper key into local variable in \`SetupHelper\`
- Use existing \`isRemoteDeleted()\` in \`pull_clean.go\` instead of duplicating the logic
- Fix ~30 error message casing violations to follow Go conventions
- Use \`fmt.Errorf\` consistently instead of string concatenation in \`generic.go\`

Reviewed-on: https://gitea.com/gitea/tea/pulls/947
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Bo-Yi Wu (吳柏毅) <appleboy.tw@gmail.com>
Co-authored-by: Nicolas <bircni@icloud.com>
Co-committed-by: Nicolas <bircni@icloud.com>
2026-04-08 03:38:49 +00:00
Bo-Yi Wu
662e339bf9 feat(pulls): add resolve, unresolve and review-comments subcommands (#948)
## Summary

- Add `tea pulls review-comments <pull-index>` subcommand to list PR review comments with configurable fields (supports table/json/csv/yaml output)
- Add `tea pulls resolve <comment-id>` subcommand to mark a review comment as resolved
- Add `tea pulls unresolve <comment-id>` subcommand to unmark a review comment as resolved
- Follow existing approve/reject pattern with shared `runResolveComment` helper in `review_helpers.go`

## Usage

```bash
# List review comments for PR #42
tea pulls review-comments 42

# Resolve comment #789
tea pulls resolve 789

# Unresolve comment #789
tea pulls unresolve 789

# Custom output fields
tea pulls review-comments 42 --fields id,path,body,resolver --output json
```

## New Files

| File | Description |
|------|-------------|
| `cmd/pulls/review_comments.go` | `review-comments` subcommand |
| `cmd/pulls/resolve.go` | `resolve` subcommand |
| `cmd/pulls/unresolve.go` | `unresolve` subcommand |
| `modules/task/pull_review_comment.go` | Task layer: list, resolve, unresolve via SDK |
| `modules/print/pull_review_comment.go` | Print formatting with `printable` interface |

## Modified Files

| File | Description |
|------|-------------|
| `cmd/pulls.go` | Register 3 new commands |
| `cmd/pulls/review_helpers.go` | Add shared `runResolveComment` helper |

## Test Plan

- [x] `go build ./...` passes
- [x] `go vet ./...` passes
- [x] `tea pulls review-comments <PR-index>` lists comments with IDs
- [x] `tea pulls resolve <comment-id>` resolves successfully
- [x] `tea pulls unresolve <comment-id>` unresolves successfully
- [x] `--output json` produces valid JSON output

Reviewed-on: https://gitea.com/gitea/tea/pulls/948
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2026-04-08 03:36:09 +00:00
appleboy
5bb73667d1 docs: add v0.13.0 release notes to CHANGELOG (#945)
Add v0.13.0 release notes to CHANGELOG.md covering 21 commits since v0.12.0: 5 new features, 2 enhancements, and dependency updates.

Reviewed-on: https://gitea.com/gitea/tea/pulls/945
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2026-04-05 16:42:27 +00:00
appleboy
f329f6fab2 feat(pulls): add edit subcommand for pull requests (#944)
## Summary

- Add `tea pr edit` subcommand to support editing pull request properties (description, title, milestone, deadline, assignees, labels, reviewers)
- Add `--add-reviewers` / `--remove-reviewers` flags for managing PR reviewers via `CreateReviewRequests` / `DeleteReviewRequests` API
- Extract shared helpers (`ResolveLabelOpts`, `ApplyLabelChanges`, `ApplyReviewerChanges`, `ResolveMilestoneID`) into `modules/task/labels.go` to reduce duplication between issue and PR editing
- Refactor existing `EditIssue` to use the same shared helpers
- Wrap original error in `ResolveMilestoneID` to preserve underlying error context

## Usage

```bash
# Edit PR description
tea pr edit 1 --description "new description"

# Edit PR title
tea pr edit 1 --title "new title"

# Edit multiple fields
tea pr edit 1 --title "new title" --description "new desc" --add-labels "bug"

# Edit multiple PRs
tea pr edit 1 2 3 --add-assignees "user1"

# Add reviewers
tea pr edit 1 --add-reviewers "user1,user2"

# Remove reviewers
tea pr edit 1 --remove-reviewers "user1"
```

## Test plan

- [x] `go build .` succeeds
- [x] `go test ./...` passes
- [x] `make clean && make vet && make lint && make fmt-check && make docs-check && make build` all pass
- [x] `tea pr edit <idx> --description "test"` updates PR description on a Gitea instance
- [x] `tea pr edit <idx> --title "test"` updates PR title
- [x] `tea pr edit <idx> --add-labels "bug"` adds label
- [x] `tea pr edit <idx> --add-reviewers "user"` requests review
- [x] `tea pr edit <idx> --remove-reviewers "user"` removes reviewer
- [x] Existing `tea issues edit` still works correctly after refactor

Reviewed-on: https://gitea.com/gitea/tea/pulls/944
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2026-04-05 05:35:15 +00:00
Renovate Bot
366069315f fix(deps): update module github.com/go-git/go-git/v5 to v5.17.2 (#943)
Reviewed-on: https://gitea.com/gitea/tea/pulls/943
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-04-01 18:14:14 +00:00
Renovate Bot
1e13681663 fix(deps): update module github.com/go-git/go-git/v5 to v5.17.1 (#942)
Reviewed-on: https://gitea.com/gitea/tea/pulls/942
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-03-30 07:00:31 +00:00
Renovate Bot
bfbec3fc00 fix(deps): update module code.gitea.io/sdk/gitea to v0.24.1 (#936)
Reviewed-on: https://gitea.com/gitea/tea/pulls/936
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-03-27 06:04:29 +00:00
Renovate Bot
e31a167e54 fix(deps): update module github.com/go-authgate/sdk-go to v0.6.1 (#935)
Reviewed-on: https://gitea.com/gitea/tea/pulls/935
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-03-27 03:47:06 +00:00
Renovate Bot
6a7c3e4efa fix(deps): update module github.com/urfave/cli/v3 to v3.8.0 (#937)
Reviewed-on: https://gitea.com/gitea/tea/pulls/937
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-03-27 03:46:50 +00:00
techknowlogick
b05e03416b replace log.Fatal/os.Exit with error returns (#941)
* Use stdlib encoders
* Reduce some duplication
* Remove global pagination state
* Dedupe JSON detail types
* Bump golangci-lint

Reviewed-on: https://gitea.com/gitea/tea/pulls/941
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2026-03-27 03:36:44 +00:00
Renovate Bot
21881525a8 chore(deps): update docker.gitea.com/gitea docker tag to v1.25.5 (#934)
Reviewed-on: https://gitea.com/gitea/tea/pulls/934
Reviewed-by: techknowlogick <9+techknowlogick@noreply.gitea.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-03-15 23:04:40 +00:00
Renovate Bot
9a462247bd fix(deps): update module github.com/olekukonko/tablewriter to v1.1.4 (#933)
Reviewed-on: https://gitea.com/gitea/tea/pulls/933
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-03-13 00:18:26 +00:00
Renovate Bot
5f74fb37df chore(deps): update mcr.microsoft.com/devcontainers/go docker tag to v2.1 (#930)
Reviewed-on: https://gitea.com/gitea/tea/pulls/930
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-03-12 17:13:25 +00:00
Bo-Yi Wu
ec658cfc33 chore(deps): update Go dependencies and CI workflow action versions (#932)
## Summary

- Run `go get -u ./...` and `go mod tidy` to update all Go dependencies
- Update CI workflow action versions:
  - `crazy-max/ghaction-import-gpg`: v6 → v7
  - `goreleaser/goreleaser-action`: v6 → v7
  - `docker/setup-qemu-action`: v3 → v4
  - `docker/setup-buildx-action`: v3 → v4
  - `docker/login-action`: v3 → v4
  - `docker/build-push-action`: v6 → v7

## Notable Go dependency updates

- `github.com/urfave/cli/v3`: v3.6.2 → v3.7.0
- `github.com/ProtonMail/go-crypto`: v1.3.0 → v1.4.0
- `charm.land/huh/v2`: v2.0.1 → v2.0.3
- `golang.org/x/crypto`: v0.48.0 → v0.49.0
- `golang.org/x/net`: v0.49.0 → v0.52.0

Reviewed-on: https://gitea.com/gitea/tea/pulls/932
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2026-03-12 05:28:47 +00:00
Bo-Yi Wu
cb9824b451 feat(repos): support owner-based repository listing with robust lookup (#931)
- Add an owner flag to the repos list command to list repositories for a specific user or organization
- Implement owner-based repository listing by detecting whether the owner is an organization or a user and calling the appropriate API
- Improve error handling for owner lookup by checking HTTP status codes instead of relying on error string matching
- Align repository search logic with the updated owner lookup behavior using HTTP response validation

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/tea/pulls/931
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2026-03-12 04:22:28 +00:00
Bo-Yi Wu
a531faa626 feat(repos): add repo edit subcommand (#928)
## Summary
- Add `tea repo edit` subcommand to update repository properties via the Gitea API
- Support flags: `--name`, `--description`/`--desc`, `--website`, `--private`, `--template`, `--archived`, `--default-branch`
- Boolean-like flags use string type to distinguish "not set" from "false", following the pattern in `releases/edit.go`

## Test plan
- [x] `go build ./...` passes
- [x] `go vet ./...` passes
- [x] `tea repo edit --help` shows all flags correctly
- [x] Manual test: `tea repo edit --private true` on a test repo
- [x] Manual test: `tea repo edit --name new-name --description "new desc"` on a test repo

Reviewed-on: https://gitea.com/gitea/tea/pulls/928
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2026-03-12 03:06:44 +00:00
Bo-Yi Wu
302c946cb8 feat: store OAuth tokens in OS keyring via credstore (#926)
## Summary

- Introduce `github.com/go-authgate/sdk-go/credstore` to store OAuth tokens securely in the OS keyring (macOS Keychain / Linux Secret Service / Windows Credential Manager), with automatic fallback to an encrypted JSON file
- Add `AuthMethod` field to `Login` struct; new OAuth logins are marked `auth_method: oauth` and no longer write `token`/`refresh_token`/`token_expiry` to `config.yml`
- Add `GetAccessToken()` / `GetRefreshToken()` / `GetTokenExpiry()` accessors that transparently read from credstore for OAuth logins, with fallback to YAML fields for legacy logins
- Update all token reference sites across the codebase to use the new accessors
- Non-OAuth logins (token, SSH) are completely unaffected; no migration of existing tokens

## Key files

| File | Role |
|------|------|
| `modules/config/credstore.go` | **New** — credstore wrapper (Load/Save/Delete) |
| `modules/config/login.go` | Login struct, token accessors, refresh logic |
| `modules/auth/oauth.go` | OAuth flow, token creation / re-authentication |
| `modules/api/client.go`, `cmd/login/helper.go`, `cmd/login/oauth_refresh.go` | Token reference updates |
| `modules/task/pull_*.go`, `modules/task/repo_clone.go` | Git operation token reference updates |

## Test plan

- [x] `go build ./...` compiles successfully
- [x] `go test ./...` all tests pass
- [x] `tea login add --oauth` completes OAuth flow; verify config.yml has `auth_method: oauth` but no token/refresh_token/token_expiry
- [x] `tea repos ls` API calls work (token read from credstore)
- [x] `tea login delete <name>` credstore token is also removed
- [x] Existing non-OAuth logins continue to work unchanged

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: https://gitea.com/gitea/tea/pulls/926
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2026-03-12 02:49:14 +00:00
techknowlogick
0346e1cbb5 add function comment 2026-03-10 10:00:39 -04:00
techknowlogick
cd4051ed38 make vet&fmt pass 2026-03-10 09:55:10 -04:00
Michal Suchanek
c797624fcf Update to charm libraries v2 (#923)
Reviewed-on: https://gitea.com/gitea/tea/pulls/923
Reviewed-by: techknowlogick <9+techknowlogick@noreply.gitea.com>
Co-authored-by: Michal Suchanek <msuchanek@suse.de>
Co-committed-by: Michal Suchanek <msuchanek@suse.de>
2026-03-09 16:36:00 +00:00
Renovate Bot
3372c9ec59 fix(deps): update module golang.org/x/oauth2 to v0.36.0 (#919)
Reviewed-on: https://gitea.com/gitea/tea/pulls/919
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-03-09 16:19:28 +00:00
techknowlogick
1ac8492ac7 go 1.26 2026-03-09 15:57:54 +00:00
Renovate Bot
d019f0dd72 fix(deps): update module github.com/go-git/go-git/v5 to v5.17.0 (#910)
Reviewed-on: https://gitea.com/gitea/tea/pulls/910
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-26 17:59:28 +00:00
techknowlogick
c031db2413 Parse multiple values in api subcommand (#911)
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2026-02-26 17:43:46 +00:00
Nikolaos Karaolidis
e3c550ff22 fix: authentication via env variables repo argument (#809)
---------

Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/809
Co-authored-by: Nikolaos Karaolidis <nick@karaolidis.com>
Co-committed-by: Nikolaos Karaolidis <nick@karaolidis.com>
2026-02-19 19:23:44 +00:00
Lunny Xiao
fab70f83c1 Fix issue detail view ignoring --owner flag (#899)
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/899
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2026-02-19 18:57:23 +00:00
techknowlogick
0b1147bfc0 build for windows aarch64 too 2026-02-19 18:41:21 +00:00
Lunny Xiao
93d4d3cc55 Skip token uniqueness check when using SSH authentication (#898)
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-authored-by: silverwind <silverwind@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/898
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2026-02-19 15:19:45 +00:00
Alain Thiffault
bdf15a57be feat(pulls): add JSON output support for single PR view (#864)
Reviewed-on: https://gitea.com/gitea/tea/pulls/864
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Alain Thiffault <athiffau@effectivemomentum.com>
Co-committed-by: Alain Thiffault <athiffau@effectivemomentum.com>
2026-02-19 15:16:21 +00:00
Lunny Xiao
87c8c3d6e0 Fix new tty prompt (#897)
Fix #827

---------

Co-authored-by: silverwind <silverwind@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/897
Reviewed-by: silverwind <silverwind@noreply.gitea.com>
2026-02-16 03:37:44 +00:00
Michal Suchanek
dfd400f15b Fix termenv OSC RGBA handling (#907)
Fixes: #889
Reviewed-on: https://gitea.com/gitea/tea/pulls/907
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Michal Suchanek <msuchanek@suse.de>
Co-committed-by: Michal Suchanek <msuchanek@suse.de>
2026-02-12 16:16:53 +00:00
yousfi saad
2152d99f2d Add tea actions runs and workflows commands (#880)
Implements comprehensive workflow execution tracking for Gitea Actions using tea CLI

## Features

### tea actions runs list
- List workflow runs with filtering (status, branch, event, actor, time)
- Time filters: relative (24h, 7d) and absolute dates
- Status symbols: ✓ success, ✘ failure, ⭮ pending, ⊘ skipped/cancelled, ⚠ blocked
- Multiple output formats: table, json, yaml, csv, tsv

### tea actions runs view
- View run details with metadata (ID, status, workflow, branch, event, trigger info)
- Shows jobs table with status, runner, duration
- Optional --jobs flag to toggle jobs display

### tea actions runs delete
- Delete/cancel workflow runs with confirmation prompt
- Supports --confirm/-y to skip prompt

### tea actions runs logs
- View job logs for all jobs or specific job (--job <id>)
- **New: --follow/-f flag for real-time log following** (like tail -f)
- Polls API every 2 seconds, only shows new content
- Auto-detects completion and exits

### tea actions workflows list
- List workflow files (.yml and .yaml) in repository
- Searches in .gitea/workflows and .github/workflows
- Shows active (✓) or inactive (✗) status based on recent runs
- Displays workflow name, path, and file size

## Commands

`tea actions runs list --status success --since 24h`
`tea actions runs view 123`
`tea actions runs delete 123 --confirm`
`tea actions runs logs 123 --job 456 --follow`
`tea actions workflows list`

## Tests
- 19 unit tests across all commands
- Full test suite passing
- Manual testing successful

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/880
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: yousfi saad <yousfi.saad@gmail.com>
Co-committed-by: yousfi saad <yousfi.saad@gmail.com>
2026-02-11 00:40:06 +00:00
Renovate Bot
ea795775af fix(deps): update module golang.org/x/crypto to v0.48.0 (#905)
Reviewed-on: https://gitea.com/gitea/tea/pulls/905
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-10 00:43:00 +00:00
Renovate Bot
1093ef1524 fix(deps): update module github.com/go-git/go-git/v5 to v5.16.5 (#904)
Reviewed-on: https://gitea.com/gitea/tea/pulls/904
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-10 00:42:45 +00:00
Renovate Bot
873a44f897 fix(deps): update module golang.org/x/sys to v0.41.0 (#901)
Reviewed-on: https://gitea.com/gitea/tea/pulls/901
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-09 05:22:44 +00:00
Renovate Bot
47f74ea696 fix(deps): update module golang.org/x/oauth2 to v0.35.0 (#900)
Reviewed-on: https://gitea.com/gitea/tea/pulls/900
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-09 04:56:34 +00:00
Michal Suchanek
59656dfcd2 Require non-empty token in GetLoginByToken (#895)
Fixes: #893
Reviewed-on: https://gitea.com/gitea/tea/pulls/895
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Michal Suchanek <msuchanek@suse.de>
Co-committed-by: Michal Suchanek <msuchanek@suse.de>
2026-02-08 18:11:54 +00:00
Michal Suchanek
e644cc49d4 Revert "Login requires a http/https login URL and revmoe SSH as a login method. SSH will be optional (#826)" (#891)
This reverts commit 90f8624ae7.

Fixes: #890

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/891
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Michal Suchanek <msuchanek@suse.de>
Co-committed-by: Michal Suchanek <msuchanek@suse.de>
2026-02-08 00:21:47 +00:00
boozedog
3595f8f89d fixed minor typo and grammar issue (#892)
Reviewed-on: https://gitea.com/gitea/tea/pulls/892
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: boozedog <boozedog@noreply.gitea.com>
Co-committed-by: boozedog <boozedog@noreply.gitea.com>
2026-02-07 16:02:20 +00:00
techknowlogick
49a9032d8a Move versions/filelocker into dedicated subpackages, and consistent headers in http requests (#888)
- move filelocker logic into dedicated subpackage
- consistent useragent in requests

Reviewed-on: https://gitea.com/gitea/tea/pulls/888
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2026-02-05 18:05:43 +00:00
techknowlogick
982adb4d02 Update README.md 2026-02-04 19:37:39 +00:00
techknowlogick
29488a1f46 build w/ go1.25 (#886)
Reviewed-on: https://gitea.com/gitea/tea/pulls/886
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2026-02-04 19:27:25 +00:00
Renovate Bot
a47ac265d2 chore(deps): update mcr.microsoft.com/devcontainers/go docker tag to v2 (#884)
Reviewed-on: https://gitea.com/gitea/tea/pulls/884
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-04 00:56:27 +00:00
Renovate Bot
037d1aad23 fix(deps): update module github.com/charmbracelet/lipgloss to v2 (#885)
Reviewed-on: https://gitea.com/gitea/tea/pulls/885
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-04 00:56:04 +00:00
Renovate Bot
e5342660fa chore(deps): update actions/checkout action to v6 (#882)
Reviewed-on: https://gitea.com/gitea/tea/pulls/882
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-04 00:16:06 +00:00
Renovate Bot
233ffe4508 chore(deps): update actions/setup-go action to v6 (#883)
Reviewed-on: https://gitea.com/gitea/tea/pulls/883
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-04 00:15:53 +00:00
techknowlogick
ae9eb4f2c0 Add locking to ensure safe concurrent access to config file (#881)
Reviewed-on: https://gitea.com/gitea/tea/pulls/881
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2026-02-03 23:48:18 +00:00
a1012112796
0d5bf60632 support create agit flow pull request (#867)
while looks the alibaba has not maintain
[`git-repo-go`](https://github.com/alibaba/git-repo-go/)
tool, to make agit flow pull requst can be create quickly.
add creating agit flow pull request feature
in tea tool

example:

```SHELL
tea pulls create --agit --remote=origin --topic=test-topic
--title="hello world" --description="test1
test 2
test 3"
```

Signed-off-by: a1012112796 <1012112796@qq.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/867
Co-authored-by: a1012112796 <1012112796@qq.com>
Co-committed-by: a1012112796 <1012112796@qq.com>
2026-02-03 20:36:04 +00:00
techknowlogick
82d8a14c73 Add api subcommand for arbitrary api calls not covered by existing subcommands (#879)
Reviewed-on: https://gitea.com/gitea/tea/pulls/879
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2026-02-03 20:24:21 +00:00
Renovate Bot
6414a5e00e chore(deps): update docker.gitea.com/gitea docker tag to v1.25.4 (#877)
Reviewed-on: https://gitea.com/gitea/tea/pulls/877
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-03 01:06:13 +00:00
Renovate Bot
864face284 fix(deps): update module golang.org/x/oauth2 to v0.34.0 (#878)
Reviewed-on: https://gitea.com/gitea/tea/pulls/878
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-03 01:06:03 +00:00
Renovate Bot
383c5fdc03 fix(deps): update module github.com/urfave/cli/v3 to v3.6.2 (#876)
Reviewed-on: https://gitea.com/gitea/tea/pulls/876
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-03 01:00:21 +00:00
Renovate Bot
7801310a18 fix(deps): update module github.com/olekukonko/tablewriter to v1.1.3 (#875)
Reviewed-on: https://gitea.com/gitea/tea/pulls/875
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2026-02-03 01:00:03 +00:00
techknowlogick
c2180048a0 Split up Context (#873)
Reviewed-on: https://gitea.com/gitea/tea/pulls/873
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2026-02-02 23:16:39 +00:00
techknowlogick
629872d1e9 nix flake update (#872)
Reviewed-on: https://gitea.com/gitea/tea/pulls/872
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2026-02-02 23:05:45 +00:00
techknowlogick
0be14de5c2 bump devcontainer 2026-02-02 23:02:00 +00:00
techknowlogick
4f8cb7ef19 helpful error messages (#871)
Reviewed-on: https://gitea.com/gitea/tea/pulls/871
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2026-02-02 22:59:22 +00:00
techknowlogick
f638dba99b More improvements (#870)
- no duplicate logins
- link to html page rather than api in output
- client side pagination of watched repos

Reviewed-on: https://gitea.com/gitea/tea/pulls/870
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2026-02-02 22:58:25 +00:00
techknowlogick
20da414145 Code Cleanup (#869)
- switch to golangci-lint for linting
- switch to gofmpt for formatting
- fix lint and fmt issues that came up from switch to new tools
- upgrade go-sdk to 0.23.2
- support pagination for listing tracked times
- remove `FixPullHeadSha` workaround (upstream fix has been merged for 5+ years at this point)
- standardize on US spelling (previously a mix of US&UK spelling)
- remove some unused code
- reduce some duplication in parsing state and issue type
- reduce some duplication in reading input for secrets and variables
- reduce some duplication with PR Review code
- report error for when yaml parsing fails
- various other misc cleanup

Reviewed-on: https://gitea.com/gitea/tea/pulls/869
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2026-02-02 22:39:26 +00:00
techknowlogick
ae740a66e8 update sdk version (#868)
Reviewed-on: https://gitea.com/gitea/tea/pulls/868
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2026-02-02 19:54:44 +00:00
techknowlogick
c2e9265dae bump more CI actions 2026-02-02 19:53:20 +00:00
techknowlogick
45260e1a1f bump action versions in CI for PRs
disable govulncheck temporarily
2026-02-02 19:50:25 +00:00
Alain Thiffault
7ab3366220 fix(labels): improve delete command and fix --id flag type (#865)
## Summary

Fix the `tea labels delete` and `tea labels update` commands which were silently ignoring the `--id` flag.

## Problem

Both commands used `IntFlag` for the `--id` parameter but called `ctx.Int64("id")` to retrieve the value. This type mismatch caused the ID to always be read as `0`, making the commands useless.

**Before (bug):**
```bash
$ tea labels delete --id 36 --debug
DELETE: .../labels/0   # Wrong! ID ignored
```

**After (fix):**
```bash
$ tea labels delete --id 36 --debug
GET: .../labels/36     # Verify exists
DELETE: .../labels/36  # Correct ID
Label 'my-label' (id: 36) deleted successfully
```

## Changes

### labels/delete.go
- Change `IntFlag` to `Int64Flag` to match `ctx.Int64()` usage
- Make `--id` flag required
- Verify label exists before attempting deletion
- Provide clear error messages with label name and ID context
- Print success message after deletion

### labels/update.go
- Change `IntFlag` to `Int64Flag` to fix the same bug

## Test plan

- [x] `go test ./...` passes
- [x] `go vet ./...` passes
- [x] `gofmt` check passes
- [x] Manual testing confirms ID is now correctly passed to API
- [ ] CI passes

Reviewed-on: https://gitea.com/gitea/tea/pulls/865
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Alain Thiffault <athiffau@effectivemomentum.com>
Co-committed-by: Alain Thiffault <athiffau@effectivemomentum.com>
2026-01-25 23:36:42 +00:00
Alain Thiffault
68b9620b8c fix: expose pagination flags for secrets list command (#853)
The command uses flags.GetListOptions() internally but didn't expose --page and --limit flags to users, making pagination inaccessible.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/853
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Alain Thiffault <athiffau@effectivemomentum.com>
Co-committed-by: Alain Thiffault <athiffau@effectivemomentum.com>
2025-12-05 06:14:41 +00:00
Alain Thiffault
e961a8f01d fix: expose pagination flags for webhooks list command (#852)
The command uses flags.GetListOptions() internally but didn't expose --page and --limit flags to users, making pagination inaccessible.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/852
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Alain Thiffault <athiffau@effectivemomentum.com>
Co-committed-by: Alain Thiffault <athiffau@effectivemomentum.com>
2025-12-05 06:14:34 +00:00
Alain Thiffault
f59430a42a fix: pass pagination options to ListRepoPullRequests (#851)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/851
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Alain Thiffault <athiffau@effectivemomentum.com>
Co-committed-by: Alain Thiffault <athiffau@effectivemomentum.com>
2025-12-05 06:14:01 +00:00
Lunny Xiao
7e2e7ee809 Fix delete repo description (#858)
Fix #857

Reviewed-on: https://gitea.com/gitea/tea/pulls/858
2025-12-05 06:11:38 +00:00
Riccardo Förster
1d1d9197ee feat(issue): Add JSON output and file redirection (#841)
This change enhances the 'issue' command functionality by enabling structured JSON
output for single issue views and introducing a method for output redirection.

**Changes Implemented:**

1. Enables the existing `--output json` flag for single issue commands (e.g., 'tea issue 17'). This flag was previously ignored in this context.
2. Introduces the new `--out <filename>` flag, which redirects the marshaled JSON output from stdout to the specified file.

Feeback more then welcome.

Co-authored-by: Jonas Toth <development@jonas-toth.eu>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/841
Reviewed-by: TheFox0x7 <thefox0x7@noreply.gitea.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Riccardo Förster <riccardo.foerster@sarad.de>
Co-committed-by: Riccardo Förster <riccardo.foerster@sarad.de>
2025-11-29 05:05:30 +00:00
TheFox0x7
f6d4b5fa4f remove group readwrite permission (#856)
closes: https://gitea.com/gitea/tea/issues/855
Reviewed-on: https://gitea.com/gitea/tea/pulls/856
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-committed-by: TheFox0x7 <thefox0x7@gmail.com>
2025-11-27 22:45:25 +00:00
Brandon Martin
016e068c60 Fix: Enable git worktree support and improve pr create error handling (#850)
## Problem

Tea commands fail when run from git worktrees with the error:
Remote repository required: Specify ID via --repo or execute from a
local git repo.

Even though the worktree is in a valid git repository with remotes
configured.

Additionally, `tea pr create` was missing context validation, showing
cryptic errors like `"path segment [0]
is empty"` instead of helpful messages.

## Root Cause

1. **Worktree issue**: go-git's `PlainOpenWithOptions` was not
configured to read the `commondir` file that
git worktrees use. This file points to the main repository's `.git`
directory where remotes are actually
stored (worktrees don't have their own remotes).

2. **PR create issue**: Missing `ctx.Ensure()` validation meant errors
weren't caught early with clear
messages.

## Solution

### 1. Enable worktree support (`modules/git/repo.go`)
```go
EnableDotGitCommonDir: true, // Enable commondir support for worktrees

This tells go-git to:
- Read the commondir file in .git/worktrees/<name>/commondir
- Follow the reference (typically ../..) to the main repository
- Load remotes from the main repo's config

2. Add context validation (cmd/pulls/create.go)

ctx.Ensure(context.CtxRequirement{
LocalRepo:  true,
RemoteRepo: true,
})

Provides clear error messages and matches the pattern used in pr
checkout (fixed in commit 0970b945 from
2020).

3. Add test coverage (modules/git/repo_test.go)

- Creates a real git repository with a worktree
- Verifies that RepoFromPath() can open the worktree
- Confirms that Config() correctly reads remotes from main repo

Test Results

Without fix:
 FAIL: Should NOT be empty, but was map[]

With fix:
 PASS: TestRepoFromPath_Worktree (0.12s)

Manual test in worktree:
cd /path/to/worktree
tea pr create --title "test"
# Now works! 

Checklist

- Tested manually in a git worktree
- Added test case that fails without the fix
- All existing tests pass

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/850
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Brandon Martin <brandon@codedmart.com>
Co-committed-by: Brandon Martin <brandon@codedmart.com>
2025-11-24 22:21:19 +00:00
Lunny Xiao
587b31503d Upgrade dependencies (#849)
Reviewed-on: https://gitea.com/gitea/tea/pulls/849
2025-11-24 19:21:55 +00:00
qwerty287
4877f181fb Only prompt for SSH passphrase if necessary (#844)
Since one of the last updates (I cannot tell you exactly which one, but likely 0.10 or 0.11), tea always asks me for my ssh passphrase without actually needing it. I do not have anything configured regarding SSH keys.

The passphrase is not even verified, you can enter anything there. But as this is quite annoying, I fixed this by moving the prompt to only be used when a ssh key/cert is configured.

Would be nice to get this in. Thanks!

Reviewed-on: https://gitea.com/gitea/tea/pulls/844
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: qwerty287 <qwerty287@posteo.de>
Co-committed-by: qwerty287 <qwerty287@posteo.de>
2025-11-20 01:32:28 +00:00
Ross Golder
81481f8f9d Fix: Only prompt for login confirmation when no default login is set (#839)
When running tea commands outside of a repository context, tea falls back to using the default login but always prompted for confirmation, even when a default was set. This fix only prompts when no default is configured.

Reviewed-on: https://gitea.com/gitea/tea/pulls/839
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Ross Golder <ross@golder.org>
Co-committed-by: Ross Golder <ross@golder.org>
2025-10-27 17:52:04 +00:00
Ross Golder
3495ec5ed4 feat: add repository webhook management (#798)
## Summary

This PR adds support for organization-level and global webhooks in the tea CLI tool.

## Changes Made

### Organization Webhooks
- Added `--org` flag to webhook commands to operate on organization-level webhooks
- Implemented full CRUD operations for org webhooks (create, list, update, delete)
- Extended TeaContext to support organization scope

### Global Webhooks
- Added `--global` flag with placeholder implementation
- Ready for when Gitea SDK adds global webhook API methods

### Technical Details
- Updated context handling to support org/global scopes
- Modified all webhook subcommands (create, list, update, delete)
- Maintained backward compatibility for repository webhooks
- Updated tests and documentation

## Usage Examples

```bash
# Repository webhooks (existing)
tea webhooks list
tea webhooks create https://example.com/hook --events push

# Organization webhooks (new)
tea webhooks list --org myorg
tea webhooks create https://example.com/hook --org myorg --events push,pull_request

# Global webhooks (future)
tea webhooks list --global
```

## Testing
- All existing tests pass
- Updated test expectations for new descriptions
- Manual testing of org webhook operations completed

Closes: webhook management feature request
Reviewed-on: https://gitea.com/gitea/tea/pulls/798
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Ross Golder <ross@golder.org>
Co-committed-by: Ross Golder <ross@golder.org>
2025-10-19 03:40:23 +00:00
Ross Golder
7a5c260268 feat: add actions management commands (#796)
## Summary

This PR adds comprehensive Actions secrets and variables management functionality to the tea CLI, enabling users to manage their repository's CI/CD configuration directly from the command line.

## Features Added

### Actions Secrets Management
- **List secrets**: `tea actions secrets list` - Display all repository action secrets
- **Create secrets**: `tea actions secrets create <name>` - Create new secrets with interactive prompts
- **Delete secrets**: `tea actions secrets delete <name>` - Remove existing secrets

### Actions Variables Management
- **List variables**: `tea actions variables list` - Display all repository action variables
- **Set variables**: `tea actions variables set <name> <value>` - Create or update variables
- **Delete variables**: `tea actions variables delete <name>` - Remove existing variables

## Implementation Details

- **Interactive prompts**: Secure input handling for sensitive secret values
- **Input validation**: Proper validation for secret/variable names and values
- **Table formatting**: Consistent output formatting with existing tea commands
- **Error handling**: Comprehensive error handling and user feedback
- **Test coverage**: Full test suite for all functionality

## Usage Examples

```bash
# Secrets management
tea actions secrets list
tea actions secrets create API_KEY    # Will prompt securely for value
tea actions secrets delete OLD_SECRET

# Variables management
tea actions variables list
tea actions variables set API_URL https://api.example.com
tea actions variables delete UNUSED_VAR
```

## Related Issue

Resolves #797

## Testing

- All new functionality includes comprehensive unit tests
- Integration with existing tea CLI patterns and conventions
- Validated against Gitea Actions API

Reviewed-on: https://gitea.com/gitea/tea/pulls/796
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Ross Golder <ross@golder.org>
Co-committed-by: Ross Golder <ross@golder.org>
2025-10-19 02:53:17 +00:00
Lunny Xiao
90f8624ae7 Login requires a http/https login URL and revmoe SSH as a login method. SSH will be optional (#826)
Fix #825

Reviewed-on: https://gitea.com/gitea/tea/pulls/826
2025-10-18 23:09:27 +00:00
Lunny Xiao
61d4e571a7 Fix Pr Create crash (#823)
Fix #822

Reviewed-on: https://gitea.com/gitea/tea/pulls/823
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-10-08 14:43:38 +00:00
Lunny Xiao
4f33146b70 add test for matching logins (#820)
Reviewed-on: https://gitea.com/gitea/tea/pulls/820
2025-10-03 18:05:51 +00:00
Lunny Xiao
08b83986dd Update README.md (#819)
Use official docker images on README

Reviewed-on: https://gitea.com/gitea/tea/pulls/819
Reviewed-by: TheFox0x7 <thefox0x7@noreply.gitea.com>
2025-09-25 07:08:21 +00:00
Lunny Xiao
6acb29efd7 Fix yaml output single quote (#814)
Fix #659

Reviewed-on: https://gitea.com/gitea/tea/pulls/814
2025-09-14 00:23:12 +00:00
Valentin Brandl
4f513ca3e3 generate man page (#811)
[CLI.md](src/branch/main/docs/CLI.md) already gets generated using `urfave/cli-docs`. `cli-docs` can also generate man pages.

This change extends the doc generator to also generate a man page for `tea`.

* Add a subcommand to the doc generator to print the generated man page to stdout

Closes #777.

Co-authored-by: Valentin Brandl <mail@vbrandl.net>
Reviewed-on: https://gitea.com/gitea/tea/pulls/811
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: TheFox0x7 <thefox0x7@noreply.gitea.com>
Co-authored-by: Valentin Brandl <vbrandl@noreply.gitea.com>
Co-committed-by: Valentin Brandl <vbrandl@noreply.gitea.com>
2025-09-14 00:17:28 +00:00
Lunny Xiao
cc20b52ab3 feat: add validation for object-format flag in repo create command (#741)
This PR adds validation for the `--object-format` flag in the `repo create` command. The flag now accepts only `sha1` or `sha256` as valid values, and returns an error for any other value.

Changes:
- Added validation in `runRepoCreate` to check for valid object format values
- Added unit tests to verify the validation logic
- Fixed the field name from `ObjectFormat` to `ObjectFormatName` to match the SDK

The changes ensure that users get clear error messages when using invalid object format values, improving the user experience.

Fix #727
Fix #660
Fix #767

Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/741
Reviewed-by: TheFox0x7 <thefox0x7@noreply.gitea.com>
2025-09-12 16:51:43 +00:00
Lunny Xiao
2ca114e309 Fix release version (#815)
Reviewed-on: https://gitea.com/gitea/tea/pulls/815
Reviewed-by: TheFox0x7 <thefox0x7@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-09-11 19:18:19 +00:00
TheFox0x7
45771265c4 update gitea sdk to v0.22 (#813)
needed because of: 25b5fb0ff7
closes: https://gitea.com/gitea/tea/issues/812

Reviewed-on: https://gitea.com/gitea/tea/pulls/813
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-committed-by: TheFox0x7 <thefox0x7@gmail.com>
2025-09-10 23:18:05 +00:00
Lunny Xiao
8faa1d33f4 don't fallback login directly (#806)
Fix #499

Reviewed-on: https://gitea.com/gitea/tea/pulls/806
2025-09-10 21:30:15 +00:00
Lunny Xiao
ddf5c0a5bb Check duplicated login name in interact mode when creating new login (#803)
Reviewed-on: https://gitea.com/gitea/tea/pulls/803
2025-09-10 20:42:49 +00:00
Lunny Xiao
d3c73cd5dc Fix bug when output json with special chars (#801)
Fix #800

Reviewed-on: https://gitea.com/gitea/tea/pulls/801
2025-09-10 20:36:27 +00:00
Lunny Xiao
6c958eec99 add debug mode and update readme (#805)
Fix #456
Fix #207

Reviewed-on: https://gitea.com/gitea/tea/pulls/805
2025-09-10 19:10:02 +00:00
Lunny Xiao
d531c6fdb0 update go.mod to retract the wrong tag v1.3.3 (#802)
Fix #674

Reviewed-on: https://gitea.com/gitea/tea/pulls/802
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-08-27 15:38:58 +00:00
TheFox0x7
cd58296995 revert completion scripts removal (#808)
partial revert of: https://gitea.com/gitea/tea/pulls/782
closes: https://gitea.com/gitea/tea/issues/784

Old versions of tea have hardcoded completion fetched from main branch

Those should not be used from v0.10 onward.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/808
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-committed-by: TheFox0x7 <thefox0x7@gmail.com>
2025-08-26 23:15:36 +00:00
TheFox0x7
b74405530a Remove pagination from context (#807)
Pagination related flags now write directly to ListOption struct and enforce non negative numbers.
Flag tests were added to cover the validation

Reviewed-on: https://gitea.com/gitea/tea/pulls/807
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-committed-by: TheFox0x7 <thefox0x7@gmail.com>
2025-08-26 23:13:27 +00:00
Chen Linxuan
8876fe3cb8 Continue auth when failed to open browser (#794)
When users login gitea on a headless server via ssh, xdg-open might not be installed on that machine. So tea may fail to open URL itself. In this case, users can use the other machine to open the URL for authentication.

Github CLI act like this, too.

Signed-off-by: Chen Linxuan <me@black-desk.cn>

Reviewed-on: https://gitea.com/gitea/tea/pulls/794
Reviewed-by: blumia <blumia@noreply.gitea.com>
Co-authored-by: Chen Linxuan <me@black-desk.cn>
Co-committed-by: Chen Linxuan <me@black-desk.cn>
2025-08-18 03:12:25 +00:00
Lunny Xiao
07ca1ba106 Fix bug (#793)
Partially fix #791

Reviewed-on: https://gitea.com/gitea/tea/pulls/793
Reviewed-by: hiifong <i@hiif.ong>
2025-08-15 02:38:45 +00:00
Lunny Xiao
d643e94a69 Fix tea login add with ssh public key bug (#789)
Fix #705

Reviewed-on: https://gitea.com/gitea/tea/pulls/789
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
2025-08-11 19:23:28 +00:00
Tim Riedl
d2ccead88b Add temporary authentication via environment variables (#639)
#633

Co-authored-by: Tim Riedl <mail@tim-riedl.de>
Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Lunny Xiao <lunny@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/639
Co-authored-by: Tim Riedl <uvulpos@noreply.gitea.com>
Co-committed-by: Tim Riedl <uvulpos@noreply.gitea.com>
2025-08-11 18:53:09 +00:00
Lunny Xiao
449b2e3117 Fix attachment size (#787)
Fix `tea releases assets <tag_name>` displayed wrong attachment size.

Reviewed-on: https://gitea.com/gitea/tea/pulls/787
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-08-11 18:45:12 +00:00
Lunny Xiao
9e8c71e13e deploy image when tagging (#792)
Reviewed-on: https://gitea.com/gitea/tea/pulls/792
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-08-11 18:44:34 +00:00
Lunny Xiao
2ddb3bd4a1 Add Zip URL for release list (#788)
Fix #780

Reviewed-on: https://gitea.com/gitea/tea/pulls/788
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-08-11 18:43:34 +00:00
Lunny Xiao
4c00b8b571 Use bubbletea instead of survey for interacting with TUI (#786)
Fix #772

Reviewed-on: https://gitea.com/gitea/tea/pulls/786
Reviewed-by: Bo-Yi Wu (吳柏毅) <appleboy.tw@gmail.com>
2025-08-11 18:23:52 +00:00
techknowlogick
c0eb30af03 capitalize a few items 2025-08-11 15:29:19 +00:00
techknowlogick
e462acfcd6 rm out of date comparison file 2025-08-11 15:27:04 +00:00
Michal Suchanek
ee111d7c12 README: Document logging in to gitea (#790)
References: #569 #675 #697 #767 #775
Reviewed-on: https://gitea.com/gitea/tea/pulls/790
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Michal Suchanek <msuchanek@suse.de>
Co-committed-by: Michal Suchanek <msuchanek@suse.de>
2025-08-11 15:25:42 +00:00
TheFox0x7
5f35cebcf1 remove autocomplete command (#782)
Add a note in readme about adding shell completions

Closes: https://gitea.com/gitea/tea/issues/781
Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/782
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-committed-by: TheFox0x7 <thefox0x7@gmail.com>
2025-07-21 21:24:28 +00:00
Renovate Bot
a010c9bc7f chore(deps): update ghcr.io/devcontainers/features/git-lfs docker tag to v1.2.5 (#773)
Reviewed-on: https://gitea.com/gitea/tea/pulls/773
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-07-21 21:22:04 +00:00
TheFox0x7
ab4ad92d40 replace arch package url (#783)
Direct users to main repository instead of AUR package

Reviewed-on: https://gitea.com/gitea/tea/pulls/783
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-committed-by: TheFox0x7 <thefox0x7@gmail.com>
2025-07-21 21:21:25 +00:00
Kirill Müller
15052b4dcc fix: Reenable -p and --limit switches (#778)
Reduced version of #776, without the new tests.

Fixes #771.

Reviewed-on: https://gitea.com/gitea/tea/pulls/778
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Kirill Müller <kirill@cynkra.com>
Co-committed-by: Kirill Müller <kirill@cynkra.com>
2025-07-14 14:28:35 +00:00
Lunny Xiao
8212d5f527 Update release ci (#768)
Reviewed-on: https://gitea.com/gitea/tea/pulls/768
2025-06-16 19:23:55 +00:00
Renovate Bot
d536242aa9 chore(deps): update crazy-max/ghaction-import-gpg action to v6 (#736)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) | action | major | `v5` -> `v6` |

---

### Release Notes

<details>
<summary>crazy-max/ghaction-import-gpg (crazy-max/ghaction-import-gpg)</summary>

### [`v6`](https://github.com/crazy-max/ghaction-import-gpg/compare/v5...v6)

[Compare Source](https://github.com/crazy-max/ghaction-import-gpg/compare/v5...v6)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yMDcuMyIsInVwZGF0ZWRJblZlciI6IjM5LjIwNy4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/736
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-06-16 17:10:55 +00:00
Renovate Bot
ffff540aa7 fix(deps): update module github.com/urfave/cli/v3 to v3.3.8 (#766)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [github.com/urfave/cli/v3](https://github.com/urfave/cli) | require | patch | `v3.3.3` -> `v3.3.8` |

---

### Release Notes

<details>
<summary>urfave/cli (github.com/urfave/cli/v3)</summary>

### [`v3.3.8`](https://github.com/urfave/cli/releases/tag/v3.3.8)

[Compare Source](https://github.com/urfave/cli/compare/v3.3.7...v3.3.8)

#### What's Changed

-   Remove "alpha" wording around `v3` series by [@&#8203;meatballhat](https://github.com/meatballhat) in https://github.com/urfave/cli/pull/2155

**Full Changelog**: https://github.com/urfave/cli/compare/v3.3.7...v3.3.8

### [`v3.3.7`](https://github.com/urfave/cli/releases/tag/v3.3.7)

[Compare Source](https://github.com/urfave/cli/compare/v3.3.6...v3.3.7)

#### What's Changed

-   fix: add missing `IsLocal` for BoolWithInverseFlag by [@&#8203;huiyifyj](https://github.com/huiyifyj) in https://github.com/urfave/cli/pull/2151
-   Fix OnUsageError Trigger When Error Is Caused by Mutually Exclusive Flags by [@&#8203;Ali-Doustkani](https://github.com/Ali-Doustkani) in https://github.com/urfave/cli/pull/2152

#### New Contributors

-   [@&#8203;Ali-Doustkani](https://github.com/Ali-Doustkani) made their first contribution in https://github.com/urfave/cli/pull/2152

**Full Changelog**: https://github.com/urfave/cli/compare/v3.3.6...v3.3.7

### [`v3.3.6`](https://github.com/urfave/cli/releases/tag/v3.3.6)

[Compare Source](https://github.com/urfave/cli/compare/v3.3.5...v3.3.6)

#### What's Changed

-   Fish completions with identically named sub-commands now work by [@&#8203;bittrance](https://github.com/bittrance) in https://github.com/urfave/cli/pull/2130

**Full Changelog**: https://github.com/urfave/cli/compare/v3.3.5...v3.3.6

### [`v3.3.5`](https://github.com/urfave/cli/releases/tag/v3.3.5)

[Compare Source](https://github.com/urfave/cli/compare/v3.3.4...v3.3.5)

#### What's Changed

-   Fix:(issue\_2137) Ensure default value for bool with inverse flag is h… by [@&#8203;dearchap](https://github.com/dearchap) in https://github.com/urfave/cli/pull/2138
-   Fix:(issue\_2131) Show help text for BoolWithInverseFlag by [@&#8203;Juneezee](https://github.com/Juneezee) in https://github.com/urfave/cli/pull/2142

**Full Changelog**: https://github.com/urfave/cli/compare/v3.3.4...v3.3.5

### [`v3.3.4`](https://github.com/urfave/cli/releases/tag/v3.3.4)

[Compare Source](https://github.com/urfave/cli/compare/v3.3.3...v3.3.4)

#### What's Changed

-   Fix Docs(issue\_2125) Add PathFlag to StringFlag migration by [@&#8203;dearchap](https://github.com/dearchap) in https://github.com/urfave/cli/pull/2136
-   fix: remove extraneous space from subcommand help template by [@&#8203;G-Rath](https://github.com/G-Rath) in https://github.com/urfave/cli/pull/2140
-   Fix:(issue\_2135) Correct formatting of default subcommand USAGE text by [@&#8203;zzspoon](https://github.com/zzspoon) in https://github.com/urfave/cli/pull/2139

#### New Contributors

-   [@&#8203;zzspoon](https://github.com/zzspoon) made their first contribution in https://github.com/urfave/cli/pull/2139

**Full Changelog**: https://github.com/urfave/cli/compare/v3.3.3...v3.3.4

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40OS42IiwidXBkYXRlZEluVmVyIjoiNDAuNDkuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/766
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-06-16 17:07:00 +00:00
Renovate Bot
f09d6ca46b fix(deps): update module github.com/go-git/go-git/v5 to v5.16.2 (#765)
Reviewed-on: https://gitea.com/gitea/tea/pulls/765
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-06-10 14:52:26 +00:00
TheFox0x7
0e54bae0c4 migrate tea to urfave v3 (#760)
I tested this somewhat, but I haven't been using the cli before so I'm not sure if there are changes - there shouldn't be though.

Reviewed-on: https://gitea.com/gitea/tea/pulls/760
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-committed-by: TheFox0x7 <thefox0x7@gmail.com>
2025-06-10 05:19:59 +00:00
JD Daniels
5420af1dfa fix: support SSH remotes with non-standard ports (#761)
The SSH host matching logic in contextFromLocalRepo only compared
exact host:port combinations. This failed for SSH remotes using
non-standard ports because the login SSH host configuration typically
stores just the hostname without the port.

This change allows matching both the full host:port string and the
hostname-only version against the configured SSH host, enabling
tea to work with SSH remotes on non-standard ports.

Fixes issue where commands requiring RemoteRepo failed with
'Remote repository required' error when using SSH on custom ports.

Signed-off-by: JD Daniels <jd@danielsdynamic.ca>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/761
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: JD Daniels <jd@danielsdynamic.ca>
Co-committed-by: JD Daniels <jd@danielsdynamic.ca>
2025-06-07 03:21:11 +00:00
Renovate Bot
177b7397f3 fix(deps): update module github.com/go-git/go-git/v5 to v5.16.1 (#762)
Reviewed-on: https://gitea.com/gitea/tea/pulls/762
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-06-06 00:13:48 +00:00
Renovate Bot
11ec662434 fix(deps): update module golang.org/x/crypto to v0.39.0 (#764)
Reviewed-on: https://gitea.com/gitea/tea/pulls/764
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-06-06 00:13:38 +00:00
Renovate Bot
8b1601c955 chore(deps): update dependency go to v1.24.4 (#763)
Reviewed-on: https://gitea.com/gitea/tea/pulls/763
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-06-06 00:13:27 +00:00
Renovate Bot
213f976dcf fix(deps): update module github.com/olekukonko/tablewriter to v1.0.7 (#759)
Reviewed-on: https://gitea.com/gitea/tea/pulls/759
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-05-28 15:10:06 +00:00
Renovate Bot
59b35fecfe fix(deps): update module github.com/olekukonko/tablewriter to v1.0.5 (#758)
Reviewed-on: https://gitea.com/gitea/tea/pulls/758
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-05-15 15:34:37 +00:00
techknowlogick
d4887f6b9d Bump Table Dep (#757)
Reviewed-on: https://gitea.com/gitea/tea/pulls/757
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2025-05-13 21:03:46 +00:00
techknowlogick
c5f398dcd2 minor helper fixes (#756)
Reviewed-on: https://gitea.com/gitea/tea/pulls/756
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2025-05-13 20:55:41 +00:00
Renovate Bot
9cb0d8baa1 chore(deps): update dependency go to v1.24.3 (#754)
Reviewed-on: https://gitea.com/gitea/tea/pulls/754
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-05-13 18:34:44 +00:00
Renovate Bot
662d441e3a fix(deps): update module golang.org/x/crypto to v0.38.0 (#751)
Reviewed-on: https://gitea.com/gitea/tea/pulls/751
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-05-06 03:54:32 +00:00
Renovate Bot
894646a83c fix(deps): update module github.com/go-git/go-git/v5 to v5.16.0 (#748)
Reviewed-on: https://gitea.com/gitea/tea/pulls/748
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-05-06 03:45:41 +00:00
Renovate Bot
4adc925aa1 fix(deps): update module github.com/charmbracelet/glamour to v0.10.0 (#749)
Reviewed-on: https://gitea.com/gitea/tea/pulls/749
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-05-06 03:45:16 +00:00
Renovate Bot
affef1980e fix(deps): update module golang.org/x/oauth2 to v0.30.0 (#752)
Reviewed-on: https://gitea.com/gitea/tea/pulls/752
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-05-06 03:44:39 +00:00
Renovate Bot
6efa25d0f7 fix(deps): update module github.com/go-git/go-git/v5 to v5.15.0 (#747)
Reviewed-on: https://gitea.com/gitea/tea/pulls/747
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-04-12 00:58:08 +00:00
Renovate Bot
2e389349e1 fix(deps): update module github.com/charmbracelet/glamour to v0.9.1 (#735)
Reviewed-on: https://gitea.com/gitea/tea/pulls/735
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-04-11 00:16:55 +00:00
Renovate Bot
2f9bc0f8d9 fix(deps): update module golang.org/x/oauth2 to v0.29.0 (#742)
Reviewed-on: https://gitea.com/gitea/tea/pulls/742
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-04-11 00:16:27 +00:00
Renovate Bot
2e4422577b fix(deps): update module code.gitea.io/sdk/gitea to v0.21.0 (#740)
Reviewed-on: https://gitea.com/gitea/tea/pulls/740
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-04-10 18:29:04 +00:00
Renovate Bot
1ea89f3409 fix(deps): update module golang.org/x/crypto to v0.37.0 (#743)
Reviewed-on: https://gitea.com/gitea/tea/pulls/743
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-04-10 18:26:21 +00:00
Renovate Bot
d2c9df3b17 chore(deps): update ghcr.io/devcontainers/features/git-lfs docker tag to v1.2.4 (#745)
Reviewed-on: https://gitea.com/gitea/tea/pulls/745
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-04-10 18:25:13 +00:00
Renovate Bot
21ede75fee chore(deps): update dependency go to v1.24.2 (#739)
Reviewed-on: https://gitea.com/gitea/tea/pulls/739
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-04-03 18:17:36 +00:00
techknowlogick
d633e3018d test fallback to v5 for gpg action 2025-03-19 01:56:55 +00:00
Renovate Bot
3447e0b6ab fix(deps): update module golang.org/x/oauth2 to v0.28.0 (#734)
Reviewed-on: https://gitea.com/gitea/tea/pulls/734
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-03-19 01:55:45 +00:00
Renovate Bot
1681af8a3e fix(deps): update module golang.org/x/crypto to v0.36.0 (#730)
Reviewed-on: https://gitea.com/gitea/tea/pulls/730
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-03-18 17:28:43 +00:00
techknowlogick
62dc1dde95 Login via oauth2 flow (#725)
Reviewed-on: https://gitea.com/gitea/tea/pulls/725
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2025-03-18 17:01:49 +00:00
Renovate Bot
e82dd9e08d fix(deps): update module github.com/urfave/cli/v2 to v2.27.6 (#729)
Reviewed-on: https://gitea.com/gitea/tea/pulls/729
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-03-18 16:46:15 +00:00
Renovate Bot
9785f6acf7 fix(deps): update module github.com/charmbracelet/glamour to v0.9.0 (#732)
Reviewed-on: https://gitea.com/gitea/tea/pulls/732
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-03-18 16:45:49 +00:00
Renovate Bot
5d78e27542 chore(deps): update mcr.microsoft.com/devcontainers/go docker tag to v1.24 (#733)
Reviewed-on: https://gitea.com/gitea/tea/pulls/733
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-03-18 16:45:38 +00:00
Renovate Bot
e2c199fcd5 chore(deps): update dependency go to v1.24.1 (#728)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [go](https://go.dev/) ([source](https://github.com/golang/go)) | toolchain | patch | `1.24.0` -> `1.24.1` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xODUuNyIsInVwZGF0ZWRJblZlciI6IjM5LjE4NS43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/728
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-03-05 01:45:28 +00:00
Renovate Bot
9888ca69b2 fix(deps): update module github.com/go-git/go-git/v5 to v5.14.0 (#724)
Reviewed-on: https://gitea.com/gitea/tea/pulls/724
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-02-28 01:57:47 +00:00
Vincent Neubauer
57e3400f0f Feat: interactive issue edit command (#708)
If there are no flags passed to the `issues edit` command, it prompts
for changes to the properties like title, description, labels, etc.

This is a follow-up to <https://gitea.com/gitea/tea/pulls/506>.

Closes: <https://gitea.com/gitea/tea/issues/605>
Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/708
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Vincent Neubauer <v.neubauer@darlor.de>
Co-committed-by: Vincent Neubauer <v.neubauer@darlor.de>
2025-02-27 18:49:24 +00:00
techknowlogick
60636cd7d8 Use flakes vs devbox (#723)
Reviewed-on: https://gitea.com/gitea/tea/pulls/723
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2025-02-27 18:33:37 +00:00
Renovate Bot
47ee397110 chore(deps): update dependency go to v1.24.0 (#719)
Reviewed-on: https://gitea.com/gitea/tea/pulls/719
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-02-26 16:11:22 +00:00
Renovate Bot
b4655aefc4 fix(deps): update module golang.org/x/crypto to v0.35.0 (#721)
Reviewed-on: https://gitea.com/gitea/tea/pulls/721
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-02-26 16:10:39 +00:00
Renovate Bot
aba2dadcfd fix(deps): update module github.com/muesli/termenv to v0.16.0 (#720)
Reviewed-on: https://gitea.com/gitea/tea/pulls/720
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-02-26 16:10:21 +00:00
Matheus Sampaio Queiroga
c2ddda6800 Fix helper panic (#676)
Fix helper on get login struct

Reviewed-on: https://gitea.com/gitea/tea/pulls/676
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Matheus Sampaio Queiroga <srherobrine20@gmail.com>
Co-committed-by: Matheus Sampaio Queiroga <srherobrine20@gmail.com>
2025-02-26 16:09:06 +00:00
Renovate Bot
fe1928679a chore(deps): update dependency go (#715)
Reviewed-on: https://gitea.com/gitea/tea/pulls/715
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-02-16 17:46:19 +00:00
Renovate Bot
681e665a59 fix(deps): update module golang.org/x/crypto to v0.33.0 (#718)
Reviewed-on: https://gitea.com/gitea/tea/pulls/718
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-02-16 17:45:59 +00:00
Renovate Bot
72790aa50a fix(deps): update module github.com/go-git/go-git/v5 to v5.13.2 (#713)
Reviewed-on: https://gitea.com/gitea/tea/pulls/713
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-01-27 02:10:31 +00:00
Renovate Bot
1c24e199f7 chore(deps): update dependency go to v1.23.5 (#712)
Reviewed-on: https://gitea.com/gitea/tea/pulls/712
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-01-23 18:15:26 +00:00
Renovate Bot
8efa1aaf1f fix(deps): update module code.gitea.io/sdk/gitea to v0.20.0 (#711)
Reviewed-on: https://gitea.com/gitea/tea/pulls/711
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-01-10 17:55:51 +00:00
Renovate Bot
bc29fa5c9c fix(deps): update module golang.org/x/crypto to v0.32.0 (#710)
Reviewed-on: https://gitea.com/gitea/tea/pulls/710
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-01-10 17:55:38 +00:00
Nithin Philips
f3fe2a0532 release create: Add --note-file flag to read release notes from a file (#678)
Add  a `--note-file` argument to pass a file with multi-line release notes instead of trying to pass as an argument using `--note`.

Co-authored-by: Lunny Xiao <lunny@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/678
Reviewed-by: Lunny Xiao <lunny@noreply.gitea.com>
Co-authored-by: Nithin Philips <nithin@nithinphilips.com>
Co-committed-by: Nithin Philips <nithin@nithinphilips.com>
2025-01-08 05:00:21 +00:00
Renovate Bot
54b3f8e5b2 fix(deps): update module golang.org/x/term to v0.28.0 (#709)
Reviewed-on: https://gitea.com/gitea/tea/pulls/709
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-01-06 04:32:32 +00:00
Renovate Bot
4e0fab6e7f fix(deps): update module github.com/go-git/go-git/v5 to v5.13.1 (#707)
Reviewed-on: https://gitea.com/gitea/tea/pulls/707
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2025-01-03 04:09:44 +00:00
Vincent Neubauer
6f924df8ac Fix/Login Edit Use Editor Env (#706)
It is very common to set `$EDITOR` to an terminal editor.
To do so, we have to attach the stdin and stdout to interact with it.
Otherwise the command would do nothing (when using `.Run()`) as it
silently fails or hang the command (using `.Start()`) because it waits
forever for input.
Also attaching stderr to make debugging easier if someone has a
non-compatible or broken `$EDITOR` set.

Reviewed-on: https://gitea.com/gitea/tea/pulls/706
Reviewed-by: Lunny Xiao <lunny@noreply.gitea.com>
Co-authored-by: Vincent Neubauer <v.neubauer@darlor.de>
Co-committed-by: Vincent Neubauer <v.neubauer@darlor.de>
2025-01-02 06:29:53 +00:00
Renovate Bot
66927f8972 fix(deps): update module github.com/go-git/go-git/v5 to v5.13.0 (#704)
Reviewed-on: https://gitea.com/gitea/tea/pulls/704
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-12-29 00:55:34 +00:00
pysen
9de3fc8667 Gitea Actions support (#682)
This reworks the container to be usable with the Gitea act-runner and connect using TLS.

The directory structure is prepared so that a config.yml can be echoed into the container for authentication.

Co-authored-by: Pysen X <pysen@svartavillan.se>
Co-authored-by: Lunny Xiao <lunny@noreply.gitea.com>
Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/682
Reviewed-by: Lunny Xiao <lunny@noreply.gitea.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: pysen <pysen@noreply.gitea.com>
Co-committed-by: pysen <pysen@noreply.gitea.com>
2024-12-27 02:00:57 +00:00
Lunny Xiao
2fb291c6d4 update ci goproxy (#703)
Reviewed-on: https://gitea.com/gitea/tea/pulls/703
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-12-26 22:53:03 +00:00
Renovate Bot
637b7ea010 fix(deps): update github.com/muesli/termenv digest to 8c990cd (#696)
Reviewed-on: https://gitea.com/gitea/tea/pulls/696
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-12-26 21:14:00 +00:00
simongregorebner
f1cad9dbb1 Expose --labels option - Fixes #698 (#699)
Expose the --labels option for the `tea repos migrate` command. This pull request fixes the issue reported in gitea/tea#698

```bash
> tea ( main ) % ./tea repos migrate --help
NAME:
   tea repos migrate - Migrate a repository

USAGE:
   tea repos migrate [command options]

DESCRIPTION:
   Migrate a repository and or mirror it.

OPTIONS:
   --name value              Name of the repository
   --owner value             Owner of the repository
   --clone-url value         Clone URL of the repository
   --service value           Service to migrate from. Supported services are: git, gitea, gitlab, gogs
   --mirror                  Mirror the repository (default: false)
   --private                 Make the repository private (default: false)
   --template                Make the repository a template (default: false)
   --wiki                    Copy the wiki (default: false)
   --issues                  Copy the issues (default: false)
   --labels                  Copy the lables (default: false)
   --pull-requests           Copy the pull requests (default: false)
   --releases                Copy the releases (default: false)
   --milestones              Copy the milestones (default: false)
   --mirror-interval value   Interval to mirror the repository.
   --lfs                     Copy the LFS objects (default: false)
   --lfs-endpoint value      LFS endpoint to use
   --auth-user value         Username to use for authentication.
   --auth-password value     Password to use for authentication.
   --auth-token value        Token to use for authentication.
   --login value, -l value   Use a different Gitea Login. Optional
   --output value, -o value  Output format. (simple, table, csv, tsv, yaml, json)
   --help, -h                show help
```

Fix tested successfully on an own migration from gitlab to gitea

This PR closes gitea/tea#698

Co-authored-by: ebner <simon.ebner@psi.ch>
Reviewed-on: https://gitea.com/gitea/tea/pulls/699
Reviewed-by: Lunny Xiao <lunny@noreply.gitea.com>
Co-authored-by: simongregorebner <simongregorebner@noreply.gitea.com>
Co-committed-by: simongregorebner <simongregorebner@noreply.gitea.com>
2024-12-23 18:40:45 +00:00
Renovate Bot
f2955a468e fix(deps): update github.com/muesli/termenv digest to 0d230cb (#694)
Reviewed-on: https://gitea.com/gitea/tea/pulls/694
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-12-12 05:09:05 +00:00
Renovate Bot
54c198cc84 fix(deps): update module golang.org/x/crypto to v0.31.0 (#695)
Reviewed-on: https://gitea.com/gitea/tea/pulls/695
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-12-12 05:08:36 +00:00
Renovate Bot
3447be6a68 chore(deps): update dependency go to v1.23.4 (#691)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [go](https://go.dev/) ([source](https://github.com/golang/go)) | toolchain | patch | `1.23.3` -> `1.23.4` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS40Mi40IiwidXBkYXRlZEluVmVyIjoiMzkuNDIuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/691
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-12-06 06:19:52 +00:00
Renovate Bot
88628368f6 fix(deps): update module golang.org/x/crypto to v0.30.0 (#692)
Reviewed-on: https://gitea.com/gitea/tea/pulls/692
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-12-05 02:01:15 +00:00
Renovate Bot
768a6fd844 fix(deps): update module github.com/stretchr/testify to v1.10.0 (#689)
Reviewed-on: https://gitea.com/gitea/tea/pulls/689
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-12-01 00:57:28 +00:00
Renovate Bot
eb204541f9 chore(deps): update mcr.microsoft.com/devcontainers/go docker tag to v1.23 (#688)
Reviewed-on: https://gitea.com/gitea/tea/pulls/688
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-12-01 00:56:56 +00:00
Renovate Bot
a9293e2f8b fix(deps): update module golang.org/x/crypto to v0.29.0 (#690)
Reviewed-on: https://gitea.com/gitea/tea/pulls/690
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-12-01 00:56:07 +00:00
Renovate Bot
3cef822f22 fix(deps): update module github.com/adrg/xdg to v0.5.3 (#687)
Reviewed-on: https://gitea.com/gitea/tea/pulls/687
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-11-30 02:53:37 +00:00
Renovate Bot
f9a08f8d3b chore(deps): update ghcr.io/devcontainers/features/git-lfs docker tag to v1.2.3 (#686)
Reviewed-on: https://gitea.com/gitea/tea/pulls/686
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-11-30 02:53:00 +00:00
Renovate Bot
10e53345ed fix(deps): update module github.com/urfave/cli/v2 to v2.27.5 (#667)
Reviewed-on: https://gitea.com/gitea/tea/pulls/667
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-11-30 02:00:43 +00:00
Renovate Bot
09aed015f2 chore(deps): update dependency go to v1.23.3 (#685)
Reviewed-on: https://gitea.com/gitea/tea/pulls/685
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-11-30 01:59:49 +00:00
Renovate Bot
b7191bf6e9 fix(deps): update github.com/muesli/termenv digest to 82936c5 (#684)
Reviewed-on: https://gitea.com/gitea/tea/pulls/684
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-11-30 01:59:10 +00:00
Matheus Sampaio Queiroga
2984ad4964 Add git helper (#612)
Add support to tea login with helper same another tools and `gh`

Reviewed-on: https://gitea.com/gitea/tea/pulls/612
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Matheus Sampaio Queiroga <srherobrine20@gmail.com>
Co-committed-by: Matheus Sampaio Queiroga <srherobrine20@gmail.com>
2024-08-26 11:34:36 +00:00
Renovate Bot
a35bf931ae fix(deps): update module golang.org/x/crypto to v0.26.0 (#664)
Reviewed-on: https://gitea.com/gitea/tea/pulls/664
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-08-16 18:59:57 +00:00
Renovate Bot
edf9f23390 chore(deps): update dependency go to v1.23.0 (#668)
Reviewed-on: https://gitea.com/gitea/tea/pulls/668
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-08-16 18:59:44 +00:00
Renovate Bot
0297738b33 fix(deps): update module code.gitea.io/sdk/gitea to v0.19.0 (#662)
Reviewed-on: https://gitea.com/gitea/tea/pulls/662
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-08-16 16:06:56 +00:00
Renovate Bot
1983c3bbd9 fix(deps): update module github.com/charmbracelet/glamour to v0.8.0 (#663)
Reviewed-on: https://gitea.com/gitea/tea/pulls/663
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-08-16 16:06:30 +00:00
Renovate Bot
d289c290bb fix(deps): update module golang.org/x/term to v0.23.0 (#665)
Reviewed-on: https://gitea.com/gitea/tea/pulls/665
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-08-16 16:06:05 +00:00
harryzcy
100c5a9eee Support auto detecting branch for PRs (#525)
Fix #524

Co-authored-by: harryzcy <harry@harryzheng.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/525
Co-authored-by: harryzcy <harryzcy@noreply.gitea.com>
Co-committed-by: harryzcy <harryzcy@noreply.gitea.com>
2024-07-26 19:21:48 +00:00
Renovate Bot
4ba3139224 fix(deps): update module github.com/urfave/cli/v2 to v2.27.3 (#610)
Reviewed-on: https://gitea.com/gitea/tea/pulls/610
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-07-26 18:06:26 +00:00
Renovate Bot
dd80463cf5 chore(deps): update actions/checkout action to v4 (#661)
Reviewed-on: https://gitea.com/gitea/tea/pulls/661
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-07-26 18:00:52 +00:00
techknowlogick
93480ac98e use v1 of goreleaser until we can bump schema to v2 2024-07-26 16:25:37 +00:00
techknowlogick
f7e2c6f89b v1 of goreleaser until we can update to schema 2 2024-07-26 16:24:42 +00:00
Jake Hillion
493b29afbf context: move human readable note to stderr (#636)
The NOTE shown when an explicit login isn't provided is output on Stdout
instead of Stderr. This messes up the output of any commands when piping
them to a tool such as `yq`. Move this human readable information to
Stderr so it doesn't break any automated parsing.

Reviewed-on: https://gitea.com/gitea/tea/pulls/636
Co-authored-by: Jake Hillion <jake@hillion.co.uk>
Co-committed-by: Jake Hillion <jake@hillion.co.uk>
2024-07-26 16:21:07 +00:00
Dan Čermák
723b0553c5 Add repos rm/delete command (#609)
This fixes https://gitea.com/gitea/tea/issues/239

Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/609
Co-authored-by: Dan Čermák <dcermak@suse.com>
Co-committed-by: Dan Čermák <dcermak@suse.com>
2024-07-26 16:18:58 +00:00
danebou
4c0cef090d Release Asset Management (#619)
Add release asset management. This includes a series of subcommands under `tea release assets`:

-  `tea release assets create <release-tag> <asset> [<asset>...]`: Upload one or more release attachments
- `tea release assets delete <release tag> <attachment name> [<attachment name>...]`: Delete one or more release attachments
- `tea release assets list <release tag>`: List Release Attachments

Co-authored-by: Dane Bouchie <dbouchie@iradimed.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/619
Co-authored-by: danebou <danebou@noreply.gitea.com>
Co-committed-by: danebou <danebou@noreply.gitea.com>
2024-07-26 16:05:12 +00:00
leonard.vimond
20479663f0 tea branches list/protect/unprotect (#645)
Hello,
This is a proposal to support consulting / protecting / unprotecting branches for a specific repository.
I copied the existing code for "issues" report and adapted to branches. There is no change of legacy code so I do not expect any impact.

Supported commands are "list", "protect", "unprotect":
- "List" print the list of branches with some available fields from gitea.Branch type.
- "protect" creates a gitea.BranchProtection with some default parameters for some specific branches
- "unprotect" destroys gitea.BranchProtection for some specific branches

What is printed now could be enriched with additional information gitea datatypes already offer.

Could you please evaluate this proposal?
I would be happy to receive any comment or remark to take into account.

**tea branches unprotect** --login opsi --repo opensky main
**tea branches list**             --login opsi --repo opensky --fields name,protected,user-can-merge,user-can-push,protection
[name protected user-can-merge user-can-push protection]
+--------+-----------+----------------+---------------+------------+
|  NAME  | PROTECTED | USER-CAN-MERGE | USER-CAN-PUSH | PROTECTION |
+--------+-----------+----------------+---------------+------------+
| b_test | false     | true           | true          | <None>     |
| main   | false     | true           | true          | <None>     |
+--------+-----------+----------------+---------------+------------+

**tea branches protect**     --login opsi --repo opensky main
**tea branches list**             --login opsi --repo opensky --fields name,protected,user-can-merge,user-can-push,protection
[name protected user-can-merge user-can-push protection]
+--------+-----------+----------------+---------------+----------------------+
|  NAME  | PROTECTED | USER-CAN-MERGE | USER-CAN-PUSH |      PROTECTION      |
+--------+-----------+----------------+---------------+----------------------+
| b_test | false     | true           | true          | <None>               |
| main   | true      | true           | false         | - enable-push: false |
|        |           |                |               | - approving:  -      |
|        |           |                |               | merging:  - pushing: |
|        |           |                |               |                      |
+--------+-----------+----------------+---------------+----------------------+

Following commands run OK:
> make test
> make fmt
> make lint

Co-authored-by: Leonard Vimond <leonard.vimond.e@thalesdigital.io>
Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/645
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: leonard.vimond <leonard.vimond@noreply.gitea.com>
Co-committed-by: leonard.vimond <leonard.vimond@noreply.gitea.com>
2024-07-26 16:02:07 +00:00
Renovate Bot
22370698bf fix(deps): update module github.com/charmbracelet/glamour to v0.7.0 (#631)
Reviewed-on: https://gitea.com/gitea/tea/pulls/631
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-07-26 16:01:37 +00:00
Renovate Bot
6d5e8d3b58 chore(deps): update goreleaser/goreleaser-action action to v6 (#654)
Reviewed-on: https://gitea.com/gitea/tea/pulls/654
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-07-20 13:38:07 +00:00
Renovate Bot
18abf24dca chore(deps): update docker/build-push-action action to v6 (#655)
Reviewed-on: https://gitea.com/gitea/tea/pulls/655
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-07-20 13:37:49 +00:00
Renovate Bot
7cb6a84098 chore(deps): update ghcr.io/devcontainers/features/git-lfs docker tag to v1.2.2 (#656)
Reviewed-on: https://gitea.com/gitea/tea/pulls/656
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-07-20 13:37:20 +00:00
techknowlogick
7e700e36f8 update template for issue reporting 2024-07-18 18:11:32 +00:00
Renovate Bot
61bdc14184 fix(deps): update module golang.org/x/crypto to v0.25.0 (#652)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| golang.org/x/crypto | require | minor | `v0.23.0` -> `v0.25.0` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4zODguMCIsInVwZGF0ZWRJblZlciI6IjM3LjQyNC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/652
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-07-17 01:39:18 +00:00
Renovate Bot
16b37cc6da fix(deps): update module golang.org/x/term to v0.22.0 (#657)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| golang.org/x/term | require | minor | `v0.21.0` -> `v0.22.0` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40MjIuNCIsInVwZGF0ZWRJblZlciI6IjM3LjQyMi40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/657
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-07-16 22:57:03 +00:00
Renovate Bot
b5d7119ab4 fix(deps): update module github.com/adrg/xdg to v0.5.0 (#658)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [github.com/adrg/xdg](https://github.com/adrg/xdg) | require | minor | `v0.4.0` -> `v0.5.0` |

---

### Release Notes

<details>
<summary>adrg/xdg (github.com/adrg/xdg)</summary>

### [`v0.5.0`](https://github.com/adrg/xdg/releases/tag/v0.5.0)

[Compare Source](https://github.com/adrg/xdg/compare/v0.4.0...v0.5.0)

##### Changelog

-   `user-dirs.dirs` config file is now parsed on Unix-like operating systems (except for macOS and Plan 9).
    See [XDG user directories](https://github.com/adrg/xdg?tab=readme-ov-file#xdg-user-directories) README section for more details.
-   Updated `golang.org/x/sys` dependency to the latest version.

##### Internal

-   Moved all path related functionality in internal `pathutil` package.
-   Added internal `userdirs` package:
    -   Moved `xdg.UserDirectories` to `userdirs.Directories`.
    -   Added parsing functions for `user-dirs.dirs` config file.
-   Improved package testing.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40MjUuMSIsInVwZGF0ZWRJblZlciI6IjM3LjQyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/658
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-07-16 22:56:25 +00:00
Renovate Bot
610d04fc85 chore(deps): update ghcr.io/devcontainers/features/git-lfs docker tag to v1.2.0 (#647)
Reviewed-on: https://gitea.com/gitea/tea/pulls/647
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-06-05 05:58:22 +00:00
Renovate Bot
6696e945c6 chore(deps): update mcr.microsoft.com/devcontainers/go docker tag to v1.22 (#648)
Reviewed-on: https://gitea.com/gitea/tea/pulls/648
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-06-05 05:57:56 +00:00
Renovate Bot
5158adbcf2 fix(deps): update module golang.org/x/term to v0.21.0 (#653)
Reviewed-on: https://gitea.com/gitea/tea/pulls/653
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-06-05 05:57:34 +00:00
Lunny Xiao
6e473ec601 Update README.md to add actions badge (#651)
Reviewed-on: https://gitea.com/gitea/tea/pulls/651
2024-05-28 06:21:14 +00:00
Renovate Bot
115f25e85f fix(deps): update module github.com/go-git/go-git/v5 to v5.12.0 (#635)
Reviewed-on: https://gitea.com/gitea/tea/pulls/635
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-05-16 05:59:44 +00:00
Renovate Bot
656d48ec8d fix(deps): update module golang.org/x/crypto to v0.23.0 (#650)
Reviewed-on: https://gitea.com/gitea/tea/pulls/650
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-05-07 14:26:56 +00:00
Renovate Bot
2cb450401c fix(deps): update module code.gitea.io/sdk/gitea to v0.18.0 (#646)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| code.gitea.io/sdk/gitea | require | minor | `v0.17.1` -> `v0.18.0` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4zMDEuNiIsInVwZGF0ZWRJblZlciI6IjM3LjMwMS42IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/646
Reviewed-by: appleboy <appleboy.tw@gmail.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-04-19 02:37:59 +00:00
techknowlogick
5236a89d4c Update .gitea/workflows/test-pr.yml 2024-04-08 16:45:15 +00:00
techknowlogick
48d2997a63 Update .gitea/workflows/test-pr.yml 2024-04-08 16:45:05 +00:00
techknowlogick
75a7d4b7cc Update .gitea/workflows/release-tag.yml 2024-04-08 16:43:59 +00:00
techknowlogick
1e3043ce6f Update .gitea/workflows/release-nightly.yml 2024-04-08 16:42:23 +00:00
techknowlogick
218b991069 shorter actions path 2024-04-08 16:41:04 +00:00
techknowlogick
16a3594474 Update .goreleaser.yaml 2024-04-08 16:37:08 +00:00
techknowlogick
b1b31da7c8 Update .goreleaser.yaml 2024-04-08 16:34:15 +00:00
Lunny Xiao
36672485d1 Fix license wrong declare. This project is created by Gitea itself, the license is a copy typo (#640)
Reviewed-on: https://gitea.com/gitea/tea/pulls/640
2024-04-07 03:30:46 +00:00
Renovate Bot
4fedaaafe1 fix(deps): update module golang.org/x/crypto to v0.21.0 (#627)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-03-07 04:04:35 +00:00
Renovate Bot
6ae3718574 fix(deps): update module golang.org/x/term to v0.18.0 (#630)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-03-06 04:38:27 +00:00
Renovate Bot
c4f6231c49 chore(deps): update crazy-max/ghaction-import-gpg action to v6 (#625)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) | action | major | `v5` -> `v6` |

---

### Release Notes

<details>
<summary>crazy-max/ghaction-import-gpg (crazy-max/ghaction-import-gpg)</summary>

### [`v6`](https://github.com/crazy-max/ghaction-import-gpg/compare/v5...v6)

[Compare Source](https://github.com/crazy-max/ghaction-import-gpg/compare/v5...v6)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4yMDIuMCIsInVwZGF0ZWRJblZlciI6IjM3LjIwMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/625
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-02-23 00:38:08 +00:00
Renovate Bot
76f7f901fc chore(deps): update actions/setup-go action to v5 (#624)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/setup-go](https://github.com/actions/setup-go) | action | major | `v4` -> `v5` |

---

### Release Notes

<details>
<summary>actions/setup-go (actions/setup-go)</summary>

### [`v5`](https://github.com/actions/setup-go/compare/v4...v5)

[Compare Source](https://github.com/actions/setup-go/compare/v4...v5)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4yMDIuMCIsInVwZGF0ZWRJblZlciI6IjM3LjIwMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/624
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-02-23 00:36:59 +00:00
Renovate Bot
005beca233 chore(deps): update goreleaser/goreleaser-action action to v5 (#626)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) | action | major | `v4` -> `v5` |

---

### Release Notes

<details>
<summary>goreleaser/goreleaser-action (goreleaser/goreleaser-action)</summary>

### [`v5`](https://github.com/goreleaser/goreleaser-action/compare/v4...v5)

[Compare Source](https://github.com/goreleaser/goreleaser-action/compare/v4...v5)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4yMDIuMCIsInVwZGF0ZWRJblZlciI6IjM3LjIwMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/626
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-02-23 00:35:56 +00:00
Renovate Bot
e62713f42a fix(deps): update module golang.org/x/crypto to v0.19.0 (#622)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-02-19 22:27:48 +00:00
John Olheiser
c8c8e9758b Add OTP and scopes to login (#546)
Resolves #542

Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/546
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: John Olheiser <john+gitea@jolheiser.com>
Co-committed-by: John Olheiser <john+gitea@jolheiser.com>
2024-02-14 16:49:29 +00:00
Renovate Bot
d15af88f83 fix(deps): update module golang.org/x/crypto to v0.18.0 (#618)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| golang.org/x/crypto | require | minor | `v0.17.0` -> `v0.18.0` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4xMjYuMiIsInVwZGF0ZWRJblZlciI6IjM3LjEyNi4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/618
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-01-15 06:58:30 +00:00
Renovate Bot
c74177556b fix(deps): update module code.gitea.io/sdk/gitea to v0.17.1 (#616)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2024-01-08 19:02:29 +00:00
Renovate Bot
02836d8e52 fix(deps): update module code.gitea.io/sdk/gitea to v0.17.0 (#611)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-12-29 04:02:18 +00:00
Konstantin Podsvirov
b8ad55117a Installation on Windows via MSYS2 (#615)
Add link to package description page.

Reviewed-on: https://gitea.com/gitea/tea/pulls/615
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Konstantin Podsvirov <podsvirov@noreply.gitea.com>
Co-committed-by: Konstantin Podsvirov <podsvirov@noreply.gitea.com>
2023-12-29 04:01:14 +00:00
Renovate Bot
fb4eb8be9c fix(deps): update module github.com/go-git/go-git/v5 to v5.11.0 (#606)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-12-26 18:36:37 +00:00
Renovate Bot
81b784d7a5 fix(deps): update module golang.org/x/crypto to v0.17.0 (#607)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| golang.org/x/crypto | require | minor | `v0.15.0` -> `v0.17.0` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy42OC4wIiwidXBkYXRlZEluVmVyIjoiMzcuMTAyLjAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIn0=-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/607
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-12-19 02:13:47 +00:00
Renovate Bot
649b0b1272 fix(deps): update module golang.org/x/crypto to v0.15.0 (#603)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-11-09 04:29:12 +00:00
Renovate Bot
3c9552c531 fix(deps): update module github.com/go-git/go-git/v5 to v5.10.0 (#601)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-10-26 01:54:58 +00:00
techknowlogick
f28ddccd90 add snapcraft file (#600)
Reviewed-on: https://gitea.com/gitea/tea/pulls/600
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-10-23 18:36:38 +00:00
Renovate Bot
5beb5bf56b fix(deps): update module golang.org/x/crypto to v0.14.0 (#596)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-10-06 01:52:57 +00:00
Renovate Bot
17e09a3c2d chore(deps): update docker/setup-buildx-action action to v3 (#594)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-29 15:28:21 +00:00
Renovate Bot
58962cbb65 chore(deps): update docker/setup-qemu-action action to v3 (#595)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-29 15:28:02 +00:00
Renovate Bot
7029e7e8c5 chore(deps): update docker/login-action action to v3 (#593)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-28 14:50:50 +00:00
Renovate Bot
3e8047dfe6 chore(deps): update docker/build-push-action action to v5 (#592)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-28 14:50:35 +00:00
techknowlogick
897dc50bfe build and push nightly docker image 2023-09-27 03:03:09 +00:00
Renovate Bot
2f1ca85b25 chore(deps): update actions/checkout action to v4 (#588)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-25 03:39:40 +00:00
Lunny Xiao
e6a6aa6f49 Update documentation about brew and some other misc (#590)
Reviewed-on: https://gitea.com/gitea/tea/pulls/590
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-09-15 02:12:50 +00:00
Renovate Bot
bcda543bb8 fix(deps): update module github.com/go-git/go-git/v5 to v5.9.0 (#589)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-13 18:52:54 +00:00
Sienna Lloyd
9e5db56dcf added migrate command (#587)
Relates to #22, but adds in all the necessary flags. I tested it manually with some scripting and it works as intended.

Signed-off-by: Sienna Lloyd <sienna@linux.com>

Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/587
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Sienna Lloyd <sienna@linux.com>
Co-committed-by: Sienna Lloyd <sienna@linux.com>
2023-09-11 06:27:48 +00:00
techknowlogick
92d49e383d fix readme badges 2023-09-10 18:48:34 +00:00
techknowlogick
2a666c84cb pie doesn't mesh with scratch image 2023-09-10 00:50:04 -04:00
techknowlogick
32b9f5e836 use chainguard images for docker 2023-09-10 00:35:56 -04:00
techknowlogick
33442fd05f rm outdated code from makefile since goreleaser was added 2023-09-10 04:14:38 +00:00
Renovate Bot
f2cf5b7979 fix(deps): update module github.com/urfave/cli/v2 to v2.25.7 (#584)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-10 03:59:02 +00:00
techknowlogick
d80c8ceffd bump to yaml.v3 2023-09-10 03:47:43 +00:00
Renovate Bot
e3b4d68af7 fix(deps): update module gopkg.in/yaml.v2 to v3 (#585)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) | require | major | `v2.4.0` -> `v3.0.1` |

---

### Release Notes

<details>
<summary>go-yaml/yaml (gopkg.in/yaml.v2)</summary>

### [`v3.0.1`](https://github.com/go-yaml/yaml/compare/v3.0.0...v3.0.1)

[Compare Source](https://github.com/go-yaml/yaml/compare/v3.0.0...v3.0.1)

### [`v3.0.0`](https://github.com/go-yaml/yaml/compare/v2.4.0...v3.0.0)

[Compare Source](https://github.com/go-yaml/yaml/compare/v2.4.0...v3.0.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi43OS4xIiwidXBkYXRlZEluVmVyIjoiMzYuNzkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/585
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-09 03:38:09 +00:00
techknowlogick
0df93dd47a bump sdk dep (#583)
Reviewed-on: https://gitea.com/gitea/tea/pulls/583
Co-authored-by: techknowlogick <hello@techknowlogick.com>
Co-committed-by: techknowlogick <hello@techknowlogick.com>
2023-09-08 15:35:38 +00:00
jolheiser
66b4decdd1 feat: devbox (#572)
This PR adds a [devbox](https://www.jetpack.io/devbox/) config to the repo.
This may be beneficial to anyone interested in nix but doesn't want to deal with the language, or anyone interested in an easy way to start development for `tea`.

With `devbox` installed, `devbox shell` should drop you into a shell with `go 1.20.x`, `gopls` (language server), and `make` for build targets available.
I didn't want to duplicate all of our Makefile, but I did add a single script `devbox run build` because it also has the packages above available, so at the very least a user with `devbox` should be able to build the project.

We don't have CGO, so nothing extra should be required that I can think of, and our linters are invoked via `go run` so they should also work since the `go` package would be available.

-----

As a quick note, I am more familiar with nix itself than I am with devbox, but I think this could potentially help anyone wanting to quickly contribute.

If you want to get really meta and are a nix user, you could even run `nix run nixpkgs#devbox shell` to drop into the shell.

Another alternative would be to write an actual flake with a devShell, however I think `devbox` is a nice in-between. Anyone comfortable with nix can probably whip up a dev environment fairly easily without `devbox`.

Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/572
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-committed-by: jolheiser <john.olheiser@gmail.com>
2023-09-08 15:24:52 +00:00
techknowlogick
9e1919ec0a add devcontainer for tea (#582)
Reviewed-on: https://gitea.com/gitea/tea/pulls/582
Co-authored-by: techknowlogick <hello@techknowlogick.com>
Co-committed-by: techknowlogick <hello@techknowlogick.com>
2023-09-08 15:24:36 +00:00
Renovate Bot
615cd31eb2 fix(deps): update module github.com/muesli/termenv to v0.15.2 (#579)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-08 01:53:03 +00:00
techknowlogick
b868d30434 spdx (#581)
Co-authored-by: techknowlogick <hello@techknowlogick.com>
Co-committed-by: techknowlogick <hello@techknowlogick.com>
2023-09-08 01:40:02 +00:00
Renovate Bot
2d57e07dc7 fix(deps): update module github.com/stretchr/testify to v1.8.4 (#580)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-08 01:06:46 +00:00
Renovate Bot
3343fedc17 fix(deps): update module github.com/alecaivazis/survey/v2 to v2.3.7 (#575)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-08 01:06:28 +00:00
Renovate Bot
f9c67f35a6 fix(deps): update module github.com/charmbracelet/glamour to v0.6.0 (#577)
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-08 01:06:11 +00:00
Renovate Bot
dd8d7fb30f fix(deps): update module code.gitea.io/gitea-vet to v0.2.2 (#574)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| code.gitea.io/gitea-vet | require | patch | `v0.2.1` -> `v0.2.2` |

---

### ⚠ Dependency Lookup Warnings ⚠

Warnings were logged while processing this repo. Please check the Dependency Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi43OS4xIiwidXBkYXRlZEluVmVyIjoiMzYuNzkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/574
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-08 01:01:52 +00:00
Renovate Bot
17453fd7cb fix(deps): update module github.com/go-git/go-git/v5 to v5.8.1 (#578)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) | require | minor | `v5.4.2` -> `v5.8.1` |

---

### ⚠ Dependency Lookup Warnings ⚠

Warnings were logged while processing this repo. Please check the Dependency Dashboard for more information.

---

### Release Notes

<details>
<summary>go-git/go-git (github.com/go-git/go-git/v5)</summary>

### [`v5.8.1`](https://github.com/go-git/go-git/releases/tag/v5.8.1)

[Compare Source](https://github.com/go-git/go-git/compare/v5.8.0...v5.8.1)

#### What's Changed

-   \*: Bump dependencies by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/815

**Full Changelog**: https://github.com/go-git/go-git/compare/v5.8.0...v5.8.1

### [`v5.8.0`](https://github.com/go-git/go-git/releases/tag/v5.8.0)

[Compare Source](https://github.com/go-git/go-git/compare/v5.7.0...v5.8.0)

#### What's Changed

-   git: Fix fetching after shallow clone. Fixes [#&#8203;305](https://github.com/go-git/go-git/issues/305) by [@&#8203;AriehSchneier](https://github.com/AriehSchneier) in https://github.com/go-git/go-git/pull/778
-   git: enable fetch with unqualified references by [@&#8203;AriehSchneier](https://github.com/AriehSchneier) in https://github.com/go-git/go-git/pull/762
-   git: don't add to want if exists, shallow and depth 1 by [@&#8203;AriehSchneier](https://github.com/AriehSchneier) in https://github.com/go-git/go-git/pull/763
-   git: Clone HEAD should not force master. Fixes [#&#8203;363](https://github.com/go-git/go-git/issues/363) by [@&#8203;AriehSchneier](https://github.com/AriehSchneier) in https://github.com/go-git/go-git/pull/758
-   git: fix the issue with submodules having the SCP style URL fail due to the wrong URL parsing by [@&#8203;matejrisek](https://github.com/matejrisek) in https://github.com/go-git/go-git/pull/756
-   git: add a clone option to allow for shallow cloning of submodules by [@&#8203;matejrisek](https://github.com/matejrisek) in https://github.com/go-git/go-git/pull/765
-   worktree: minor speedup for `doAddDirectory` by [@&#8203;ThinkChaos](https://github.com/ThinkChaos) in https://github.com/go-git/go-git/pull/702
-   \_examples: Remove wrong comment by [@&#8203;pascal-hofmann](https://github.com/pascal-hofmann) in https://github.com/go-git/go-git/pull/357
-   \*: Handle paths starting with tilde by [@&#8203;ricci2511](https://github.com/ricci2511) in https://github.com/go-git/go-git/pull/808
-   \*: Handle paths starting with ~Username by [@&#8203;AriehSchneier](https://github.com/AriehSchneier) in https://github.com/go-git/go-git/pull/809
-   storage: filesystem/dotgit, add support for tmp_objdir prefix by [@&#8203;L11R](https://github.com/L11R) in https://github.com/go-git/go-git/pull/812
-   plumbing: gitignore, replace user dir in path by [@&#8203;Jleagle](https://github.com/Jleagle) in https://github.com/go-git/go-git/pull/772
-   plumbing: gitignore, fix incorrect parsing. Fixes [#&#8203;500](https://github.com/go-git/go-git/issues/500) by [@&#8203;AriehSchneier](https://github.com/AriehSchneier) in https://github.com/go-git/go-git/pull/781
-   plumbing: http, Fix empty repos on Git v2.41+ by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/802
-   plumbing: packp, A request is not empty if it contains shallows. Fixes [#&#8203;328](https://github.com/go-git/go-git/issues/328) by [@&#8203;AriehSchneier](https://github.com/AriehSchneier) in https://github.com/go-git/go-git/pull/792
-   plumbing: blame, Complete rewrite. Fixes [#&#8203;603](https://github.com/go-git/go-git/issues/603) by [@&#8203;AriehSchneier](https://github.com/AriehSchneier) in https://github.com/go-git/go-git/pull/789
-   plumbing: gitignore, Allow gitconfig to contain a gitignore relative to any user home. Fixes [#&#8203;578](https://github.com/go-git/go-git/issues/578) by [@&#8203;AriehSchneier](https://github.com/AriehSchneier) in https://github.com/go-git/go-git/pull/785

#### New Contributors

-   [@&#8203;Jleagle](https://github.com/Jleagle) made their first contribution in https://github.com/go-git/go-git/pull/772
-   [@&#8203;pascal-hofmann](https://github.com/pascal-hofmann) made their first contribution in https://github.com/go-git/go-git/pull/357
-   [@&#8203;ricci2511](https://github.com/ricci2511) made their first contribution in https://github.com/go-git/go-git/pull/808
-   [@&#8203;L11R](https://github.com/L11R) made their first contribution in https://github.com/go-git/go-git/pull/812

**Full Changelog**: https://github.com/go-git/go-git/compare/v5.7.0...v5.7.1

### [`v5.7.0`](https://github.com/go-git/go-git/releases/tag/v5.7.0)

[Compare Source](https://github.com/go-git/go-git/compare/v5.6.1...v5.7.0)

#### What's Changed

-   \*: Add support for initializing SHA256 repositories by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/707
-   git: add mirror clone option by [@&#8203;aymanbagabas](https://github.com/aymanbagabas) in https://github.com/go-git/go-git/pull/735
-   git: Add support to ls-remote with peeled references. Fixes [#&#8203;749](https://github.com/go-git/go-git/issues/749) by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/750
-   git: fix cloning with branch name by [@&#8203;AriehSchneier](https://github.com/AriehSchneier) in https://github.com/go-git/go-git/pull/755
-   git: Worktree, add check to see if file already checked in. Fixes [#&#8203;718](https://github.com/go-git/go-git/issues/718) by [@&#8203;cbbm142](https://github.com/cbbm142) in https://github.com/go-git/go-git/pull/719
-   git: Worktree, git grep bare repositories by [@&#8203;aymanbagabas](https://github.com/aymanbagabas) in https://github.com/go-git/go-git/pull/728
-   git: Add Depth to SubmoduleUpdateOptions by [@&#8203;matejrisek](https://github.com/matejrisek) in https://github.com/go-git/go-git/pull/754
-   git: Testing, Fix tests not cleaning temp folders by [@&#8203;AriehSchneier](https://github.com/AriehSchneier) in https://github.com/go-git/go-git/pull/769
-   git: remote, add support for a configurable timeout. by [@&#8203;andrewpollock](https://github.com/andrewpollock) in https://github.com/go-git/go-git/pull/753
-   git: Allow Initial Branch to be configurable by [@&#8203;techknowlogick](https://github.com/techknowlogick) in https://github.com/go-git/go-git/pull/764
-   storage: filesystem/dotgit, Improve load packed-refs by [@&#8203;fcharlie](https://github.com/fcharlie) in https://github.com/go-git/go-git/pull/743
-   storage: filesystem, Populate index before use. Fixes [#&#8203;148](https://github.com/go-git/go-git/issues/148) by [@&#8203;AriehSchneier](https://github.com/AriehSchneier) in https://github.com/go-git/go-git/pull/722
-   plumbing: resolve non-external delta references by [@&#8203;ZauberNerd](https://github.com/ZauberNerd) in https://github.com/go-git/go-git/pull/485
-   plumbing/transport: fix regression in scp-like match by [@&#8203;jotadrilo](https://github.com/jotadrilo) in https://github.com/go-git/go-git/pull/715
-   plumbing/transport: Add support for custom proxy settings by [@&#8203;aryan9600](https://github.com/aryan9600) in https://github.com/go-git/go-git/pull/744
-   \*: small fixes across the codebase by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/770
-   \*: bump github.com/cloudflare/circl from 1.1.0 to 1.3.3 by [@&#8203;dependabot](https://github.com/dependabot) in https://github.com/go-git/go-git/pull/776
-   \*: bump dependencies by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/748
-   \*: bump Go version to 1.18 on go.mod by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/774
-   \*: add Codeql workflow and bump dependencies by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/775
-   ci: fix upstream git build for master branch by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/739

#### New Contributors

-   [@&#8203;ZauberNerd](https://github.com/ZauberNerd) made their first contribution in https://github.com/go-git/go-git/pull/485
-   [@&#8203;jotadrilo](https://github.com/jotadrilo) made their first contribution in https://github.com/go-git/go-git/pull/715
-   [@&#8203;fcharlie](https://github.com/fcharlie) made their first contribution in https://github.com/go-git/go-git/pull/743
-   [@&#8203;AriehSchneier](https://github.com/AriehSchneier) made their first contribution in https://github.com/go-git/go-git/pull/755
-   [@&#8203;cbbm142](https://github.com/cbbm142) made their first contribution in https://github.com/go-git/go-git/pull/719
-   [@&#8203;aryan9600](https://github.com/aryan9600) made their first contribution in https://github.com/go-git/go-git/pull/744
-   [@&#8203;matejrisek](https://github.com/matejrisek) made their first contribution in https://github.com/go-git/go-git/pull/754
-   [@&#8203;andrewpollock](https://github.com/andrewpollock) made their first contribution in https://github.com/go-git/go-git/pull/753
-   [@&#8203;techknowlogick](https://github.com/techknowlogick) made their first contribution in https://github.com/go-git/go-git/pull/764

**Full Changelog**: https://github.com/go-git/go-git/compare/v5.6.1...v5.7.0

### [`v5.6.1`](https://github.com/go-git/go-git/releases/tag/v5.6.1)

[Compare Source](https://github.com/go-git/go-git/compare/v5.6.0...v5.6.1)

#### What's Changed

-   plumbing/transport: don't use the `firstErrLine` when it is empty by [@&#8203;ThinkChaos](https://github.com/ThinkChaos) in https://github.com/go-git/go-git/pull/682
-   plumbing/transport: ssh, unable to pass a custom HostKeyCallback func by [@&#8203;aymanbagabas](https://github.com/aymanbagabas) in https://github.com/go-git/go-git/pull/655
-   storage/filesystem: dotgit: fix a filesystem race in Refs/walkReferencesTree by [@&#8203;MichaelMure](https://github.com/MichaelMure) in https://github.com/go-git/go-git/pull/659
-   \*: bump golang.org/x/net from 0.2.0 to 0.7.0 by [@&#8203;dependabot](https://github.com/dependabot) in https://github.com/go-git/go-git/pull/684
-   \*: bump dependencies by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/697
-   \*: fix panic for empty revisions by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/696
-   ci: bump GitHub actions, enable go test race detection and stop using developer's GPG keys during test execution by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/701

**Full Changelog**: https://github.com/go-git/go-git/compare/v5.6.0...v5.6.1

### [`v5.6.0`](https://github.com/go-git/go-git/releases/tag/v5.6.0)

[Compare Source](https://github.com/go-git/go-git/compare/v5.5.2...v5.6.0)

#### What's Changed

-   Worktree, check for empty parent dirs during Reset (Fixes [#&#8203;670](https://github.com/go-git/go-git/issues/670)) by [@&#8203;mbohy](https://github.com/mbohy) in https://github.com/go-git/go-git/pull/671
-   \*: remove need to build with CGO by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/688
-   plumbing: support SSH/X509 signed tags by [@&#8203;hiddeco](https://github.com/hiddeco) in https://github.com/go-git/go-git/pull/690

**Full Changelog**: https://github.com/go-git/go-git/compare/v5.5.2...v5.6.0

### [`v5.5.2`](https://github.com/go-git/go-git/releases/tag/v5.5.2)

[Compare Source](https://github.com/go-git/go-git/compare/v5.5.1...v5.5.2)

#### What's Changed

-   \*: update go-billy v5.4.0, removes data races. Fixes [#&#8203;629](https://github.com/go-git/go-git/issues/629) by [@&#8203;mcuadros](https://github.com/mcuadros) in https://github.com/go-git/go-git/pull/653
-   Worktree: Add, fix add removed files. Fixes [#&#8203;223](https://github.com/go-git/go-git/issues/223) by [@&#8203;tfujiwar](https://github.com/tfujiwar) in https://github.com/go-git/go-git/pull/652

**Full Changelog**: https://github.com/go-git/go-git/compare/v5.5.1...v5.5.2

### [`v5.5.1`](https://github.com/go-git/go-git/releases/tag/v5.5.1)

[Compare Source](https://github.com/go-git/go-git/compare/v5.5.0...v5.5.1)

#### What's Changed

-   \*: fix error when building with `CGO_ENABLED=0` by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/625
-   plumbing: transport/ssh: fix panic on Windows 10 with paegent as ssh-agent by [@&#8203;doxsch](https://github.com/doxsch) in https://github.com/go-git/go-git/pull/617
-   CommitOptions: AllowEmptyCommits, return an error instead of creating empty commits by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/623

**Full Changelog**: https://github.com/go-git/go-git/compare/v5.5.0...v5.5.1

### [`v5.5.0`](https://github.com/go-git/go-git/releases/tag/v5.5.0)

[Compare Source](https://github.com/go-git/go-git/compare/v5.4.2...v5.5.0)

#### What's Changed

-   \*: add collision resistent SHA1 implementation by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/618
-   \*: replace go-homedir with os.UserHomeDir by [@&#8203;mvdan](https://github.com/mvdan) in https://github.com/go-git/go-git/pull/535
-   Remote: add RemoteURL to {Fetch,Pull,Push}Options by [@&#8203;noerw](https://github.com/noerw) in https://github.com/go-git/go-git/pull/375
-   Remote: Push, add support to push commits per hashes by [@&#8203;tjamet](https://github.com/tjamet) in https://github.com/go-git/go-git/pull/325
-   Remote: Push, add ForceWithLease Push Option by [@&#8203;john-cai](https://github.com/john-cai) in https://github.com/go-git/go-git/pull/404
-   Remote: PushOptions add push-options by [@&#8203;S-Bohn](https://github.com/S-Bohn) in https://github.com/go-git/go-git/pull/399
-   Remote: Push, add atomic to push options by [@&#8203;john-cai](https://github.com/john-cai) in https://github.com/go-git/go-git/pull/406
-   Remote: add FollowTags option for pushes by [@&#8203;john-cai](https://github.com/john-cai) in https://github.com/go-git/go-git/pull/385
-   Worktree: use syscall.Timespec.Unix by [@&#8203;tklauser](https://github.com/tklauser) in https://github.com/go-git/go-git/pull/437
-   Worktree: Checkout, simplified sparse checkout by [@&#8203;john-cai](https://github.com/john-cai) in https://github.com/go-git/go-git/pull/410
-   Repository: don't crash accessing invalid pathinfo by [@&#8203;muesli](https://github.com/muesli) in https://github.com/go-git/go-git/pull/443
-   storage: filesystem, switch from os.SEEK_\* to io.Seek\* by [@&#8203;abhinav](https://github.com/abhinav) in https://github.com/go-git/go-git/pull/421
-   config: add branch description support by [@&#8203;ninedraft](https://github.com/ninedraft) in https://github.com/go-git/go-git/pull/409
-   revision: fix endless looping in revision parser by [@&#8203;michenriksen](https://github.com/michenriksen) in https://github.com/go-git/go-git/pull/475
-   pumbling: optimise zlib reader and consolidate sync.Pools by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/608
-   pumbling: parse optimisations by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/602
-   plumbing: object, rename calculation uses too much memory by [@&#8203;jfontan](https://github.com/jfontan) in https://github.com/go-git/go-git/pull/503
-   plumbing: protocol/pakp and server, include the contents of `GO_GIT_USER_AGENT_EXTRA`. Fixes [#&#8203;529](https://github.com/go-git/go-git/issues/529) by [@&#8203;stewing](https://github.com/stewing) in https://github.com/go-git/go-git/pull/531
-   plumbing: protocol/pakp, avoid duplicate encoding when overriding a Capability value. by [@&#8203;tylerchr](https://github.com/tylerchr) in https://github.com/go-git/go-git/pull/521
-   plumbing: protocol/pakp, update agent by [@&#8203;caarlos0](https://github.com/caarlos0) in https://github.com/go-git/go-git/pull/453
-   plumbing: protocol/pakp: Actions should have type Action by [@&#8203;abhinav](https://github.com/abhinav) in https://github.com/go-git/go-git/pull/420
-   plumbing: protocol/pakp: allow unsupported `multi_ack` capability by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/613
-   plumbing: transport/ssh, auto-populate HostKeyAlgorithms. Fixes [#&#8203;411](https://github.com/go-git/go-git/issues/411) by [@&#8203;evanelias](https://github.com/evanelias) in https://github.com/go-git/go-git/pull/548
-   pumbling: format/packfile, resolve external reference delta by [@&#8203;ga-paul-t](https://github.com/ga-paul-t) in https://github.com/go-git/go-git/pull/392
-   plumbing: format/packfile, prevent large objects from being read into memory completely by [@&#8203;zeripath](https://github.com/zeripath) in https://github.com/go-git/go-git/pull/330
-   plumbing: format/index, support v3 index by [@&#8203;john-cai](https://github.com/john-cai) in https://github.com/go-git/go-git/pull/407
-   plumbing: format/gitignore, Read .git/info/exclude file too. by [@&#8203;enisdenjo](https://github.com/enisdenjo) in https://github.com/go-git/go-git/pull/402
-   plumbing: format/gitattributes, Avoid index out of range  by [@&#8203;To1ne](https://github.com/To1ne) in https://github.com/go-git/go-git/pull/598
-   plumbing: format/config, Branch name with hash can be cloned. Fixes [#&#8203;309](https://github.com/go-git/go-git/issues/309) by [@&#8203;dowy](https://github.com/dowy) in https://github.com/go-git/go-git/pull/354
-   go.mod: update github.com/xanzy/ssh-agent to v0.3.1 by [@&#8203;tklauser](https://github.com/tklauser) in https://github.com/go-git/go-git/pull/403
-   go.mod: update dependencies to remove supply chain CVEs by [@&#8203;pjbgf](https://github.com/pjbgf) in https://github.com/go-git/go-git/pull/620
-   examples: added "tag find if head is tagged" by [@&#8203;snebel29](https://github.com/snebel29) in https://github.com/go-git/go-git/pull/374
-   examples: remote fix typo by [@&#8203;nep-0](https://github.com/nep-0) in https://github.com/go-git/go-git/pull/408

**Full Changelog**: https://github.com/go-git/go-git/compare/v5.4.2...v5.5.0

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi43OS4xIiwidXBkYXRlZEluVmVyIjoiMzYuNzkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/578
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-08 01:01:32 +00:00
Renovate Bot
fe4551142a Configure Renovate (#573)
Welcome to [Renovate](https://github.com/renovatebot/renovate)! This is an onboarding PR to help you understand and configure settings before regular Pull Requests begin.

🚦 To activate Renovate, merge this Pull Request. To disable Renovate, simply close this Pull Request unmerged.

---
### Detected Package Files

 * `Dockerfile` (dockerfile)
 * `go.mod` (gomod)

### Configuration Summary

Based on the default config's presets, Renovate will:

  - Start dependency updates only once this onboarding PR is merged
  - Enable Renovate Dependency Dashboard creation.
  - Use semantic commit type `fix` for dependencies and `chore` for all others if semantic commits are in use.
  - Ignore `node_modules`, `bower_components`, `vendor` and various test/tests directories.
  - Group known monorepo packages together.
  - Use curated list of recommended non-monorepo package groupings.
  - Apply crowd-sourced package replacement rules.
  - Apply crowd-sourced workarounds for known problems with packages.
  - Pin Docker digests.

🔡 Would you like to change the way Renovate is upgrading your dependencies? Simply edit the `renovate.json5` in this branch with your custom config and the list of Pull Requests in the "What to Expect" section below will be updated the next time Renovate runs.

---

### What to Expect

With your current configuration, Renovate will create 12 Pull Requests:

<details>
<summary>chore(deps): pin golang docker tag to 46571ac</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/pin-dependencies`
  - Merge into: `main`
  - Upgrade golang to `sha256:46571ac5f5f793cae20df72cc6f29b155eeba6e6b4377eccc641f84880529986`

</details>

<details>
<summary>fix(deps): update module code.gitea.io/gitea-vet to v0.2.2</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/code.gitea.io-gitea-vet-0.x`
  - Merge into: `main`
  - Upgrade code.gitea.io/gitea-vet to `v0.2.2`

</details>

<details>
<summary>fix(deps): update module github.com/alecaivazis/survey/v2 to v2.3.7</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/github.com-alecaivazis-survey-v2-2.x`
  - Merge into: `main`
  - Upgrade [github.com/AlecAivazis/survey/v2](https://github.com/AlecAivazis/survey) to `v2.3.7`

</details>

<details>
<summary>chore(deps): update golang docker tag to v1.21.1</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/golang-1.x`
  - Merge into: `main`
  - Upgrade golang to `sha256:96634e55b363cb93d39f78fb18aa64abc7f96d372c176660d7b8b6118939d97b`

</details>

<details>
<summary>fix(deps): update module github.com/charmbracelet/glamour to v0.6.0</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/github.com-charmbracelet-glamour-0.x`
  - Merge into: `main`
  - Upgrade [github.com/charmbracelet/glamour](https://github.com/charmbracelet/glamour) to `v0.6.0`

</details>

<details>
<summary>fix(deps): update module github.com/go-git/go-git/v5 to v5.8.1</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/github.com-go-git-go-git-v5-5.x`
  - Merge into: `main`
  - Upgrade [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) to `v5.8.1`

</details>

<details>
<summary>fix(deps): update module github.com/muesli/termenv to v0.15.2</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/github.com-muesli-termenv-0.x`
  - Merge into: `main`
  - Upgrade [github.com/muesli/termenv](https://github.com/muesli/termenv) to `v0.15.2`

</details>

<details>
<summary>fix(deps): update module github.com/stretchr/testify to v1.8.4</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/github.com-stretchr-testify-1.x`
  - Merge into: `main`
  - Upgrade [github.com/stretchr/testify](https://github.com/stretchr/testify) to `v1.8.4`

</details>

<details>
<summary>fix(deps): update module github.com/urfave/cli/v2 to v2.25.7</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/github.com-urfave-cli-v2-2.x`
  - Merge into: `main`
  - Upgrade [github.com/urfave/cli/v2](https://github.com/urfave/cli) to `v2.25.7`

</details>

<details>
<summary>fix(deps): update module golang.org/x/crypto to v0.13.0</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/golang.org-x-crypto-0.x`
  - Merge into: `main`
  - Upgrade golang.org/x/crypto to `0d375be9b61cb69eb94173d0375a05e90875bbf6`

</details>

<details>
<summary>fix(deps): update module golang.org/x/term to v0.12.0</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/golang.org-x-term-0.x`
  - Merge into: `main`
  - Upgrade golang.org/x/term to `f413282cd8dbb55102093d9f16ab3ba90f7b9b31`

</details>

<details>
<summary>fix(deps): update module gopkg.in/yaml.v2 to v3</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/gopkg.in-yaml.v2-3.x`
  - Merge into: `main`
  - Upgrade [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) to `v3.0.1`

</details>

<br />

🚸 Branch creation will be limited to maximum 2 per hour, so it doesn't swamp any CI resources or overwhelm the project. See docs for `prhourlylimit` for details.

---

### ⚠ Dependency Lookup Warnings ⚠

Please correct - or verify that you can safely ignore - these lookup failures before you merge this PR.

-   `Could not determine new digest for update (datasource: go)`

Files affected: `go.mod`

---

 Got questions? Check out Renovate's [Docs](https://docs.renovatebot.com/), particularly the Getting Started section.
If you need any further assistance then you can also [request help here](https://github.com/renovatebot/renovate/discussions).

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).

<!--renovate-config-hash:b0e8c3aba9246cf9ec209c46e593f123f2daeea739622a97656e285413290a78-->

Reviewed-on: https://gitea.com/gitea/tea/pulls/573
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
Co-committed-by: Renovate Bot <renovate-bot@gitea.com>
2023-09-07 23:08:07 +00:00
mvaldes14
10bd00d066 adding installer option with asdf (#563)
created a plugin to install tea via asdf - hoping someone else can find it useful.

Co-authored-by: mvaldes <mvaldes.revilla@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/563
Co-authored-by: mvaldes14 <mvaldes14@noreply.gitea.com>
Co-committed-by: mvaldes14 <mvaldes14@noreply.gitea.com>
2023-09-01 15:34:42 +00:00
jolheiser
0869c15a6c Initial CLI docs (#565)
As title, then we could potentially start ingesting these in the docs repo as they are expanded.

Reviewed-on: https://gitea.com/gitea/tea/pulls/565
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-committed-by: jolheiser <john.olheiser@gmail.com>
2023-09-01 15:34:13 +00:00
Martin Veldwijk
e4e8eb07d2 Fix for go tools called from make (#568)
Fixes #567 by offloading the `$PATH` interpolation to the shell instead of letting make handle it.
Tested and works on Linux, tested and works on Windows using MSYS2/MingW64. Not tested on a pure Windows env without translation layers, but nothing was changed that would actively break it either.

Reviewed-on: https://gitea.com/gitea/tea/pulls/568
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Martin Veldwijk <riven@tae.moe>
Co-committed-by: Martin Veldwijk <riven@tae.moe>
2023-08-21 22:01:15 +00:00
John Olheiser
620f236723 Update issue template for dl.gitea.com
Signed-off-by: John Olheiser <john+gitea@jolheiser.com>
2023-08-01 14:53:10 +00:00
Martin Veldwijk
77837e909e Fix Makefile for Windows builds (#566)
Fixes #564 (/gitea/tea/issues/564) by applying minor changes to the Makefile.
- Defines a `BINEXT` variable which holds a file name extension (.exe for Windows, empty otherwise)
- Replaces uses of `$(EXECUTABLE)` with `tea$(EXECUTABLE)`
- appends `$(BINEXT)` to the custom vet tool flag

Besides that I made two minor changes
- Add `[.exe]` to the ignore rule for gitea-vet, so it also ignores the binary when built on Windows hosts
- Removed `$(DIST)/binaries` from release-dirs as this is not being called anywhere else

I've tested almost all `make` tasks to work on Windows. The only one I haven't run is the docker build.

WIP tag is because I still have to test it on Linux, will do so in a bit. I also asked for some discussion on the original issue, which I'd like to do before blindly asking to merge in changes

Reviewed-on: https://gitea.com/gitea/tea/pulls/566
Co-authored-by: Martin Veldwijk <riven@tae.moe>
Co-committed-by: Martin Veldwijk <riven@tae.moe>
2023-07-28 14:03:26 +00:00
6543
5582f92df4 fix interactive login add (#557)
if you just hit "enter" the loginMethod is `` so this coves that case

Reviewed-on: https://gitea.com/gitea/tea/pulls/557
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2023-06-07 16:47:52 +00:00
vsysoev
c72c6c0679 issues list can show filtered by owner/org instead of repo too (#550)
close #551

Co-authored-by: Sysoev, Vladimir <vladimir.sysoev@sarov-itc.ru>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/550
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: vsysoev <vsysoev@noreply.gitea.com>
Co-committed-by: vsysoev <vsysoev@noreply.gitea.com>
2023-06-06 13:25:36 +00:00
Grady Wong
b2d845b8c7 fix: non-standard ssh port URL's repo can't be recognized (#555)
Resolve #452

Reviewed-on: https://gitea.com/gitea/tea/pulls/555
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Grady Wong <wgqimut@gmail.com>
Co-committed-by: Grady Wong <wgqimut@gmail.com>
2023-06-06 13:14:21 +00:00
Bo-Yi Wu
07039b82e3 ci: improve release process and test coverage (#553)
- Add extra_files configuration to .goreleaser.yaml
- Include .xz and .xz.sha256 files in the release

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/tea/pulls/553
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.io>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2023-05-04 11:58:15 +08:00
appleboy
7040c350ae build: optimize compression method and clean up environment variables (#552)
- Replace tar command with xz command for compression
- Remove redundant environment variable XZ_OPT

ref: https://gitea.com/gitea/homebrew-gitea/pulls/164

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/tea/pulls/552
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.io>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2023-05-01 21:59:26 +08:00
appleboy
b02263adb0 refactor: improve code quality and efficiency in various files (#548)
- Replace loadConfig() with _ = loadConfig()
- Update file permissions from 0660 to 0o660
- Simplify variable declarations
- Replace golang.org/x/crypto/ssh/terminal with golang.org/x/term
- Remove unused getCertPrincipals function
- Replace time.Now().Sub() with time.Since()
- Add test for ArgToIndex function

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/548
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.io>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2023-04-30 11:43:26 +08:00
appleboy
4915862b95 refactor: optimize PR title generation and expand test coverage (#549)
- Import "regexp" package in pull_create.go
- Replace existing code for `GetDefaultPRTitle` with a more efficient regular expression approach
- Add a new test file pull_create_test.go with a test function for `GetDefaultPRTitle`

Signed-off-by: appleboy <appleboy.tw@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/549
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.io>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2023-04-30 00:16:18 +08:00
appleboy
cd6a7b35c3 ci: improve build process and test coverage (#547)
- Add `dir: ./dist/` to the builds section in `.goreleaser.yaml`

ref: https://gitea.com/gitea/act_runner/issues/158

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/tea/pulls/547
Reviewed-by: Jason Song <i@wolfogre.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2023-04-29 08:29:24 +08:00
ASDFGamer
abcaa58312 Improve Compilation Section in Readme (#543)
This PR adds two small improvements to the compilation Section of the Readme:

- Add go/The Go Programming Language to the list of required tools in the compilation section of the readme.
- Add a short explanation how the compiled source can be installed via make install, with a note about the GOPATH

Co-authored-by: Christoph Wildhagen <git@christoph-wildhagen.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/543
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.io>
Co-authored-by: ASDFGamer <asdfgamer@noreply.gitea.io>
Co-committed-by: ASDFGamer <asdfgamer@noreply.gitea.io>
2023-04-19 03:06:00 +08:00
ASDFGamer
d9da952e96 Output full path to go bin directory during installation (#544)
This fixed a Bug, that a wrong directory could be written to shell via
@echo, when the env var GOPATH isn't set.

Co-authored-by: Christoph Wildhagen <git@christoph-wildhagen.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/544
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.io>
Co-authored-by: ASDFGamer <asdfgamer@noreply.gitea.io>
Co-committed-by: ASDFGamer <asdfgamer@noreply.gitea.io>
2023-04-18 05:08:50 +08:00
Lunny Xiao
7f335a2b9c Update '.gitea/workflows/release-tag.yml' (#541)
Reviewed-on: https://gitea.com/gitea/tea/pulls/541
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-04-13 22:54:46 +08:00
6543
b510756438 If gitea version is not detected, just fall back to last supported one and print warning (#538)
close #531

Reviewed-on: https://gitea.com/gitea/tea/pulls/538
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-04-03 13:49:33 +08:00
techknowlogick
3640bbac5e gpg fingerprint 2023-03-29 22:44:01 -04:00
techknowlogick
b705188599 Switch CI to Actions and use Goreleaser (#536)
Reviewed-on: https://gitea.com/gitea/tea/pulls/536
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-committed-by: techknowlogick <techknowlogick@gitea.io>
2023-03-30 10:24:03 +08:00
6543
cfce25f0a7 followup to: use secrets for s3 info (#530) 2023-02-16 00:14:31 +01:00
6543
e783f4ea4a Changelog for v0.9.1 (#535)
Reviewed-on: https://gitea.com/gitea/tea/pulls/535
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
2023-02-15 23:23:12 +01:00
John Olheiser
342b573d45 Remove vendor docs (#534)
As title, we no longer use vendoring (or dep!).

Also updated the link to Discord.

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/gitea/tea/pulls/534
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: John Olheiser <john+gitea@jolheiser.com>
Co-committed-by: John Olheiser <john+gitea@jolheiser.com>
2023-02-09 12:41:43 +08:00
Lunny Xiao
1a6d9b343a Fix makefile vendor problem (#533)
Replace #532

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-on: https://gitea.com/gitea/tea/pulls/533
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-09 11:05:39 +08:00
techknowlogick
faa9858a7e use secrets for s3 info (#530)
Reviewed-on: https://gitea.com/gitea/tea/pulls/530
2023-02-08 05:53:05 +08:00
techknowlogick
e51eb1a00e bump to go 1.20 (#529)
Reviewed-on: https://gitea.com/gitea/tea/pulls/529
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-08 05:46:31 +08:00
Wim
6a848cb72a Add option to disable version checking (#520)
Fixes "Only signed in user is allowed to call APIs." as the
/api/v1/version returns a 403 when running a gitea where
REQUIRE_SIGNIN_VIEW is enabled

Co-authored-by: Wim <wim@42.be>
Reviewed-on: https://gitea.com/gitea/tea/pulls/520
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Wim <42wim@noreply.gitea.io>
Co-committed-by: Wim <42wim@noreply.gitea.io>
2022-10-31 09:56:23 +08:00
Norwin
2a8c1daa67 Add tea issue edit (#506)
fixes #229
fixes #502

interactive mode will be in a follow up

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/506
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-10-25 08:40:00 +08:00
6543
54b535a527 Find DefaultPRHead based on branch and SHA (#514)
Reviewed-on: https://gitea.com/gitea/tea/pulls/514
Reviewed-by: strk <strk@noreply.gitea.io>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
2022-10-25 06:38:39 +08:00
6543
c1c7870ceb Handle Team Reviews (#515)
at the moment we crash with an nil exeption if there exist team reviews

this fix it and add support to display them

Reviewed-on: https://gitea.com/gitea/tea/pulls/515
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: wxiaoguang <wxiaoguang@noreply.gitea.io>
2022-09-29 10:49:24 +08:00
6543
a37377d181 Add "json" as output type (#513)
Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/513
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: strk <strk@noreply.gitea.io>
2022-09-27 23:39:47 +08:00
6543
15457f1770 Add Allow Maintainer Edits (#509)
close #508

Reviewed-on: https://gitea.com/gitea/tea/pulls/509
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: strk <strk@noreply.gitea.io>
2022-09-27 23:36:36 +08:00
Norwin
4487213581 Allow batch operations on multiple entities (#512)
commands now accept multiple arguments where it makes sense.

#### before
```
NAME:
   tea issues close - Change state of an issue to 'closed'

USAGE:
   tea issues close [command options] <issue index>
```

#### after
```
NAME:
   tea issues close - Change state of one ore more issues to 'closed'

USAGE:
   tea issues close [command options] <issue index> [<issue index>...]
```

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/tea/pulls/512
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: justusbunsi <justusbunsi@noreply.gitea.io>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-09-27 04:35:59 +08:00
Wim
6a4ba6a689 Add support for authentication via ssh certificates and pub/privatekey (#442)
This adds support for authentication using a SSH certificate and normal public keys when you've got an ssh-agent running that has this certificate or your public key loaded.

First question when creating a new login is to ask about the ssh certificates or public keys, when the answer is yes, we don't need to ask about tokens/usernames anymore.

Co-authored-by: Wim <wim@42.be>
Reviewed-on: https://gitea.com/gitea/tea/pulls/442
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Wim <42wim@noreply.gitea.io>
Co-committed-by: Wim <42wim@noreply.gitea.io>
2022-09-15 03:00:08 +08:00
6543
4ee5ce4b52 Fix "go install" example (#505)
because some wrong tagging was done in the past, `latest` will point to `1.3.3` witch is actually v0.1.x.

so you have to specify the version

Reviewed-on: https://gitea.com/gitea/tea/pulls/505
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2022-09-14 09:08:41 +08:00
259 changed files with 19375 additions and 2655 deletions

View File

@@ -0,0 +1,20 @@
{
"name": "Tea DevContainer",
"image": "mcr.microsoft.com/devcontainers/go:2.1-trixie",
"features": {
"ghcr.io/devcontainers/features/git-lfs:1.2.5": {}
},
"customizations": {
"vscode": {
"settings": {},
"extensions": [
"editorconfig.editorconfig",
"golang.go",
"stylelint.vscode-stylelint",
"DavidAnson.vscode-markdownlint",
"ms-azuretools.vscode-docker",
"GitHub.vscode-pull-request-github"
]
}
}
}

View File

@@ -1,219 +0,0 @@
---
kind: pipeline
name: default
platform:
os: linux
arch: amd64
steps:
- name: vendor
pull: always
image: golang:1.18
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
commands:
- make vendor # use vendor folder as cache
- name: build
pull: always
image: golang:1.18
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
commands:
- make clean
- make vet
- make lint
- make fmt-check
- make misspell-check
- make build
when:
event:
- push
- tag
- pull_request
- name: unit-test
image: golang:1.18
commands:
- make unit-test-coverage
settings:
group: test
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
when:
branch:
- main
event:
- push
- pull_request
- name: release-test
image: golang:1.18
commands:
- make test
settings:
group: test
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
when:
branch:
- "release/*"
event:
- push
- pull_request
- name: tag-test
pull: always
image: golang:1.18
commands:
- make test
settings:
group: test
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
when:
event:
- tag
- name: static
image: golang:1.18
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
commands:
- make release
when:
event:
- push
- tag
- name: gpg-sign
pull: always
image: plugins/gpgsign:1
settings:
detach_sign: true
excludes:
- "dist/release/*.sha256"
files:
- "dist/release/*"
environment:
GPGSIGN_KEY:
from_secret: gpgsign_key
GPGSIGN_PASSPHRASE:
from_secret: gpgsign_passphrase
when:
event:
- push
- tag
- name: tag-release
pull: always
image: woodpeckerci/plugin-s3:latest
settings:
acl:
from_secret: aws_s3_acl
region:
from_secret: aws_s3_region
bucket:
from_secret: aws_s3_bucket
endpoint:
from_secret: aws_s3_endpoint
path_style:
from_secret: aws_s3_path_style
source: "dist/release/*"
strip_prefix: dist/release/
target: "/tea/${DRONE_TAG##v}"
environment:
AWS_ACCESS_KEY_ID:
from_secret: aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_access_key
when:
event:
- tag
- name: release-branch-release
pull: always
image: woodpeckerci/plugin-s3:latest
settings:
acl:
from_secret: aws_s3_acl
region:
from_secret: aws_s3_region
bucket:
from_secret: aws_s3_bucket
endpoint:
from_secret: aws_s3_endpoint
path_style:
from_secret: aws_s3_path_style
source: "dist/release/*"
strip_prefix: dist/release/
target: "/tea/${DRONE_BRANCH##release/v}"
environment:
AWS_ACCESS_KEY_ID:
from_secret: aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_access_key
when:
branch:
- "release/*"
event:
- push
- name: release
pull: always
image: woodpeckerci/plugin-s3:latest
settings:
acl:
from_secret: aws_s3_acl
region:
from_secret: aws_s3_region
bucket:
from_secret: aws_s3_bucket
endpoint:
from_secret: aws_s3_endpoint
path_style:
from_secret: aws_s3_path_style
source: "dist/release/*"
strip_prefix: dist/release/
target: /tea/main
environment:
AWS_ACCESS_KEY_ID:
from_secret: aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_access_key
when:
branch:
- main
event:
- push
- name: gitea
pull: always
image: plugins/gitea-release:1
settings:
files:
- "dist/release/*"
base_url: https://gitea.com
api_key:
from_secret: gitea_token
when:
event:
- tag
- name: discord
pull: always
image: appleboy/drone-discord:1.0.0
environment:
DISCORD_WEBHOOK_ID:
from_secret: discord_webhook_id
DISCORD_WEBHOOK_TOKEN:
from_secret: discord_webhook_token
when:
event:
- push
- tag
- pull_request
status:
- changed
- failure

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

View File

@@ -8,7 +8,7 @@ labels:
### describe your environment ### describe your environment
- tea version used (`tea -v`): - tea version used (`tea -v`):
- [ ] I also reproduced the issue [with the latest master build](https://dl.gitea.io/tea/master) - [ ] I also reproduced the issue [with the latest main build](https://dl.gitea.com/tea/main/)
- Gitea version used: - Gitea version used:
- [ ] the issue only occurred after updating gitea recently - [ ] the issue only occurred after updating gitea recently
- operating system: - operating system:

View File

@@ -0,0 +1,80 @@
name: goreleaser
on:
push:
branches: [ main ]
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- run: git fetch --force --tags
- uses: actions/setup-go@v6
with:
go-version-file: "go.mod"
- name: import gpg
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v7
with:
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
- name: get SDK version
id: sdk_version
run: echo "version=$(go list -f '{{.Version}}' -m code.gitea.io/sdk/gitea)" >> "$GITHUB_OUTPUT"
- name: goreleaser
uses: goreleaser/goreleaser-action@v7
with:
distribution: goreleaser-pro
version: "~> v1"
args: release --nightly
env:
SDK_VERSION: ${{ steps.sdk_version.outputs.version }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
S3_REGION: ${{ secrets.AWS_REGION }}
S3_BUCKET: ${{ secrets.AWS_BUCKET }}
GORELEASER_FORCE_TOKEN: 'gitea'
GPGSIGN_PASSPHRASE: ${{ secrets.GPGSIGN_PASSPHRASE }}
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
release-image:
runs-on: ubuntu-latest
env:
DOCKER_ORG: gitea
DOCKER_LATEST: nightly
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0 # all history for all branches and tags
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v4
- name: Login to DockerHub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v7
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with:
context: .
file: ./Dockerfile
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
gitea/tea:latest

View File

@@ -0,0 +1,85 @@
name: goreleaser
on:
push:
tags:
- '*'
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- run: git fetch --force --tags
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: import gpg
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v7
with:
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
- name: get SDK version
id: sdk_version
run: echo "version=$(go list -f '{{.Version}}' -m code.gitea.io/sdk/gitea)" >> "$GITHUB_OUTPUT"
- name: goreleaser
uses: goreleaser/goreleaser-action@v7
with:
distribution: goreleaser-pro
version: "~> v1"
args: release
env:
SDK_VERSION: ${{ steps.sdk_version.outputs.version }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
S3_REGION: ${{ secrets.AWS_REGION }}
S3_BUCKET: ${{ secrets.AWS_BUCKET }}
GORELEASER_FORCE_TOKEN: 'gitea'
GPGSIGN_PASSPHRASE: ${{ secrets.GPGSIGN_PASSPHRASE }}
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
release-image:
runs-on: ubuntu-latest
env:
DOCKER_ORG: gitea
DOCKER_LATEST: nightly
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0 # all history for all branches and tags
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v4
- name: Login to DockerHub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Get tag version without v
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Build and push
uses: docker/build-push-action@v7
env:
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
with:
context: .
file: ./Dockerfile
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
gitea/tea:${{ env.VERSION }}

View File

@@ -0,0 +1,63 @@
name: check-and-test
on:
- pull_request
jobs:
#govulncheck_job:
# runs-on: ubuntu-latest
# name: Run govulncheck
# steps:
# - id: govulncheck
# uses: golang/govulncheck-action@v1
# with:
# go-version-file: 'go.mod'
check-and-test:
runs-on: ubuntu-latest
env:
HTTP_PROXY: ""
GITEA_TEA_TEST_URL: "http://gitea:3000"
GITEA_TEA_TEST_USERNAME: "test01"
GITEA_TEA_TEST_PASSWORD: "test01"
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: lint and build
run: |
make clean
make vet
make lint
make fmt-check
make docs-check
make build
- run: curl --noproxy "*" http://gitea:3000/api/v1/version # verify connection to instance
- name: test and coverage
run: |
make test
make unit-test-coverage
services:
gitea:
image: docker.gitea.com/gitea:1.25.5
cmd:
- bash
- -c
- >-
mkdir -p /tmp/conf/
&& mkdir -p /tmp/data/
&& echo "I_AM_BEING_UNSAFE_RUNNING_AS_ROOT = true" > /tmp/conf/app.ini
&& echo "[security]" >> /tmp/conf/app.ini
&& echo "INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4" >> /tmp/conf/app.ini
&& echo "INSTALL_LOCK = true" >> /tmp/conf/app.ini
&& echo "SECRET_KEY = 2crAW4UANgvLipDS6U5obRcFosjSJHQANll6MNfX7P0G3se3fKcCwwK3szPyGcbo" >> /tmp/conf/app.ini
&& echo "PASSWORD_COMPLEXITY = off" >> /tmp/conf/app.ini
&& echo "[database]" >> /tmp/conf/app.ini
&& echo "DB_TYPE = sqlite3" >> /tmp/conf/app.ini
&& echo "[repository]" >> /tmp/conf/app.ini
&& echo "ROOT = /tmp/data/" >> /tmp/conf/app.ini
&& echo "[server]" >> /tmp/conf/app.ini
&& echo "ROOT_URL = http://gitea:3000" >> /tmp/conf/app.ini
&& gitea migrate -c /tmp/conf/app.ini
&& gitea admin user create --username=test01 --password=test01 --email=test01@gitea.io --admin=true --must-change-password=false --access-token -c /tmp/conf/app.ini
&& gitea web -c /tmp/conf/app.ini

12
.gitignore vendored
View File

@@ -1,5 +1,6 @@
tea /tea
/gitea-vet /gitea-vet
/gitea-vet.exe
.idea/ .idea/
.history/ .history/
@@ -7,3 +8,12 @@ dist/
.vscode/ .vscode/
vendor/ vendor/
coverage.out
dist/
# Nix-specific
.direnv/
result
result-*

45
.golangci.yml Normal file
View File

@@ -0,0 +1,45 @@
version: "2"
formatters:
enable:
- gofumpt
linters:
default: none
enable:
- govet
- revive
- misspell
- ineffassign
- unused
settings:
revive:
rules:
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: error-return
- name: error-strings
- name: error-naming
- name: exported
- name: if-return
- name: increment-decrement
- name: var-declaration
- name: range
- name: receiver-naming
- name: time-naming
- name: unexported-return
- name: indent-error-flow
- name: errorf
misspell:
locale: US
ignore-words:
- unknwon
- destory
issues:
max-issues-per-linter: 0
max-same-issues: 0

12
.goreleaser.checksum.sh Normal file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
set -e
if [ -z "$1" ]; then
echo "usage: $0 <path>"
exit 1
fi
SUM=$(shasum -a 256 "$1" | cut -d' ' -f1)
BASENAME=$(basename "$1")
echo -n "${SUM} ${BASENAME}" > "$1".sha256

122
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,122 @@
before:
hooks:
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
goos:
- darwin
- linux
- windows
- freebsd
goarch:
- amd64
- arm
- arm64
goarm:
- "5"
- "6"
- "7"
ignore:
- goos: darwin
goarch: arm
- goos: darwin
goarch: ppc64le
- goos: darwin
goarch: s390x
- goos: windows
goarch: ppc64le
- goos: windows
goarch: s390x
- goos: windows
goarch: arm
goarm: "5"
- goos: windows
goarch: arm
goarm: "6"
- goos: windows
goarch: arm
goarm: "7"
- goos: freebsd
goarch: ppc64le
- goos: freebsd
goarch: s390x
- goos: freebsd
goarch: arm
goarm: "5"
- goos: freebsd
goarch: arm
goarm: "6"
- goos: freebsd
goarch: arm
goarm: "7"
- goos: freebsd
goarch: arm64
flags:
- -trimpath
ldflags:
- -s -w -X "code.gitea.io/tea/modules/version.Version={{ trimprefix .Summary "v" }}" -X "code.gitea.io/tea/modules/version.Tags=" -X "code.gitea.io/tea/modules/version.SDK={{ .Env.SDK_VERSION }}"
binary: >-
{{ .ProjectName }}-
{{- .Version }}-
{{- .Os }}-
{{- if eq .Arch "amd64" }}amd64
{{- else if eq .Arch "amd64_v1" }}amd64
{{- else if eq .Arch "386" }}386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}-{{ .Arm }}{{ end }}
no_unique_dist_dir: true
hooks:
post:
- cmd: xz -k -9 {{ .Path }}
dir: ./dist/
- cmd: sh .goreleaser.checksum.sh {{ .Path }}
- cmd: sh .goreleaser.checksum.sh {{ .Path }}.xz
blobs:
-
provider: s3
bucket: "{{ .Env.S3_BUCKET }}"
region: "{{ .Env.S3_REGION }}"
folder: "tea/{{.Version}}"
extra_files:
- glob: ./**.xz
- glob: ./**.sha256
archives:
- format: binary
name_template: "{{ .Binary }}"
allow_different_binary_count: true
checksum:
name_template: 'checksums.txt'
extra_files:
- glob: ./**.xz
force_token: gitea
signs:
-
signature: "${artifact}.sig"
artifacts: checksum
stdin: '{{ .Env.GPGSIGN_PASSPHRASE }}'
args: ["--batch", "-u", "{{ .Env.GPG_FINGERPRINT }}", "--output", "${signature}", "--detach-sign", "${artifact}"]
snapshot:
name_template: "{{ .Branch }}-devel"
nightly:
name_template: "{{ .Branch }}"
gitea_urls:
api: https://gitea.com/api/v1
download: https://gitea.com
release:
extra_files:
- glob: ./**.xz
- glob: ./**.xz.sha256
# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj

View File

@@ -1,5 +1,20 @@
# Changelog # Changelog
## [v0.13.0](https://gitea.com/gitea/tea/releases/tag/v0.13.0) - 2026-04-05
* FEATURES
* Add `tea pr edit` subcommand for pull requests (#944)
* Add `tea repo edit` subcommand (#928)
* Support owner-based repository listing in `tea repo ls` (#931)
* Store OAuth tokens in OS keyring via credstore (#926)
* Support parsing multiple values in `tea api` subcommand (#911)
* ENHANCEMENTS
* Replace log.Fatal/os.Exit with proper error returns (#941)
* Update to charm libraries v2 (#923)
* MISC
* Bump Go version to 1.26
* Update dependencies: go-git/v5 v5.17.2, gitea SDK v0.24.1, urfave/cli/v3 v3.8.0, oauth2 v0.36.0, tablewriter v1.1.4, go-authgate/sdk-go v0.6.1
## [v0.9.1](https://gitea.com/gitea/tea/releases/tag/v0.9.1) - 2023-02-15 ## [v0.9.1](https://gitea.com/gitea/tea/releases/tag/v0.9.1) - 2023-02-15
* BUGFIXES * BUGFIXES

View File

@@ -7,7 +7,6 @@
- [Bug reports](#bug-reports) - [Bug reports](#bug-reports)
- [Discuss your design](#discuss-your-design) - [Discuss your design](#discuss-your-design)
- [Testing redux](#testing-redux) - [Testing redux](#testing-redux)
- [Vendoring](#vendoring)
- [Code review](#code-review) - [Code review](#code-review)
- [Styleguide](#styleguide) - [Styleguide](#styleguide)
- [Sign-off your work](#sign-off-your-work) - [Sign-off your work](#sign-off-your-work)
@@ -60,20 +59,6 @@ high-level discussions.
Before sending code out for review, run all the test by executing: `make test` Before sending code out for review, run all the test by executing: `make test`
Since TEA is an cli tool it should be obvious to test your feature locally first. Since TEA is an cli tool it should be obvious to test your feature locally first.
## 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).
## Code review ## Code review
Changes to TEA must be reviewed before they are accepted—no matter who Changes to TEA must be reviewed before they are accepted—no matter who
@@ -175,7 +160,7 @@ maintainers](MAINTAINERS). Every PR **MUST** be reviewed by at least
two maintainers (or owners) before it can get merged. A maintainer two maintainers (or owners) before it can get merged. A maintainer
should be a contributor of Gitea (or Gogs) and contributed at least should be a contributor of Gitea (or Gogs) and contributed at least
4 accepted PRs. A contributor should apply as a maintainer in the 4 accepted PRs. A contributor should apply as a maintainer in the
[Discord](https://discord.gg/NsatcWJ) #develop channel. The owners [Discord](https://discord.gg/Gitea) #develop channel. The owners
or the team maintainers may invite the contributor. A maintainer or the team maintainers may invite the contributor. A maintainer
should spend some time on code reviews. If a maintainer has no should spend some time on code reviews. If a maintainer has no
time to do that, they should apply to leave the maintainers team time to do that, they should apply to leave the maintainers team
@@ -208,7 +193,7 @@ https://help.github.com/articles/securing-your-account-with-two-factor-authentic
After the election, the new owners should proactively agree After the election, the new owners should proactively agree
with our [CONTRIBUTING](CONTRIBUTING.md) requirements in the with our [CONTRIBUTING](CONTRIBUTING.md) requirements in the
[Discord](https://discord.gg/NsatcWJ) #general channel. Below are the [Discord](https://discord.gg/Gitea) #general channel. Below are the
words to speak: words to speak:
``` ```
@@ -236,8 +221,8 @@ Code that you contribute should use the standard copyright header:
``` ```
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
``` ```
Files in the repository contain copyright from the year they are added Files in the repository contain copyright from the year they are added

View File

@@ -1,25 +1,12 @@
ARG GOVERSION="1.16.2" FROM docker.io/chainguard/go:latest AS build
COPY . /build/
WORKDIR /build
RUN make build && mkdir -p /app/.config/tea
FROM golang:${GOVERSION}-alpine AS buildenv FROM docker.io/chainguard/busybox:latest-glibc
COPY --from=build /build/tea /bin/tea
ARG GOOS="linux" COPY --from=build --chown=65532:65532 /app /app
VOLUME [ "/app" ]
COPY . $GOPATH/src/
WORKDIR $GOPATH/src
RUN apk add --quiet --no-cache \
build-base \
make \
git && \
make clean build STATIC=true
FROM scratch
ARG VERSION="0.7.0"
LABEL org.opencontainers.image.title="tea - CLI for Gitea - git with a cup of tea"
LABEL org.opencontainers.image.description="A command line tool to interact with Gitea servers"
LABEL org.opencontainers.image.version="${VERSION}"
LABEL org.opencontainers.image.authors="Tamás Gérczei <tamas@gerczei.eu>"
LABEL org.opencontainers.image.vendor="The Gitea Authors"
COPY --from=buildenv /go/src/tea /
ENV HOME="/app" ENV HOME="/app"
ENTRYPOINT ["/tea"] ENTRYPOINT ["/bin/sh", "-c"]
CMD [ "tea" ]

View File

@@ -1,63 +0,0 @@
# comparing git forge commandline interfaces
[tea]: https://gitea.com/gitea/tea
[sip]: https://gitea.com/jolheiser/sip
[gitlab]: https://github.com/makkes/gitlab-cli
[glab]: https://github.com/profclems/glab
[gh]: https://cli.github.com
last update: 2020-12-11
## general
/ | [tea][tea] | [sip][sip] | [gitlab][gitlab] | [gh][gh]
-----------------------|:-----:|:-----:|:-----:|:-----:
forge|gitea|gitea|gitlab|github
official forge support|✓|✘|✘|✓
dev status|adding features|maintenance||
platform|any|any|any|any
## philosophy
/ | [tea][tea] | [sip][sip] | [gitlab][gitlab] | [gh][gh]
-----------------------|:-----:|:-----:|:-----:|:-----:
aims to replace git cli|✘|||✓
works with decentralization in mind|✓|✓|✓|✘
per-repo setup needed|✘||✓|✘
workflow helpers|✓|||
interactive mode |[(✓)](https://gitea.com/gitea/tea/issues?type=all&state=open&labels=&milestone=0&assignee=0&q=interactive)|✘| |✓
programmatic mode|✓|||✓
machine readable output|✓|||
follows XDG spec|✓|||
## features
/ | [tea][tea] | [sip][sip] | [gitlab][gitlab] | [gh][gh]
-----------------------|:-----:|:-----:|:-----:|:-----:
open web UI|✓|||
search repos|✓|||
search issues|✘|✓||
textual item search filter syntax|✘|✓||
CRUD repos|[(✓)](https://gitea.com/gitea/tea/issues/239)|||
CRUD issues|[(✓)](https://gitea.com/gitea/tea/issues/229)|||
CRUD milestones|[(✓)](https://gitea.com/gitea/tea/issues/246)|||
CRUD releases|✓|||
CRUD labels|✓|||
CRUD PRs|✓|||
CRUD time tracking|✓|||x
CRUD orgs|[(✓)](https://gitea.com/gitea/tea/issues/287)|||
create PRs from local repo|✓|||
create PRs from remote repo|✓|||
code review|[u](https://gitea.com/gitea/tea/issues/131)|||
merge PRs||||
read comments|[u](https://gitea.com/gitea/tea/issues/172)|||
post comments||||
manage CI|✘|✘|✓|
manage notifications|[(✓)]()|||
administration|[u](https://gitea.com/gitea/tea/issues/161)|✘||✘
markdown rendering|✓|||✓
issue import/export|[u](https://gitea.com/gitea/tea/issues/132)|||
checkout PRs|✓|||
- ✓: supported
- (✓): partial support
- u: upcoming
- ✘: not supported
- ?: unknown

View File

@@ -1,5 +1,4 @@
Copyright (c) 2016 The Gitea Authors 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

113
Makefile
View File

@@ -1,14 +1,14 @@
DIST := dist DIST := dist
export GO111MODULE=on
export CGO_ENABLED=0
GO ?= go GO ?= go
SHASUM ?= shasum -a 256 SHASUM ?= shasum -a 256
export PATH := $($(GO) env GOPATH)/bin:$(PATH) export PATH := $($(GO) env GOPATH)/bin:$(PATH)
GOFILES := $(shell find . -name "*.go" -type f ! -path "./vendor/*" ! -path "*/bindata.go") GOFILES := $(shell find . -name "*.go" -type f ! -path "*/bindata.go")
GOFMT ?= gofmt -s
# Tool packages with pinned versions
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4
ifneq ($(DRONE_TAG),) ifneq ($(DRONE_TAG),)
VERSION ?= $(subst v,,$(DRONE_TAG)) VERSION ?= $(subst v,,$(DRONE_TAG))
@@ -17,7 +17,7 @@ else
ifneq ($(DRONE_BRANCH),) ifneq ($(DRONE_BRANCH),)
VERSION ?= $(subst release/v,,$(DRONE_BRANCH)) VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
else else
VERSION ?= master VERSION ?= main
endif endif
TEA_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') TEA_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
endif endif
@@ -25,22 +25,21 @@ TEA_VERSION_TAG ?= $(shell sed 's/+/_/' <<< $(TEA_VERSION))
TAGS ?= TAGS ?=
SDK ?= $(shell $(GO) list -f '{{.Version}}' -m code.gitea.io/sdk/gitea) SDK ?= $(shell $(GO) list -f '{{.Version}}' -m code.gitea.io/sdk/gitea)
LDFLAGS := -X "main.Version=$(TEA_VERSION)" -X "main.Tags=$(TAGS)" -X "main.SDK=$(SDK)" -s -w LDFLAGS := -X "code.gitea.io/tea/modules/version.Version=$(TEA_VERSION)" -X "code.gitea.io/tea/modules/version.Tags=$(TAGS)" -X "code.gitea.io/tea/modules/version.SDK=$(SDK)" -s -w
# override to allow passing additional goflags via make CLI # override to allow passing additional goflags via make CLI
override GOFLAGS := $(GOFLAGS) -mod=vendor -tags '$(TAGS)' -ldflags '$(LDFLAGS)' override GOFLAGS := $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)'
PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/) PACKAGES ?= $(shell $(GO) list ./...)
SOURCES ?= $(shell find . -name "*.go" -type f) SOURCES ?= $(shell find . -name "*.go" -type f)
# OS specific vars. # OS specific vars.
ifeq ($(OS), Windows_NT) ifeq ($(OS), Windows_NT)
EXECUTABLE := tea.exe EXECUTABLE := tea.exe
VET_TOOL := gitea-vet.exe
else else
EXECUTABLE := tea EXECUTABLE := tea
ifneq ($(shell uname -s), OpenBSD) VET_TOOL := gitea-vet
override BUILDMODE := -buildmode=pie
endif
endif endif
.PHONY: all .PHONY: all
@@ -48,62 +47,71 @@ all: build
.PHONY: clean .PHONY: clean
clean: clean:
$(GO) clean -mod=vendor -i ./... $(GO) clean -i ./...
rm -rf $(EXECUTABLE) $(DIST) rm -rf $(EXECUTABLE) $(DIST)
.PHONY: fmt .PHONY: fmt
fmt: fmt:
$(GOFMT) -w $(GOFILES) $(GO) run $(GOFUMPT_PACKAGE) -w $(GOFILES)
.PHONY: vet .PHONY: vet
vet: vet:
# Default vet # Default vet
$(GO) vet -mod=vendor $(PACKAGES) $(GO) vet $(PACKAGES)
# Custom vet # Custom vet
$(GO) build -mod=vendor code.gitea.io/gitea-vet $(GO) build code.gitea.io/gitea-vet
$(GO) vet -vettool=gitea-vet $(PACKAGES) $(GO) vet -vettool=$(VET_TOOL) $(PACKAGES)
.PHONY: lint .PHONY: lint
lint: install-lint-tools lint:
revive -config .revive.toml -exclude=./vendor/... ./... || exit 1 $(GO) run $(GOLANGCI_LINT_PACKAGE) run
.PHONY: misspell-check .PHONY: lint-fix
misspell-check: install-lint-tools lint-fix:
misspell -error -i unknwon,destory $(GOFILES) $(GO) run $(GOLANGCI_LINT_PACKAGE) run --fix
.PHONY: misspell
misspell: install-lint-tools
misspell -w -i unknwon $(GOFILES)
.PHONY: fmt-check .PHONY: fmt-check
fmt-check: fmt-check:
# get all go files and run go fmt on them # get all go files and run gofumpt on them
@diff=$$($(GOFMT) -d $(GOFILES)); \ @diff=$$($(GO) run $(GOFUMPT_PACKAGE) -d $(GOFILES)); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "Please run 'make fmt' and commit the result:"; \ echo "Please run 'make fmt' and commit the result:"; \
echo "$${diff}"; \ echo "$${diff}"; \
exit 1; \ exit 1; \
fi; fi;
.PHONY: docs
docs:
$(GO) run docs/docs.go --out docs/CLI.md
.PHONY: docs-check
docs-check:
@DIFF=$$($(GO) run docs/docs.go | diff docs/CLI.md -); \
if [ -n "$$DIFF" ]; then \
echo "Please run 'make docs' and commit the result:"; \
echo "$$DIFF"; \
exit 1; \
fi;
.PHONY: test .PHONY: test
test: test:
$(GO) test -mod=vendor -tags='sqlite sqlite_unlock_notify' $(PACKAGES) $(GO) test -tags='sqlite sqlite_unlock_notify' $(PACKAGES)
.PHONY: unit-test-coverage .PHONY: unit-test-coverage
unit-test-coverage: unit-test-coverage:
$(GO) test -mod=vendor -tags='sqlite sqlite_unlock_notify' -cover -coverprofile coverage.out $(PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1 $(GO) test -tags='sqlite sqlite_unlock_notify' -cover -coverprofile coverage.out $(PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
.PHONY: vendor .PHONY: tidy
vendor: tidy:
$(GO) mod tidy && $(GO) mod vendor $(GO) mod tidy
.PHONY: check .PHONY: check
check: test check: test
.PHONY: install .PHONY: install
install: $(SOURCES) install: $(SOURCES)
@echo "installing to $(GOPATH)/bin/$(EXECUTABLE)" @echo "installing to $(shell $(GO) env GOPATH)/bin/$(EXECUTABLE)"
$(GO) install -v $(BUILDMODE) $(GOFLAGS) $(GO) install -v $(BUILDMODE) $(GOFLAGS)
.PHONY: build .PHONY: build
build: $(EXECUTABLE) build: $(EXECUTABLE)
@@ -115,38 +123,3 @@ $(EXECUTABLE): $(SOURCES)
build-image: build-image:
docker build --build-arg VERSION=$(TEA_VERSION) -t gitea/tea:$(TEA_VERSION_TAG) . docker build --build-arg VERSION=$(TEA_VERSION) -t gitea/tea:$(TEA_VERSION_TAG) .
.PHONY: release
release: release-dirs install-release-tools release-os release-compress release-check
.PHONY: release-dirs
release-dirs:
mkdir -p $(DIST)/binaries $(DIST)/release
.PHONY: release-os
release-os:
CGO_ENABLED=0 gox -verbose -cgo=false $(GOFLAGS) -osarch='!darwin/386 !darwin/arm' -os="windows linux darwin" -arch="386 amd64 arm arm64" -output="$(DIST)/release/tea-$(VERSION)-{{.OS}}-{{.Arch}}"
.PHONY: release-compress
release-compress: install-release-tools
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done;
.PHONY: release-check
release-check: install-release-tools
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "checksumming $${file}" && $(SHASUM) `echo $${file} | sed 's/^..//'` > $${file}.sha256; done;
### tools
install-release-tools:
@hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install github.com/mitchellh/gox@latest; \
fi
@hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install github.com/ulikunitz/xz/cmd/gxz@latest; \
fi
install-lint-tools:
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install github.com/mgechev/revive@latest; \
fi
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install github.com/client9/misspell/cmd/misspell@latest; \
fi

213
README.md
View File

@@ -1,78 +1,102 @@
# <img alt='' src='https://gitea.com/repo-avatars/550-80a3a8c2ab0e2c2d69f296b7f8582485' height="40"/> *T E A* # <img alt='tea logo' src='https://gitea.com/repo-avatars/550-80a3a8c2ab0e2c2d69f296b7f8582485' height="40"/> *T E A*
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Release](https://raster.shields.io/badge/dynamic/json.svg?label=release&url=https://gitea.com/api/v1/repos/gitea/tea/releases&query=$[0].tag_name)](https://gitea.com/gitea/tea/releases) [![Build Status](https://drone.gitea.com/api/badges/gitea/tea/status.svg)](https://drone.gitea.com/gitea/tea) [![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/Gitea) [![Go Report Card](https://goreportcard.com/badge/code.gitea.io/tea)](https://goreportcard.com/report/code.gitea.io/tea) [![GoDoc](https://godoc.org/code.gitea.io/tea?status.svg)](https://godoc.org/code.gitea.io/tea) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Release](https://raster.shields.io/badge/dynamic/json.svg?label=release&url=https://gitea.com/api/v1/repos/gitea/tea/releases&query=$[0].tag_name)](https://gitea.com/gitea/tea/releases)
[![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/Gitea)
[![Go Report Card](https://goreportcard.com/badge/code.gitea.io/tea)](https://goreportcard.com/report/code.gitea.io/tea) [![GoDoc](https://pkg.go.dev/badge/code.gitea.io/tea?status.svg)](https://godoc.org/code.gitea.io/tea)
![Tea Release Status](https://gitea.com/gitea/tea/actions/workflows/release-nightly.yml/badge.svg)
### The official CLI for Gitea ## The official CLI for Gitea
![demo gif](./demo.gif) ![demo gif](./demo.gif)
``` ```
tea - command line tool to interact with Gitea NAME:
version 0.8.0-preview tea - command line tool to interact with Gitea
USAGE USAGE:
tea command [subcommand] [command options] [arguments...] tea [global options] [command [command options]]
DESCRIPTION VERSION:
tea is a productivity helper for Gitea. It can be used to manage most entities on Version: 0.10.1+15-g8876fe3 golang: 1.25.0 go-sdk: v0.21.0
one or multiple Gitea instances & provides local helpers like 'tea pr checkout'.
tea tries to make use of context provided by the repository in $PWD if available.
tea works best in a upstream/fork workflow, when the local main branch tracks the
upstream repo. tea assumes that local git state is published on the remote before
doing operations with tea. Configuration is persisted in $XDG_CONFIG_HOME/tea.
COMMANDS DESCRIPTION:
help, h Shows a list of commands or help for one command tea is a productivity helper for Gitea. It can be used to manage most entities on
ENTITIES: one or multiple Gitea instances & provides local helpers like 'tea pr checkout'.
issues, issue, i List, create and update issues
pulls, pull, pr Manage and checkout pull requests
labels, label Manage issue labels
milestones, milestone, ms List and create milestones
releases, release, r Manage releases
times, time, t Operate on tracked times of a repository's issues & pulls
organizations, organization, org List, create, delete organizations
repos, repo Show repository details
comment, c Add a comment to an issue / pr
HELPERS:
open, o Open something of the repository in web browser
notifications, notification, n Show notifications
clone, C Clone a repository locally
SETUP:
logins, login Log in to a Gitea server
logout Log out from a Gitea server
shellcompletion, autocomplete Install shell completion for tea
whoami Show current logged in user
OPTIONS tea tries to make use of context provided by the repository in $PWD if available.
--help, -h show help (default: false) tea works best in a upstream/fork workflow, when the local main branch tracks the
--version, -v print the version (default: false) upstream repo. tea assumes that local git state is published on the remote before
doing operations with tea. Configuration is persisted in $XDG_CONFIG_HOME/tea.
EXAMPLES COMMANDS:
tea login add # add a login once to get started help, h Shows a list of commands or help for one command
tea pulls # list open pulls for the repo in $PWD ENTITIES:
tea pulls --repo $HOME/foo # list open pulls for the repo in $HOME/foo issues, issue, i List, create and update issues
tea pulls --remote upstream # list open pulls for the repo pointed at by pulls, pull, pr Manage and checkout pull requests
# your local "upstream" git remote labels, label Manage issue labels
# list open pulls for any gitea repo at the given login instance milestones, milestone, ms List and create milestones
tea pulls --repo gitea/tea --login gitea.com releases, release, r Manage releases
times, time, t Operate on tracked times of a repository's issues & pulls
organizations, organization, org List, create, delete organizations
repos, repo Show repository details
branches, branch, b Consult branches
actions Manage repository actions (secrets, variables)
comment, c Add a comment to an issue / pr
webhooks, webhook Manage repository webhooks
tea milestone issues 0.7.0 # view open issues for milestone '0.7.0' HELPERS:
tea issue 189 # view contents of issue 189 open, o Open something of the repository in web browser
tea open 189 # open web ui for issue 189 notifications, notification, n Show notifications
tea open milestones # open web ui for milestones clone, C Clone a repository locally
# send gitea desktop notifications every 5 minutes (bash + libnotify) MISCELLANEOUS:
while :; do tea notifications --mine -o simple | xargs -i notify-send {}; sleep 300; done whoami Show current logged in user
admin, a Operations requiring admin access on the Gitea instance
ABOUT SETUP:
Written & maintained by The Gitea Authors. logins, login Log in to a Gitea server
If you find a bug or want to contribute, we'll welcome you at https://gitea.com/gitea/tea. logout Log out from a Gitea server
More info about Gitea itself on https://gitea.io.
GLOBAL OPTIONS:
--debug, --vvv Enable debug mode (default: false)
--help, -h show help
--version, -v print the version
EXAMPLES
tea login add # add a login once to get started
tea pulls # list open pulls for the repo in $PWD
tea pulls --repo $HOME/foo # list open pulls for the repo in $HOME/foo
tea pulls --remote upstream # list open pulls for the repo pointed at by
# your local "upstream" git remote
# list open pulls for any gitea repo at the given login instance
tea pulls --repo gitea/tea --login gitea.com
tea milestone issues 0.7.0 # view open issues for milestone '0.7.0'
tea issue 189 # view contents of issue 189
tea open 189 # open web ui for issue 189
tea open milestones # open web ui for milestones
tea actions secrets list # list all repository action secrets
tea actions secrets create API_KEY # create a new secret (will prompt for value)
tea actions variables list # list all repository action variables
tea actions variables set API_URL https://api.example.com
tea webhooks list # list repository webhooks
tea webhooks list --org myorg # list organization webhooks
tea webhooks create https://example.com/hook --events push,pull_request
# send gitea desktop notifications every 5 minutes (bash + libnotify)
while :; do tea notifications --mine -o simple | xargs -i notify-send {}; sleep 300; done
ABOUT
Written & maintained by The Gitea Authors.
If you find a bug or want to contribute, we'll welcome you at https://gitea.com/gitea/tea.
More info about Gitea itself on https://about.gitea.com.
``` ```
- [Compare features with other git forge CLIs](./FEATURE-COMPARISON.md)
- tea uses [code.gitea.io/sdk](https://code.gitea.io/sdk) and interacts with the Gitea API. - tea uses [code.gitea.io/sdk](https://code.gitea.io/sdk) and interacts with the Gitea API.
## Installation ## Installation
@@ -80,23 +104,72 @@
There are different ways to get `tea`: There are different ways to get `tea`:
1. Install via your system package manager: 1. Install via your system package manager:
- macOS via `brew` (gitea-maintained): - macOS via `brew` (official):
```sh ```sh
brew tap gitea/tap https://gitea.com/gitea/homebrew-gitea
brew install tea brew install tea
``` ```
- arch linux ([gitea-tea-git](https://aur.archlinux.org/packages/gitea-tea-git), thirdparty) - arch linux ([tea](https://archlinux.org/packages/extra/x86_64/tea/), thirdparty)
- alpine linux ([tea](https://pkgs.alpinelinux.org/packages?name=tea&branch=edge), thirdparty) - alpine linux ([tea](https://pkgs.alpinelinux.org/packages?name=tea&branch=edge), thirdparty)
- Windows via `MSYS2` ([tea](https://packages.msys2.org/base/mingw-w64-tea), thirdparty)
2. Use the prebuilt binaries from [dl.gitea.io](https://dl.gitea.io/tea/) 2. Use the prebuilt binaries from [dl.gitea.com](https://dl.gitea.com/tea/)
3. Install from source: [see *Compilation*](#compilation) 3. Install from source: [see *Compilation*](#compilation)
4. Docker (thirdparty): [tgerczei/tea](https://hub.docker.com/r/tgerczei/tea) 4. Docker: [Tea at docker hub](https://hub.docker.com/r/gitea/tea)
### Log in to Gitea from tea
Gitea can use many different authentication schemes, and not every authentication method will work with every Gitea deployment. When you are a Gitea instance administrator you can tweak your settings to fit your use case. For the method that is most likely to work with any Gitea deployment use the following steps:
1. Open your Gitea instance in a web browser
2. Log in to Gitea in your web browser. Any MFA, IDP, or whatever else should be available this way.
3. In your "user settings", generate an application token with at least **user read** permissions. If you want to do anything useful with the token add additional permissions/scopes.
4. Run `tea login add`, select **application token** authentication when asked for authentication type, and answer **yes** to the question if you have a token. Paste the generated token when asked for one.
You should now be logged in to your gitea instance from tea.
Since 0.10 Gitea supports the much simpler oauth workflow but oauth may not be available on all Gitea deployments, and gets much more complex when running tea on a remote system.
### Shell completion
If you installed from source or the package does not provide the completions with it you can add them yourself with `tea completion <shell>` command which is not visible in help. To generate the completions run one of the following commands depending on your shell.
```shell
# .bashrc
source <(tea completion bash)
# .zshrc
source <(tea completion zsh)
# fish
tea completion fish > ~/.config/fish/completions/tea.fish
# Powershell
Output the script to path/to/autocomplete/tea.ps1 an run it.
```
### Man Page
The hidden command `tea man` can be used to generate the `tea` man page.
```shell
# for bash or zsh
man <(tea man)
# for fish
man (tea man | psub)
# write man page to a file
tea man --out ./tea.man
```
## Compilation ## Compilation
Make sure you have a current go version installed (1.13 or newer). Make sure you have a current Go version installed (1.26 or newer).
- To compile the source yourself with the recommended flags & tags: - To compile the source yourself with the recommended flags & tags:
```sh ```sh
@@ -105,10 +178,15 @@ Make sure you have a current go version installed (1.13 or newer).
make make
``` ```
Note that GNU Make (gmake on OpenBSD) is required. Note that GNU Make (gmake on OpenBSD) is required.
If you want to install the compiled program you have to execute the following command:
- For a quick installation without `git` & `make`:
```sh ```sh
go install code.gitea.io/tea@latest make install
```
This installs the binary into the "bin" folder inside of your GOPATH folder (`go env GOPATH`). It is possible that this folder isn't in your PATH Environment Variable.
- For a quick installation without `git` & `make`, set $version and exec:
```sh
go install code.gitea.io/tea@${version}
``` ```
## Contributing ## Contributing
@@ -117,7 +195,6 @@ Fork -> Patch -> Push -> Pull Request
- `make test` run testsuite - `make test` run testsuite
- `make vet` run checks (check the order of imports; preventing failure on CI pipeline beforehand) - `make vet` run checks (check the order of imports; preventing failure on CI pipeline beforehand)
- `make vendor` when adding new dependencies
- ... (for other development tasks, check the `Makefile`) - ... (for other development tasks, check the `Makefile`)
**Please** read the [CONTRIBUTING](CONTRIBUTING.md) documentation, it will tell you about internal structures and concepts. **Please** read the [CONTRIBUTING](CONTRIBUTING.md) documentation, it will tell you about internal structures and concepts.

View File

@@ -1,6 +1,6 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
//go:build vendor //go:build vendor
// +build vendor // +build vendor

47
cmd/actions.go Normal file
View File

@@ -0,0 +1,47 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
stdctx "context"
"code.gitea.io/tea/cmd/actions"
"github.com/urfave/cli/v3"
)
// CmdActions represents the actions command for managing Gitea Actions
var CmdActions = cli.Command{
Name: "actions",
Aliases: []string{"action"},
Category: catEntities,
Usage: "Manage repository actions",
Description: "Manage repository actions including secrets, variables, and workflow runs",
Action: runActionsDefault,
Commands: []*cli.Command{
&actions.CmdActionsSecrets,
&actions.CmdActionsVariables,
&actions.CmdActionsRuns,
&actions.CmdActionsWorkflows,
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "repo",
Usage: "repository to operate on",
},
&cli.StringFlag{
Name: "login",
Usage: "gitea login instance to use",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "output format [table, csv, simple, tsv, yaml, json]",
},
},
}
func runActionsDefault(_ stdctx.Context, cmd *cli.Command) error {
return cli.ShowSubcommandHelp(cmd)
}

31
cmd/actions/runs.go Normal file
View File

@@ -0,0 +1,31 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
stdctx "context"
"code.gitea.io/tea/cmd/actions/runs"
"github.com/urfave/cli/v3"
)
// CmdActionsRuns represents the actions runs command
var CmdActionsRuns = cli.Command{
Name: "runs",
Aliases: []string{"run"},
Usage: "Manage workflow runs",
Description: "List, view, and manage workflow runs for repository actions",
Action: runRunsDefault,
Commands: []*cli.Command{
&runs.CmdRunsList,
&runs.CmdRunsView,
&runs.CmdRunsDelete,
&runs.CmdRunsLogs,
},
}
func runRunsDefault(ctx stdctx.Context, cmd *cli.Command) error {
return runs.RunRunsList(ctx, cmd)
}

View File

@@ -0,0 +1,71 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package runs
import (
stdctx "context"
"fmt"
"strconv"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"github.com/urfave/cli/v3"
)
// CmdRunsDelete represents a sub command to delete/cancel workflow runs
var CmdRunsDelete = cli.Command{
Name: "delete",
Aliases: []string{"remove", "rm", "cancel"},
Usage: "Delete or cancel a workflow run",
Description: "Delete (cancel) a workflow run from the repository",
ArgsUsage: "<run-id>",
Action: runRunsDelete,
Flags: append([]cli.Flag{
&cli.BoolFlag{
Name: "confirm",
Aliases: []string{"y"},
Usage: "confirm deletion without prompting",
},
}, flags.AllDefaultFlags...),
}
func runRunsDelete(ctx stdctx.Context, cmd *cli.Command) error {
if cmd.Args().Len() == 0 {
return fmt.Errorf("run ID is required")
}
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
runIDStr := cmd.Args().First()
runID, err := strconv.ParseInt(runIDStr, 10, 64)
if err != nil {
return fmt.Errorf("invalid run ID: %s", runIDStr)
}
if !cmd.Bool("confirm") {
fmt.Printf("Are you sure you want to delete run %d? [y/N] ", runID)
var response string
fmt.Scanln(&response)
if response != "y" && response != "Y" && response != "yes" {
fmt.Println("Deletion canceled.")
return nil
}
}
_, err = client.DeleteRepoActionRun(c.Owner, c.Repo, runID)
if err != nil {
return fmt.Errorf("failed to delete run: %w", err)
}
fmt.Printf("Run %d deleted successfully\n", runID)
return nil
}

148
cmd/actions/runs/list.go Normal file
View File

@@ -0,0 +1,148 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package runs
import (
stdctx "context"
"fmt"
"time"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
)
// CmdRunsList represents a sub command to list workflow runs
var CmdRunsList = cli.Command{
Name: "list",
Aliases: []string{"ls"},
Usage: "List workflow runs",
Description: "List workflow runs for repository actions with optional filtering",
Action: RunRunsList,
Flags: append([]cli.Flag{
&flags.PaginationPageFlag,
&flags.PaginationLimitFlag,
&cli.StringFlag{
Name: "status",
Usage: "Filter by status (success, failure, pending, queued, in_progress, skipped, canceled)",
},
&cli.StringFlag{
Name: "branch",
Usage: "Filter by branch name",
},
&cli.StringFlag{
Name: "event",
Usage: "Filter by event type (push, pull_request, etc.)",
},
&cli.StringFlag{
Name: "actor",
Usage: "Filter by actor username (who triggered the run)",
},
&cli.StringFlag{
Name: "since",
Usage: "Show runs started after this time (e.g., '24h', '2024-01-01')",
},
&cli.StringFlag{
Name: "until",
Usage: "Show runs started before this time (e.g., '2024-01-01')",
},
}, flags.AllDefaultFlags...),
}
// parseTimeFlag parses time flags like "24h" or "2024-01-01"
func parseTimeFlag(value string) (time.Time, error) {
if value == "" {
return time.Time{}, nil
}
// Try parsing as duration (e.g., "24h", "168h")
if duration, err := time.ParseDuration(value); err == nil {
return time.Now().Add(-duration), nil
}
// Try parsing as date
formats := []string{
"2006-01-02",
"2006-01-02 15:04",
"2006-01-02T15:04:05",
time.RFC3339,
}
for _, format := range formats {
if t, err := time.Parse(format, value); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf("unable to parse time: %s", value)
}
// RunRunsList lists workflow runs
func RunRunsList(ctx stdctx.Context, cmd *cli.Command) error {
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
// Parse time filters
since, err := parseTimeFlag(cmd.String("since"))
if err != nil {
return fmt.Errorf("invalid --since value: %w", err)
}
until, err := parseTimeFlag(cmd.String("until"))
if err != nil {
return fmt.Errorf("invalid --until value: %w", err)
}
// Build list options
listOpts := flags.GetListOptions(cmd)
runs, _, err := client.ListRepoActionRuns(c.Owner, c.Repo, gitea.ListRepoActionRunsOptions{
ListOptions: listOpts,
Status: cmd.String("status"),
Branch: cmd.String("branch"),
Event: cmd.String("event"),
Actor: cmd.String("actor"),
})
if err != nil {
return err
}
if runs == nil {
return print.ActionRunsList(nil, c.Output)
}
// Filter by time if specified
filteredRuns := filterRunsByTime(runs.WorkflowRuns, since, until)
return print.ActionRunsList(filteredRuns, c.Output)
}
// filterRunsByTime filters runs based on time range
func filterRunsByTime(runs []*gitea.ActionWorkflowRun, since, until time.Time) []*gitea.ActionWorkflowRun {
if since.IsZero() && until.IsZero() {
return runs
}
var filtered []*gitea.ActionWorkflowRun
for _, run := range runs {
if !since.IsZero() && run.StartedAt.Before(since) {
continue
}
if !until.IsZero() && run.StartedAt.After(until) {
continue
}
filtered = append(filtered, run)
}
return filtered
}

View File

@@ -0,0 +1,111 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package runs
import (
stdctx "context"
"os"
"testing"
"time"
"code.gitea.io/sdk/gitea"
"code.gitea.io/tea/modules/config"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v3"
)
func TestFilterRunsByTime(t *testing.T) {
now := time.Now()
runs := []*gitea.ActionWorkflowRun{
{ID: 1, StartedAt: now.Add(-1 * time.Hour)},
{ID: 2, StartedAt: now.Add(-2 * time.Hour)},
{ID: 3, StartedAt: now.Add(-3 * time.Hour)},
{ID: 4, StartedAt: now.Add(-4 * time.Hour)},
{ID: 5, StartedAt: now.Add(-5 * time.Hour)},
}
tests := []struct {
name string
since time.Time
until time.Time
expected []int64
}{
{
name: "no filter",
since: time.Time{},
until: time.Time{},
expected: []int64{1, 2, 3, 4, 5},
},
{
name: "since 2.5 hours ago",
since: now.Add(-150 * time.Minute),
until: time.Time{},
expected: []int64{1, 2},
},
{
name: "until 2.5 hours ago",
since: time.Time{},
until: now.Add(-150 * time.Minute),
expected: []int64{3, 4, 5},
},
{
name: "between 2 and 4 hours ago",
since: now.Add(-4 * time.Hour),
until: now.Add(-2 * time.Hour),
expected: []int64{2, 3, 4},
},
{
name: "filter excludes all",
since: now.Add(-30 * time.Minute),
until: time.Time{},
expected: []int64{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := filterRunsByTime(runs, tt.since, tt.until)
if len(result) != len(tt.expected) {
t.Errorf("filterRunsByTime() returned %d runs, want %d", len(result), len(tt.expected))
return
}
for i, run := range result {
if run.ID != tt.expected[i] {
t.Errorf("filterRunsByTime()[%d].ID = %d, want %d", i, run.ID, tt.expected[i])
}
}
})
}
}
func TestRunRunsListRequiresRepoContext(t *testing.T) {
oldWd, err := os.Getwd()
require.NoError(t, err)
require.NoError(t, os.Chdir(t.TempDir()))
t.Cleanup(func() {
require.NoError(t, os.Chdir(oldWd))
})
config.SetConfigForTesting(config.LocalConfig{
Logins: []config.Login{{
Name: "test",
URL: "https://gitea.example.com",
Token: "token",
User: "tester",
Default: true,
}},
})
cmd := &cli.Command{
Name: CmdRunsList.Name,
Flags: CmdRunsList.Flags,
}
require.NoError(t, cmd.Set("login", "test"))
err = RunRunsList(stdctx.Background(), cmd)
require.ErrorContains(t, err, "remote repository required")
}

175
cmd/actions/runs/logs.go Normal file
View File

@@ -0,0 +1,175 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package runs
import (
stdctx "context"
"fmt"
"strconv"
"time"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
)
// CmdRunsLogs represents a sub command to view workflow run logs
var CmdRunsLogs = cli.Command{
Name: "logs",
Aliases: []string{"log"},
Usage: "View workflow run logs",
Description: "View logs for a workflow run or specific job",
ArgsUsage: "<run-id>",
Action: runRunsLogs,
Flags: append([]cli.Flag{
&cli.StringFlag{
Name: "job",
Usage: "specific job ID to view logs for (if omitted, shows all jobs)",
},
&cli.BoolFlag{
Name: "follow",
Aliases: []string{"f"},
Usage: "follow log output (like tail -f), requires job to be in progress",
},
}, flags.AllDefaultFlags...),
}
func runRunsLogs(ctx stdctx.Context, cmd *cli.Command) error {
if cmd.Args().Len() == 0 {
return fmt.Errorf("run ID is required")
}
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
runIDStr := cmd.Args().First()
runID, err := strconv.ParseInt(runIDStr, 10, 64)
if err != nil {
return fmt.Errorf("invalid run ID: %s", runIDStr)
}
// Check if follow mode is enabled
follow := cmd.Bool("follow")
// If specific job ID provided, fetch only that job's logs
jobIDStr := cmd.String("job")
if jobIDStr != "" {
jobID, err := strconv.ParseInt(jobIDStr, 10, 64)
if err != nil {
return fmt.Errorf("invalid job ID: %s", jobIDStr)
}
if follow {
return followJobLogs(client, c, jobID, "")
}
logs, _, err := client.GetRepoActionJobLogs(c.Owner, c.Repo, jobID)
if err != nil {
return fmt.Errorf("failed to get logs for job %d: %w", jobID, err)
}
fmt.Printf("Logs for job %d:\n", jobID)
fmt.Printf("---\n%s\n", string(logs))
return nil
}
// Otherwise, fetch all jobs and their logs
jobs, _, err := client.ListRepoActionRunJobs(c.Owner, c.Repo, runID, gitea.ListRepoActionJobsOptions{
ListOptions: flags.GetListOptions(cmd),
})
if err != nil {
return fmt.Errorf("failed to get jobs: %w", err)
}
if len(jobs.Jobs) == 0 {
fmt.Printf("No jobs found for run %d\n", runID)
return nil
}
// If following and multiple jobs, require --job flag
if follow && len(jobs.Jobs) > 1 {
return fmt.Errorf("--follow requires --job when run has multiple jobs (found %d jobs)", len(jobs.Jobs))
}
// If following with single job, follow it
if follow && len(jobs.Jobs) == 1 {
return followJobLogs(client, c, jobs.Jobs[0].ID, jobs.Jobs[0].Name)
}
// Fetch logs for each job
for i, job := range jobs.Jobs {
if i > 0 {
fmt.Println()
}
fmt.Printf("Job: %s (ID: %d)\n", job.Name, job.ID)
fmt.Printf("Status: %s\n", job.Status)
fmt.Println("---")
logs, _, err := client.GetRepoActionJobLogs(c.Owner, c.Repo, job.ID)
if err != nil {
fmt.Printf("Error fetching logs: %v\n", err)
continue
}
fmt.Println(string(logs))
}
return nil
}
// followJobLogs continuously fetches and displays logs for a running job
func followJobLogs(client *gitea.Client, c *context.TeaContext, jobID int64, jobName string) error {
var lastLogLength int
if jobName != "" {
fmt.Printf("Following logs for job '%s' (ID: %d) - press Ctrl+C to stop...\n", jobName, jobID)
} else {
fmt.Printf("Following logs for job %d (press Ctrl+C to stop)...\n", jobID)
}
fmt.Println("---")
for {
// Fetch job status
job, _, err := client.GetRepoActionJob(c.Owner, c.Repo, jobID)
if err != nil {
return fmt.Errorf("failed to get job: %w", err)
}
// Check if job is still running
isRunning := job.Status == "in_progress" || job.Status == "queued" || job.Status == "pending"
// Fetch logs
logs, _, err := client.GetRepoActionJobLogs(c.Owner, c.Repo, jobID)
if err != nil {
return fmt.Errorf("failed to get logs: %w", err)
}
// Display new content only
if len(logs) > lastLogLength {
newLogs := string(logs)[lastLogLength:]
fmt.Print(newLogs)
lastLogLength = len(logs)
}
// If job is complete, exit
if !isRunning {
fmt.Printf("\n---\nJob completed with status: %s\n", job.Status)
break
}
// Wait before next poll
time.Sleep(2 * time.Second)
}
return nil
}

83
cmd/actions/runs/view.go Normal file
View File

@@ -0,0 +1,83 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package runs
import (
stdctx "context"
"fmt"
"strconv"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
)
// CmdRunsView represents a sub command to view workflow run details
var CmdRunsView = cli.Command{
Name: "view",
Aliases: []string{"show", "get"},
Usage: "View workflow run details",
Description: "View details of a specific workflow run including jobs",
ArgsUsage: "<run-id>",
Action: runRunsView,
Flags: append([]cli.Flag{
&cli.BoolFlag{
Name: "jobs",
Usage: "show jobs table",
Value: true,
},
}, flags.AllDefaultFlags...),
}
func runRunsView(ctx stdctx.Context, cmd *cli.Command) error {
if cmd.Args().Len() == 0 {
return fmt.Errorf("run ID is required")
}
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
runIDStr := cmd.Args().First()
runID, err := strconv.ParseInt(runIDStr, 10, 64)
if err != nil {
return fmt.Errorf("invalid run ID: %s", runIDStr)
}
// Fetch run details
run, _, err := client.GetRepoActionRun(c.Owner, c.Repo, runID)
if err != nil {
return fmt.Errorf("failed to get run: %w", err)
}
// Print run details
print.ActionRunDetails(run)
// Fetch and print jobs if requested
if cmd.Bool("jobs") {
jobs, _, err := client.ListRepoActionRunJobs(c.Owner, c.Repo, runID, gitea.ListRepoActionJobsOptions{
ListOptions: flags.GetListOptions(cmd),
})
if err != nil {
return fmt.Errorf("failed to get jobs: %w", err)
}
if jobs != nil && len(jobs.Jobs) > 0 {
fmt.Printf("\nJobs:\n\n")
if err := print.ActionWorkflowJobsList(jobs.Jobs, c.Output); err != nil {
return err
}
}
}
return nil
}

30
cmd/actions/secrets.go Normal file
View File

@@ -0,0 +1,30 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
stdctx "context"
"code.gitea.io/tea/cmd/actions/secrets"
"github.com/urfave/cli/v3"
)
// CmdActionsSecrets represents the actions secrets command
var CmdActionsSecrets = cli.Command{
Name: "secrets",
Aliases: []string{"secret"},
Usage: "Manage repository action secrets",
Description: "Manage secrets used by repository actions and workflows",
Action: runSecretsDefault,
Commands: []*cli.Command{
&secrets.CmdSecretsList,
&secrets.CmdSecretsCreate,
&secrets.CmdSecretsDelete,
},
}
func runSecretsDefault(ctx stdctx.Context, cmd *cli.Command) error {
return secrets.RunSecretsList(ctx, cmd)
}

View File

@@ -0,0 +1,74 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package secrets
import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/utils"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
)
// CmdSecretsCreate represents a sub command to create action secrets
var CmdSecretsCreate = cli.Command{
Name: "create",
Aliases: []string{"add", "set"},
Usage: "Create an action secret",
Description: "Create a secret for use in repository actions and workflows",
ArgsUsage: "<secret-name> [secret-value]",
Action: runSecretsCreate,
Flags: append([]cli.Flag{
&cli.StringFlag{
Name: "file",
Usage: "read secret value from file",
},
&cli.BoolFlag{
Name: "stdin",
Usage: "read secret value from stdin",
},
}, flags.AllDefaultFlags...),
}
func runSecretsCreate(ctx stdctx.Context, cmd *cli.Command) error {
if cmd.Args().Len() == 0 {
return fmt.Errorf("secret name is required")
}
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
secretName := cmd.Args().First()
// Read secret value using the utility
secretValue, err := utils.ReadValue(cmd, utils.ReadValueOptions{
ResourceName: "secret",
PromptMsg: fmt.Sprintf("Enter secret value for '%s'", secretName),
Hidden: true,
AllowEmpty: false,
})
if err != nil {
return err
}
_, err = client.CreateRepoActionSecret(c.Owner, c.Repo, secretName, gitea.CreateOrUpdateSecretOption{
Data: secretValue,
})
if err != nil {
return err
}
fmt.Printf("Secret '%s' created successfully\n", secretName)
return nil
}

View File

@@ -0,0 +1,56 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package secrets
import (
"testing"
)
func TestGetSecretSourceArgs(t *testing.T) {
tests := []struct {
name string
args []string
wantErr bool
}{
{
name: "valid args",
args: []string{"VALID_SECRET", "secret_value"},
wantErr: false,
},
{
name: "missing name",
args: []string{},
wantErr: true,
},
{
name: "too many args",
args: []string{"SECRET_NAME", "value", "extra"},
wantErr: true,
},
{
name: "invalid secret name",
args: []string{"invalid_secret", "value"},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test argument validation only
if len(tt.args) == 0 {
if !tt.wantErr {
t.Error("Expected error for empty args")
}
return
}
if len(tt.args) > 2 {
if !tt.wantErr {
t.Error("Expected error for too many args")
}
return
}
})
}
}

View File

@@ -0,0 +1,66 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package secrets
import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"github.com/urfave/cli/v3"
)
// CmdSecretsDelete represents a sub command to delete action secrets
var CmdSecretsDelete = cli.Command{
Name: "delete",
Aliases: []string{"remove", "rm"},
Usage: "Delete an action secret",
Description: "Delete a secret used by repository actions",
ArgsUsage: "<secret-name>",
Action: runSecretsDelete,
Flags: append([]cli.Flag{
&cli.BoolFlag{
Name: "confirm",
Aliases: []string{"y"},
Usage: "confirm deletion without prompting",
},
}, flags.AllDefaultFlags...),
}
func runSecretsDelete(ctx stdctx.Context, cmd *cli.Command) error {
if cmd.Args().Len() == 0 {
return fmt.Errorf("secret name is required")
}
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
secretName := cmd.Args().First()
if !cmd.Bool("confirm") {
fmt.Printf("Are you sure you want to delete secret '%s'? [y/N] ", secretName)
var response string
fmt.Scanln(&response)
if response != "y" && response != "Y" && response != "yes" {
fmt.Println("Deletion canceled.")
return nil
}
}
_, err = client.DeleteRepoActionSecret(c.Owner, c.Repo, secretName)
if err != nil {
return err
}
fmt.Printf("Secret '%s' deleted successfully\n", secretName)
return nil
}

View File

@@ -0,0 +1,93 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package secrets
import (
"fmt"
"testing"
)
func TestSecretsDeleteValidation(t *testing.T) {
tests := []struct {
name string
args []string
wantErr bool
}{
{
name: "valid secret name",
args: []string{"VALID_SECRET"},
wantErr: false,
},
{
name: "no args",
args: []string{},
wantErr: true,
},
{
name: "too many args",
args: []string{"SECRET1", "SECRET2"},
wantErr: true,
},
{
name: "invalid secret name but client does not validate",
args: []string{"invalid_secret"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateDeleteArgs(tt.args)
if (err != nil) != tt.wantErr {
t.Errorf("validateDeleteArgs() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestSecretsDeleteFlags(t *testing.T) {
cmd := CmdSecretsDelete
// Test command properties
if cmd.Name != "delete" {
t.Errorf("Expected command name 'delete', got %s", cmd.Name)
}
// Check that rm is one of the aliases
hasRmAlias := false
for _, alias := range cmd.Aliases {
if alias == "rm" {
hasRmAlias = true
break
}
}
if !hasRmAlias {
t.Error("Expected 'rm' to be one of the aliases for delete command")
}
if cmd.ArgsUsage != "<secret-name>" {
t.Errorf("Expected ArgsUsage '<secret-name>', got %s", cmd.ArgsUsage)
}
if cmd.Usage == "" {
t.Error("Delete command should have usage text")
}
if cmd.Description == "" {
t.Error("Delete command should have description")
}
}
// validateDeleteArgs validates arguments for the delete command
func validateDeleteArgs(args []string) error {
if len(args) == 0 {
return fmt.Errorf("secret name is required")
}
if len(args) > 1 {
return fmt.Errorf("only one secret name allowed")
}
return nil
}

View File

@@ -0,0 +1,49 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package secrets
import (
stdctx "context"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
)
// CmdSecretsList represents a sub command to list action secrets
var CmdSecretsList = cli.Command{
Name: "list",
Aliases: []string{"ls"},
Usage: "List action secrets",
Description: "List secrets configured for repository actions",
Action: RunSecretsList,
Flags: append([]cli.Flag{
&flags.PaginationPageFlag,
&flags.PaginationLimitFlag,
}, flags.AllDefaultFlags...),
}
// RunSecretsList list action secrets
func RunSecretsList(ctx stdctx.Context, cmd *cli.Command) error {
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
secrets, _, err := client.ListRepoActionSecret(c.Owner, c.Repo, gitea.ListRepoActionSecretOption{
ListOptions: flags.GetListOptions(cmd),
})
if err != nil {
return err
}
return print.ActionSecretsList(secrets, c.Output)
}

View File

@@ -0,0 +1,98 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package secrets
import (
stdctx "context"
"os"
"testing"
"code.gitea.io/tea/modules/config"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v3"
)
func TestSecretsListFlags(t *testing.T) {
cmd := CmdSecretsList
// Test that required flags exist
expectedFlags := []string{"output", "remote", "login", "repo"}
for _, flagName := range expectedFlags {
found := false
for _, flag := range cmd.Flags {
if flag.Names()[0] == flagName {
found = true
break
}
}
if !found {
t.Errorf("Expected flag %s not found in CmdSecretsList", flagName)
}
}
// Test command properties
if cmd.Name != "list" {
t.Errorf("Expected command name 'list', got %s", cmd.Name)
}
if len(cmd.Aliases) == 0 || cmd.Aliases[0] != "ls" {
t.Errorf("Expected alias 'ls' for list command")
}
if cmd.Usage == "" {
t.Error("List command should have usage text")
}
if cmd.Description == "" {
t.Error("List command should have description")
}
}
func TestSecretsListValidation(t *testing.T) {
// Basic validation that the command accepts the expected arguments
// More detailed testing would require mocking the Gitea client
// Test that list command doesn't require arguments
args := []string{}
if len(args) > 0 {
t.Error("List command should not require arguments")
}
// Test that extra arguments are ignored
extraArgs := []string{"extra", "args"}
if len(extraArgs) > 0 {
// This is fine - list commands typically ignore extra args
}
}
func TestRunSecretsListRequiresRepoContext(t *testing.T) {
oldWd, err := os.Getwd()
require.NoError(t, err)
require.NoError(t, os.Chdir(t.TempDir()))
t.Cleanup(func() {
require.NoError(t, os.Chdir(oldWd))
})
config.SetConfigForTesting(config.LocalConfig{
Logins: []config.Login{{
Name: "test",
URL: "https://gitea.example.com",
Token: "token",
User: "tester",
Default: true,
}},
})
cmd := &cli.Command{
Name: CmdSecretsList.Name,
Flags: CmdSecretsList.Flags,
}
require.NoError(t, cmd.Set("login", "test"))
err = RunSecretsList(stdctx.Background(), cmd)
require.ErrorContains(t, err, "remote repository required")
}

30
cmd/actions/variables.go Normal file
View File

@@ -0,0 +1,30 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
stdctx "context"
"code.gitea.io/tea/cmd/actions/variables"
"github.com/urfave/cli/v3"
)
// CmdActionsVariables represents the actions variables command
var CmdActionsVariables = cli.Command{
Name: "variables",
Aliases: []string{"variable", "vars", "var"},
Usage: "Manage repository action variables",
Description: "Manage variables used by repository actions and workflows",
Action: runVariablesDefault,
Commands: []*cli.Command{
&variables.CmdVariablesList,
&variables.CmdVariablesSet,
&variables.CmdVariablesDelete,
},
}
func runVariablesDefault(ctx stdctx.Context, cmd *cli.Command) error {
return variables.RunVariablesList(ctx, cmd)
}

View File

@@ -0,0 +1,66 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package variables
import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"github.com/urfave/cli/v3"
)
// CmdVariablesDelete represents a sub command to delete action variables
var CmdVariablesDelete = cli.Command{
Name: "delete",
Aliases: []string{"remove", "rm"},
Usage: "Delete an action variable",
Description: "Delete a variable used by repository actions",
ArgsUsage: "<variable-name>",
Action: runVariablesDelete,
Flags: append([]cli.Flag{
&cli.BoolFlag{
Name: "confirm",
Aliases: []string{"y"},
Usage: "confirm deletion without prompting",
},
}, flags.AllDefaultFlags...),
}
func runVariablesDelete(ctx stdctx.Context, cmd *cli.Command) error {
if cmd.Args().Len() == 0 {
return fmt.Errorf("variable name is required")
}
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
variableName := cmd.Args().First()
if !cmd.Bool("confirm") {
fmt.Printf("Are you sure you want to delete variable '%s'? [y/N] ", variableName)
var response string
fmt.Scanln(&response)
if response != "y" && response != "Y" && response != "yes" {
fmt.Println("Deletion canceled.")
return nil
}
}
_, err = client.DeleteRepoActionVariable(c.Owner, c.Repo, variableName)
if err != nil {
return err
}
fmt.Printf("Variable '%s' deleted successfully\n", variableName)
return nil
}

View File

@@ -0,0 +1,98 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package variables
import (
"fmt"
"testing"
)
func TestVariablesDeleteValidation(t *testing.T) {
tests := []struct {
name string
args []string
wantErr bool
}{
{
name: "valid variable name",
args: []string{"VALID_VARIABLE"},
wantErr: false,
},
{
name: "valid lowercase name",
args: []string{"valid_variable"},
wantErr: false,
},
{
name: "no args",
args: []string{},
wantErr: true,
},
{
name: "too many args",
args: []string{"VARIABLE1", "VARIABLE2"},
wantErr: true,
},
{
name: "invalid variable name",
args: []string{"invalid-variable"},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateVariableDeleteArgs(tt.args)
if (err != nil) != tt.wantErr {
t.Errorf("validateVariableDeleteArgs() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestVariablesDeleteFlags(t *testing.T) {
cmd := CmdVariablesDelete
// Test command properties
if cmd.Name != "delete" {
t.Errorf("Expected command name 'delete', got %s", cmd.Name)
}
// Check that rm is one of the aliases
hasRmAlias := false
for _, alias := range cmd.Aliases {
if alias == "rm" {
hasRmAlias = true
break
}
}
if !hasRmAlias {
t.Error("Expected 'rm' to be one of the aliases for delete command")
}
if cmd.ArgsUsage != "<variable-name>" {
t.Errorf("Expected ArgsUsage '<variable-name>', got %s", cmd.ArgsUsage)
}
if cmd.Usage == "" {
t.Error("Delete command should have usage text")
}
if cmd.Description == "" {
t.Error("Delete command should have description")
}
}
// validateVariableDeleteArgs validates arguments for the delete command
func validateVariableDeleteArgs(args []string) error {
if len(args) == 0 {
return fmt.Errorf("variable name is required")
}
if len(args) > 1 {
return fmt.Errorf("only one variable name allowed")
}
return validateVariableName(args[0])
}

View File

@@ -0,0 +1,61 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package variables
import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print"
"github.com/urfave/cli/v3"
)
// CmdVariablesList represents a sub command to list action variables
var CmdVariablesList = cli.Command{
Name: "list",
Aliases: []string{"ls"},
Usage: "List action variables",
Description: "List variables configured for repository actions",
Action: RunVariablesList,
Flags: append([]cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "show specific variable by name",
},
}, flags.AllDefaultFlags...),
}
// RunVariablesList list action variables
func RunVariablesList(ctx stdctx.Context, cmd *cli.Command) error {
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
if name := cmd.String("name"); name != "" {
// Get specific variable
variable, _, err := client.GetRepoActionVariable(c.Owner, c.Repo, name)
if err != nil {
return err
}
print.ActionVariableDetails(variable)
return nil
}
// List all variables - Note: SDK doesn't have ListRepoActionVariables yet
// This is a limitation of the current SDK
fmt.Println("Note: Listing all variables is not yet supported by the Gitea SDK.")
fmt.Println("Use 'tea actions variables list --name <variable-name>' to get a specific variable.")
fmt.Println("You can also check your repository's Actions settings in the web interface.")
return nil
}

View File

@@ -0,0 +1,98 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package variables
import (
stdctx "context"
"os"
"testing"
"code.gitea.io/tea/modules/config"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v3"
)
func TestVariablesListFlags(t *testing.T) {
cmd := CmdVariablesList
// Test that required flags exist
expectedFlags := []string{"output", "remote", "login", "repo"}
for _, flagName := range expectedFlags {
found := false
for _, flag := range cmd.Flags {
if flag.Names()[0] == flagName {
found = true
break
}
}
if !found {
t.Errorf("Expected flag %s not found in CmdVariablesList", flagName)
}
}
// Test command properties
if cmd.Name != "list" {
t.Errorf("Expected command name 'list', got %s", cmd.Name)
}
if len(cmd.Aliases) == 0 || cmd.Aliases[0] != "ls" {
t.Errorf("Expected alias 'ls' for list command")
}
if cmd.Usage == "" {
t.Error("List command should have usage text")
}
if cmd.Description == "" {
t.Error("List command should have description")
}
}
func TestVariablesListValidation(t *testing.T) {
// Basic validation that the command accepts the expected arguments
// More detailed testing would require mocking the Gitea client
// Test that list command doesn't require arguments
args := []string{}
if len(args) > 0 {
t.Error("List command should not require arguments")
}
// Test that extra arguments are ignored
extraArgs := []string{"extra", "args"}
if len(extraArgs) > 0 {
// This is fine - list commands typically ignore extra args
}
}
func TestRunVariablesListRequiresRepoContext(t *testing.T) {
oldWd, err := os.Getwd()
require.NoError(t, err)
require.NoError(t, os.Chdir(t.TempDir()))
t.Cleanup(func() {
require.NoError(t, os.Chdir(oldWd))
})
config.SetConfigForTesting(config.LocalConfig{
Logins: []config.Login{{
Name: "test",
URL: "https://gitea.example.com",
Token: "token",
User: "tester",
Default: true,
}},
})
cmd := &cli.Command{
Name: CmdVariablesList.Name,
Flags: CmdVariablesList.Flags,
}
require.NoError(t, cmd.Set("login", "test"))
err = RunVariablesList(stdctx.Background(), cmd)
require.ErrorContains(t, err, "remote repository required")
}

View File

@@ -0,0 +1,108 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package variables
import (
stdctx "context"
"fmt"
"regexp"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/utils"
"github.com/urfave/cli/v3"
)
// CmdVariablesSet represents a sub command to set action variables
var CmdVariablesSet = cli.Command{
Name: "set",
Aliases: []string{"create", "update"},
Usage: "Set an action variable",
Description: "Set a variable for use in repository actions and workflows",
ArgsUsage: "<variable-name> [variable-value]",
Action: runVariablesSet,
Flags: append([]cli.Flag{
&cli.StringFlag{
Name: "file",
Usage: "read variable value from file",
},
&cli.BoolFlag{
Name: "stdin",
Usage: "read variable value from stdin",
},
}, flags.AllDefaultFlags...),
}
func runVariablesSet(ctx stdctx.Context, cmd *cli.Command) error {
if cmd.Args().Len() == 0 {
return fmt.Errorf("variable name is required")
}
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
variableName := cmd.Args().First()
if err := validateVariableName(variableName); err != nil {
return err
}
// Read variable value using the utility
variableValue, err := utils.ReadValue(cmd, utils.ReadValueOptions{
ResourceName: "variable",
PromptMsg: fmt.Sprintf("Enter variable value for '%s'", variableName),
Hidden: false,
AllowEmpty: false,
})
if err != nil {
return err
}
if err := validateVariableValue(variableValue); err != nil {
return err
}
_, err = client.CreateRepoActionVariable(c.Owner, c.Repo, variableName, variableValue)
if err != nil {
return err
}
fmt.Printf("Variable '%s' set successfully\n", variableName)
return nil
}
// validateVariableName validates that a variable name follows the required format
func validateVariableName(name string) error {
if name == "" {
return fmt.Errorf("variable name cannot be empty")
}
// Variable names can contain letters (upper/lower), numbers, and underscores
// Cannot start with a number
// Cannot contain spaces or special characters (except underscore)
validPattern := regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
if !validPattern.MatchString(name) {
return fmt.Errorf("variable name must contain only letters, numbers, and underscores, and cannot start with a number")
}
return nil
}
// validateVariableValue validates that a variable value is acceptable
func validateVariableValue(value string) error {
// Variables can be empty or contain whitespace, unlike secrets
// Check for maximum size (64KB limit)
if len(value) > 65536 {
return fmt.Errorf("variable value cannot exceed 64KB")
}
return nil
}

View File

@@ -0,0 +1,213 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package variables
import (
"strings"
"testing"
)
func TestValidateVariableName(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
}{
{
name: "valid name",
input: "VALID_VARIABLE_NAME",
wantErr: false,
},
{
name: "valid name with numbers",
input: "VARIABLE_123",
wantErr: false,
},
{
name: "valid lowercase",
input: "valid_variable",
wantErr: false,
},
{
name: "valid mixed case",
input: "Mixed_Case_Variable",
wantErr: false,
},
{
name: "invalid - spaces",
input: "INVALID VARIABLE",
wantErr: true,
},
{
name: "invalid - special chars",
input: "INVALID-VARIABLE!",
wantErr: true,
},
{
name: "invalid - starts with number",
input: "1INVALID",
wantErr: true,
},
{
name: "invalid - empty",
input: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateVariableName(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("validateVariableName(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
}
})
}
}
func TestGetVariableSourceArgs(t *testing.T) {
tests := []struct {
name string
args []string
wantErr bool
}{
{
name: "valid args",
args: []string{"VALID_VARIABLE", "variable_value"},
wantErr: false,
},
{
name: "valid lowercase",
args: []string{"valid_variable", "value"},
wantErr: false,
},
{
name: "missing name",
args: []string{},
wantErr: true,
},
{
name: "too many args",
args: []string{"VARIABLE_NAME", "value", "extra"},
wantErr: true,
},
{
name: "invalid variable name",
args: []string{"invalid-variable", "value"},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test argument validation only
if len(tt.args) == 0 {
if !tt.wantErr {
t.Error("Expected error for empty args")
}
return
}
if len(tt.args) > 2 {
if !tt.wantErr {
t.Error("Expected error for too many args")
}
return
}
// Test variable name validation
err := validateVariableName(tt.args[0])
if (err != nil) != tt.wantErr {
t.Errorf("validateVariableName() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestVariableNameValidation(t *testing.T) {
// Test that variable names follow GitHub Actions/Gitea Actions conventions
validNames := []string{
"VALID_VARIABLE",
"API_URL",
"DATABASE_HOST",
"VARIABLE_123",
"mixed_Case_Variable",
"lowercase_variable",
"UPPERCASE_VARIABLE",
}
invalidNames := []string{
"Invalid-Dashes",
"INVALID SPACES",
"123_STARTS_WITH_NUMBER",
"", // Empty
"INVALID!@#", // Special chars
}
for _, name := range validNames {
t.Run("valid_"+name, func(t *testing.T) {
err := validateVariableName(name)
if err != nil {
t.Errorf("validateVariableName(%q) should be valid, got error: %v", name, err)
}
})
}
for _, name := range invalidNames {
t.Run("invalid_"+name, func(t *testing.T) {
err := validateVariableName(name)
if err == nil {
t.Errorf("validateVariableName(%q) should be invalid, got no error", name)
}
})
}
}
func TestVariableValueValidation(t *testing.T) {
tests := []struct {
name string
value string
wantErr bool
}{
{
name: "valid value",
value: "variable123",
wantErr: false,
},
{
name: "valid complex value",
value: "https://api.example.com/v1",
wantErr: false,
},
{
name: "valid multiline value",
value: "line1\nline2\nline3",
wantErr: false,
},
{
name: "empty value allowed",
value: "",
wantErr: false, // Variables can be empty unlike secrets
},
{
name: "whitespace only allowed",
value: " \t\n ",
wantErr: false, // Variables can contain whitespace
},
{
name: "very long value",
value: strings.Repeat("a", 65537), // Over 64KB
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateVariableValue(tt.value)
if (err != nil) != tt.wantErr {
t.Errorf("validateVariableValue() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

32
cmd/actions/workflows.go Normal file
View File

@@ -0,0 +1,32 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
stdctx "context"
"code.gitea.io/tea/cmd/actions/workflows"
"github.com/urfave/cli/v3"
)
// CmdActionsWorkflows represents the actions workflows command
var CmdActionsWorkflows = cli.Command{
Name: "workflows",
Aliases: []string{"workflow"},
Usage: "Manage repository workflows",
Description: "List and manage repository action workflows",
Action: runWorkflowsDefault,
Commands: []*cli.Command{
&workflows.CmdWorkflowsList,
&workflows.CmdWorkflowsView,
&workflows.CmdWorkflowsDispatch,
&workflows.CmdWorkflowsEnable,
&workflows.CmdWorkflowsDisable,
},
}
func runWorkflowsDefault(ctx stdctx.Context, cmd *cli.Command) error {
return workflows.RunWorkflowsList(ctx, cmd)
}

View File

@@ -0,0 +1,65 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package workflows
import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"github.com/urfave/cli/v3"
)
// CmdWorkflowsDisable represents a sub command to disable a workflow
var CmdWorkflowsDisable = cli.Command{
Name: "disable",
Usage: "Disable a workflow",
Description: "Disable a workflow in the repository",
ArgsUsage: "<workflow-id>",
Action: runWorkflowsDisable,
Flags: append([]cli.Flag{
&cli.BoolFlag{
Name: "confirm",
Aliases: []string{"y"},
Usage: "confirm disable without prompting",
},
}, flags.AllDefaultFlags...),
}
func runWorkflowsDisable(ctx stdctx.Context, cmd *cli.Command) error {
if cmd.Args().Len() == 0 {
return fmt.Errorf("workflow ID is required")
}
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
workflowID := cmd.Args().First()
if !cmd.Bool("confirm") {
fmt.Printf("Are you sure you want to disable workflow %s? [y/N] ", workflowID)
var response string
fmt.Scanln(&response)
if response != "y" && response != "Y" && response != "yes" {
fmt.Println("Disable canceled.")
return nil
}
}
_, err = client.DisableRepoActionWorkflow(c.Owner, c.Repo, workflowID)
if err != nil {
return fmt.Errorf("failed to disable workflow: %w", err)
}
fmt.Printf("Workflow %s disabled successfully\n", workflowID)
return nil
}

View File

@@ -0,0 +1,174 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package workflows
import (
stdctx "context"
"fmt"
"strings"
"time"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
)
// CmdWorkflowsDispatch represents a sub command to dispatch a workflow
var CmdWorkflowsDispatch = cli.Command{
Name: "dispatch",
Aliases: []string{"trigger", "run"},
Usage: "Dispatch a workflow run",
Description: "Trigger a workflow_dispatch event for a workflow",
ArgsUsage: "<workflow-id>",
Action: runWorkflowsDispatch,
Flags: append([]cli.Flag{
&cli.StringFlag{
Name: "ref",
Aliases: []string{"r"},
Usage: "branch or tag to dispatch on (default: current branch)",
},
&cli.StringSliceFlag{
Name: "input",
Aliases: []string{"i"},
Usage: "workflow input in key=value format (can be specified multiple times)",
},
&cli.BoolFlag{
Name: "follow",
Aliases: []string{"f"},
Usage: "follow log output after dispatching",
},
}, flags.AllDefaultFlags...),
}
func runWorkflowsDispatch(ctx stdctx.Context, cmd *cli.Command) error {
if cmd.Args().Len() == 0 {
return fmt.Errorf("workflow ID is required")
}
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
workflowID := cmd.Args().First()
ref := cmd.String("ref")
if ref == "" {
if c.LocalRepo != nil {
branchName, _, localErr := c.LocalRepo.TeaGetCurrentBranchNameAndSHA()
if localErr == nil && branchName != "" {
ref = branchName
}
}
if ref == "" {
return fmt.Errorf("--ref is required (no local branch detected)")
}
}
inputs := make(map[string]string)
for _, input := range cmd.StringSlice("input") {
key, value, ok := strings.Cut(input, "=")
if !ok {
return fmt.Errorf("invalid input format %q, expected key=value", input)
}
inputs[key] = value
}
opt := gitea.CreateActionWorkflowDispatchOption{
Ref: ref,
Inputs: inputs,
}
details, _, err := client.DispatchRepoActionWorkflow(c.Owner, c.Repo, workflowID, opt, true)
if err != nil {
return fmt.Errorf("failed to dispatch workflow: %w", err)
}
print.ActionWorkflowDispatchResult(details)
if cmd.Bool("follow") && details != nil && details.WorkflowRunID > 0 {
return followDispatchedRun(client, c, details.WorkflowRunID)
}
return nil
}
const (
followPollInterval = 2 * time.Second
followMaxDuration = 30 * time.Minute
)
// followDispatchedRun waits for the dispatched run to start, then follows its logs
func followDispatchedRun(client *gitea.Client, c *context.TeaContext, runID int64) error {
fmt.Printf("\nWaiting for run %d to start...\n", runID)
var jobs *gitea.ActionWorkflowJobsResponse
for range 30 {
time.Sleep(followPollInterval)
var err error
jobs, _, err = client.ListRepoActionRunJobs(c.Owner, c.Repo, runID, gitea.ListRepoActionJobsOptions{})
if err != nil {
return fmt.Errorf("failed to get jobs: %w", err)
}
if len(jobs.Jobs) > 0 {
break
}
}
if jobs == nil || len(jobs.Jobs) == 0 {
return fmt.Errorf("timed out waiting for jobs to appear")
}
jobID := jobs.Jobs[0].ID
jobName := jobs.Jobs[0].Name
fmt.Printf("Following logs for job '%s' (ID: %d) - press Ctrl+C to stop...\n", jobName, jobID)
fmt.Println("---")
deadline := time.Now().Add(followMaxDuration)
var lastLogLength int
for time.Now().Before(deadline) {
job, _, err := client.GetRepoActionJob(c.Owner, c.Repo, jobID)
if err != nil {
return fmt.Errorf("failed to get job: %w", err)
}
isRunning := job.Status == "in_progress" || job.Status == "queued" || job.Status == "pending"
logs, _, logErr := client.GetRepoActionJobLogs(c.Owner, c.Repo, jobID)
if logErr != nil && isRunning {
time.Sleep(followPollInterval)
continue
}
if logErr == nil && len(logs) > lastLogLength {
fmt.Print(string(logs[lastLogLength:]))
lastLogLength = len(logs)
}
if !isRunning {
if logErr != nil {
fmt.Printf("\n---\nJob completed with status: %s (failed to fetch final logs: %v)\n", job.Status, logErr)
} else {
fmt.Printf("\n---\nJob completed with status: %s\n", job.Status)
}
break
}
time.Sleep(followPollInterval)
}
if time.Now().After(deadline) {
return fmt.Errorf("timed out after %s following logs", followMaxDuration)
}
return nil
}

View File

@@ -0,0 +1,48 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package workflows
import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"github.com/urfave/cli/v3"
)
// CmdWorkflowsEnable represents a sub command to enable a workflow
var CmdWorkflowsEnable = cli.Command{
Name: "enable",
Usage: "Enable a workflow",
Description: "Enable a disabled workflow in the repository",
ArgsUsage: "<workflow-id>",
Action: runWorkflowsEnable,
Flags: flags.AllDefaultFlags,
}
func runWorkflowsEnable(ctx stdctx.Context, cmd *cli.Command) error {
if cmd.Args().Len() == 0 {
return fmt.Errorf("workflow ID is required")
}
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
workflowID := cmd.Args().First()
_, err = client.EnableRepoActionWorkflow(c.Owner, c.Repo, workflowID)
if err != nil {
return fmt.Errorf("failed to enable workflow: %w", err)
}
fmt.Printf("Workflow %s enabled successfully\n", workflowID)
return nil
}

View File

@@ -0,0 +1,50 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package workflows
import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
)
// CmdWorkflowsList represents a sub command to list workflows
var CmdWorkflowsList = cli.Command{
Name: "list",
Aliases: []string{"ls"},
Usage: "List repository workflows",
Description: "List workflows in the repository with their status",
Action: RunWorkflowsList,
Flags: flags.AllDefaultFlags,
}
// RunWorkflowsList lists workflows in the repository using the workflow API
func RunWorkflowsList(ctx stdctx.Context, cmd *cli.Command) error {
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
resp, _, err := client.ListRepoActionWorkflows(c.Owner, c.Repo)
if err != nil {
return fmt.Errorf("failed to list workflows: %w", err)
}
var workflows []*gitea.ActionWorkflow
if resp != nil {
workflows = resp.Workflows
}
return print.ActionWorkflowsList(workflows, c.Output)
}

View File

@@ -0,0 +1,50 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package workflows
import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print"
"github.com/urfave/cli/v3"
)
// CmdWorkflowsView represents a sub command to view workflow details
var CmdWorkflowsView = cli.Command{
Name: "view",
Aliases: []string{"show", "get"},
Usage: "View workflow details",
Description: "View details of a specific workflow",
ArgsUsage: "<workflow-id>",
Action: runWorkflowsView,
Flags: flags.AllDefaultFlags,
}
func runWorkflowsView(ctx stdctx.Context, cmd *cli.Command) error {
if cmd.Args().Len() == 0 {
return fmt.Errorf("workflow ID is required")
}
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
workflowID := cmd.Args().First()
wf, _, err := client.GetRepoActionWorkflow(c.Owner, c.Repo, workflowID)
if err != nil {
return fmt.Errorf("failed to get workflow: %w", err)
}
print.ActionWorkflowDetails(wf)
return nil
}

View File

@@ -1,14 +1,15 @@
// Copyright 2021 The Gitea Authors. All rights reserved. // Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package cmd package cmd
import ( import (
stdctx "context"
"code.gitea.io/tea/cmd/admin/users" "code.gitea.io/tea/cmd/admin/users"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdAdmin represents the namespace of admin commands. // CmdAdmin represents the namespace of admin commands.
@@ -18,10 +19,10 @@ var CmdAdmin = cli.Command{
Usage: "Operations requiring admin access on the Gitea instance", Usage: "Operations requiring admin access on the Gitea instance",
Aliases: []string{"a"}, Aliases: []string{"a"},
Category: catMisc, Category: catMisc,
Action: func(cmd *cli.Context) error { Action: func(_ stdctx.Context, cmd *cli.Command) error {
return cli.ShowSubcommandHelp(cmd) return cli.ShowSubcommandHelp(cmd)
}, },
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&cmdAdminUsers, &cmdAdminUsers,
}, },
} }
@@ -30,20 +31,23 @@ var cmdAdminUsers = cli.Command{
Name: "users", Name: "users",
Aliases: []string{"u"}, Aliases: []string{"u"},
Usage: "Manage registered users", Usage: "Manage registered users",
Action: func(ctx *cli.Context) error { Action: func(ctx stdctx.Context, cmd *cli.Command) error {
if ctx.Args().Len() == 1 { if cmd.Args().Len() == 1 {
return runAdminUserDetail(ctx, ctx.Args().First()) return runAdminUserDetail(ctx, cmd, cmd.Args().First())
} }
return users.RunUserList(ctx) return users.RunUserList(ctx, cmd)
}, },
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&users.CmdUserList, &users.CmdUserList,
}, },
Flags: users.CmdUserList.Flags, Flags: users.CmdUserList.Flags,
} }
func runAdminUserDetail(cmd *cli.Context, u string) error { func runAdminUserDetail(_ stdctx.Context, cmd *cli.Command, u string) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
if err != nil {
return err
}
client := ctx.Login.Client() client := ctx.Login.Client()
user, _, err := client.GetUserInfo(u) user, _, err := client.GetUserInfo(u)
if err != nil { if err != nil {

View File

@@ -1,16 +1,17 @@
// Copyright 2021 The Gitea Authors. All rights reserved. // Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package users package users
import ( import (
stdctx "context"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var userFieldsFlag = flags.FieldsFlag(print.UserFields, []string{ var userFieldsFlag = flags.FieldsFlag(print.UserFields, []string{
@@ -32,8 +33,11 @@ var CmdUserList = cli.Command{
} }
// RunUserList list users // RunUserList list users
func RunUserList(cmd *cli.Context) error { func RunUserList(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
if err != nil {
return err
}
fields, err := userFieldsFlag.GetValues(cmd) fields, err := userFieldsFlag.GetValues(cmd)
if err != nil { if err != nil {
@@ -42,13 +46,11 @@ func RunUserList(cmd *cli.Context) error {
client := ctx.Login.Client() client := ctx.Login.Client()
users, _, err := client.AdminListUsers(gitea.AdminListUsersOptions{ users, _, err := client.AdminListUsers(gitea.AdminListUsersOptions{
ListOptions: ctx.GetListOptions(), ListOptions: flags.GetListOptions(cmd),
}) })
if err != nil { if err != nil {
return err return err
} }
print.UserList(users, ctx.Output, fields) return print.UserList(users, ctx.Output, fields)
return nil
} }

408
cmd/api.go Normal file
View File

@@ -0,0 +1,408 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"bytes"
stdctx "context"
"encoding/json"
"fmt"
"io"
"os"
"strconv"
"strings"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/api"
"code.gitea.io/tea/modules/context"
"github.com/urfave/cli/v3"
"golang.org/x/term"
)
// apiFlags returns a fresh set of flag instances for the api command.
// This is a factory function so that each invocation gets independent flag
// objects, avoiding shared hasBeenSet state across tests.
func apiFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "method",
Aliases: []string{"X"},
Usage: "HTTP method (GET, POST, PUT, PATCH, DELETE)",
Value: "GET",
},
&cli.StringSliceFlag{
Name: "field",
Aliases: []string{"f"},
Usage: "Add a string field to the request body (key=value)",
},
&cli.StringSliceFlag{
Name: "Field",
Aliases: []string{"F"},
Usage: "Add a typed field to the request body (key=value, @file, or @- for stdin)",
},
&cli.StringSliceFlag{
Name: "header",
Aliases: []string{"H"},
Usage: "Add a custom header (key:value)",
},
&cli.StringFlag{
Name: "data",
Aliases: []string{"d"},
Usage: "Raw JSON request body (use @file to read from file, @- for stdin)",
},
&cli.BoolFlag{
Name: "include",
Aliases: []string{"i"},
Usage: "Include HTTP status and response headers in output (written to stderr)",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Write response body to file instead of stdout (use '-' for stdout)",
},
}
}
// CmdApi represents the api command
var CmdApi = cli.Command{
Name: "api",
Category: catHelpers,
DisableSliceFlagSeparator: true,
Usage: "Make an authenticated API request",
Description: `Makes an authenticated HTTP request to the Gitea API and prints the response.
The endpoint argument is the path to the API endpoint, which will be prefixed
with /api/v1/ if it doesn't start with /api/ or http(s)://.
Placeholders like {owner} and {repo} in the endpoint will be replaced with
values from the current repository context.
Use -f for string fields and -F for typed fields (numbers, booleans, null).
With -F, prefix value with @ to read from file (@- for stdin). Values starting
with [ or { are parsed as JSON arrays/objects. Wrap values in quotes to force
string type (e.g., -F key="null" for literal string "null").
Use -d/--data to send a raw JSON body. Use @file to read from a file, or @-
to read from stdin. The -d flag cannot be combined with -f or -F.
When a request body is provided via -f, -F, or -d, the method defaults to POST
unless explicitly set with -X/--method.
Note: if your endpoint contains ? or &, quote it to prevent shell expansion
(e.g., '/repos/{owner}/{repo}/issues?state=open').`,
ArgsUsage: "<endpoint>",
Action: runApi,
Flags: append(apiFlags(), flags.LoginRepoFlags...),
}
type preparedAPIRequest struct {
Method string
Endpoint string
Headers map[string]string
Body []byte
}
func runApi(_ stdctx.Context, cmd *cli.Command) error {
ctx, err := context.InitCommand(cmd)
if err != nil {
return err
}
request, err := prepareAPIRequest(cmd, ctx)
if err != nil {
return err
}
var body io.Reader
if request.Body != nil {
body = bytes.NewReader(request.Body)
}
// Create API client and make request
client := api.NewClient(ctx.Login)
resp, err := client.Do(request.Method, request.Endpoint, body, request.Headers)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
fmt.Fprintf(os.Stderr, "warning: failed to close response body: %v\n", closeErr)
}
}()
// Print headers to stderr if requested (so redirects/pipes work correctly)
if cmd.Bool("include") {
fmt.Fprintf(os.Stderr, "%s %s\n", resp.Proto, resp.Status)
for key, values := range resp.Header {
for _, value := range values {
fmt.Fprintf(os.Stderr, "%s: %s\n", key, value)
}
}
fmt.Fprintln(os.Stderr)
}
// Determine output destination
outputPath := cmd.String("output")
forceStdout := outputPath == "-"
outputToStdout := outputPath == "" || forceStdout
// Check for binary output to terminal (skip warning if user explicitly forced stdout)
if outputToStdout && !forceStdout && term.IsTerminal(int(os.Stdout.Fd())) && !isTextContentType(resp.Header.Get("Content-Type")) {
fmt.Fprintln(os.Stderr, "Warning: Binary output detected. Use '-o <file>' to save to a file,")
fmt.Fprintln(os.Stderr, "or '-o -' to force output to terminal.")
return nil
}
var output io.Writer = os.Stdout
if !outputToStdout {
file, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("failed to create output file: %w", err)
}
defer func() {
if closeErr := file.Close(); closeErr != nil {
fmt.Fprintf(os.Stderr, "warning: failed to close output file: %v\n", closeErr)
}
}()
output = file
}
// Copy response body to output
_, err = io.Copy(output, resp.Body)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)
}
// Add newline for better terminal display
if outputToStdout && term.IsTerminal(int(os.Stdout.Fd())) {
fmt.Println()
}
return nil
}
func prepareAPIRequest(cmd *cli.Command, ctx *context.TeaContext) (*preparedAPIRequest, error) {
var err error
// Get the endpoint argument
if cmd.NArg() < 1 {
return nil, fmt.Errorf("endpoint argument required")
}
endpoint := cmd.Args().First()
// Expand placeholders in endpoint
endpoint = expandPlaceholders(endpoint, ctx)
// Parse headers
headers := make(map[string]string)
for _, h := range cmd.StringSlice("header") {
parts := strings.SplitN(h, ":", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid header format: %q (expected key:value)", h)
}
headers[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
// Build request body from fields
var bodyBytes []byte
stringFields := cmd.StringSlice("field")
typedFields := cmd.StringSlice("Field")
dataRaw := cmd.String("data")
if dataRaw != "" && (len(stringFields) > 0 || len(typedFields) > 0) {
return nil, fmt.Errorf("--data/-d cannot be combined with --field/-f or --Field/-F")
}
if dataRaw != "" {
var dataBytes []byte
var dataSource string
if strings.HasPrefix(dataRaw, "@") {
filename := dataRaw[1:]
if filename == "-" {
dataBytes, err = io.ReadAll(os.Stdin)
dataSource = "stdin"
} else {
dataBytes, err = os.ReadFile(filename)
dataSource = filename
}
if err != nil {
return nil, fmt.Errorf("failed to read %q: %w", dataRaw, err)
}
} else {
dataBytes = []byte(dataRaw)
}
if !json.Valid(dataBytes) {
if dataSource != "" {
return nil, fmt.Errorf("--data/-d value from %s is not valid JSON", dataSource)
}
return nil, fmt.Errorf("--data/-d value is not valid JSON")
}
bodyBytes = dataBytes
} else if len(stringFields) > 0 || len(typedFields) > 0 {
bodyMap := make(map[string]any)
// Process string fields (-f)
for _, f := range stringFields {
parts := strings.SplitN(f, "=", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid field format: %q (expected key=value)", f)
}
key := parts[0]
if key == "" {
return nil, fmt.Errorf("field key cannot be empty in %q", f)
}
if _, exists := bodyMap[key]; exists {
return nil, fmt.Errorf("duplicate field key %q", key)
}
bodyMap[key] = parts[1]
}
// Process typed fields (-F)
for _, f := range typedFields {
parts := strings.SplitN(f, "=", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid field format: %q (expected key=value)", f)
}
key := parts[0]
if key == "" {
return nil, fmt.Errorf("field key cannot be empty in %q", f)
}
if _, exists := bodyMap[key]; exists {
return nil, fmt.Errorf("duplicate field key %q", key)
}
value := parts[1]
parsedValue, err := parseTypedValue(value)
if err != nil {
return nil, fmt.Errorf("failed to parse field %q: %w", key, err)
}
bodyMap[key] = parsedValue
}
bodyBytes, err = json.Marshal(bodyMap)
if err != nil {
return nil, fmt.Errorf("failed to encode request body: %w", err)
}
}
method := strings.ToUpper(cmd.String("method"))
if !cmd.IsSet("method") {
if bodyBytes != nil {
method = "POST"
} else {
method = "GET"
}
}
return &preparedAPIRequest{
Method: method,
Endpoint: endpoint,
Headers: headers,
Body: bodyBytes,
}, nil
}
// parseTypedValue parses a value for -F flag, handling:
// - @filename: read content from file
// - @-: read content from stdin
// - "quoted": literal string (prevents type parsing)
// - true/false: boolean
// - null: nil
// - numbers: int or float
// - []/{}: JSON arrays/objects
// - otherwise: string
func parseTypedValue(value string) (any, error) {
// Handle file references.
// Note: if multiple fields use @- (stdin), only the first will get data;
// subsequent reads will return empty since stdin is consumed once.
if strings.HasPrefix(value, "@") {
filename := value[1:]
var content []byte
var err error
if filename == "-" {
content, err = io.ReadAll(os.Stdin)
} else {
content, err = os.ReadFile(filename)
}
if err != nil {
return nil, fmt.Errorf("failed to read %q: %w", value, err)
}
return strings.TrimSuffix(string(content), "\n"), nil
}
// Handle quoted strings (literal strings, no type parsing).
// Uses strconv.Unquote so escape sequences like \" are handled correctly.
if len(value) >= 2 && value[0] == '"' && value[len(value)-1] == '"' {
unquoted, err := strconv.Unquote(value)
if err != nil {
return nil, fmt.Errorf("invalid quoted string %s: %w", value, err)
}
return unquoted, nil
}
// Handle null
if value == "null" {
return nil, nil
}
// Handle booleans
if value == "true" {
return true, nil
}
if value == "false" {
return false, nil
}
// Handle integers
if i, err := strconv.ParseInt(value, 10, 64); err == nil {
return i, nil
}
// Handle floats
if f, err := strconv.ParseFloat(value, 64); err == nil {
return f, nil
}
// Handle JSON arrays and objects
if len(value) > 0 && (value[0] == '[' || value[0] == '{') {
var jsonVal any
if err := json.Unmarshal([]byte(value), &jsonVal); err == nil {
return jsonVal, nil
}
}
// Default to string
return value, nil
}
// isTextContentType returns true if the content type indicates text data
func isTextContentType(contentType string) bool {
if contentType == "" {
return true // assume text if unknown
}
contentType = strings.ToLower(strings.Split(contentType, ";")[0]) // strip charset
return strings.HasPrefix(contentType, "text/") ||
strings.Contains(contentType, "json") ||
strings.Contains(contentType, "xml") ||
strings.Contains(contentType, "javascript") ||
strings.Contains(contentType, "yaml") ||
strings.Contains(contentType, "toml")
}
// expandPlaceholders replaces {owner}, {repo}, and {branch} in the endpoint
func expandPlaceholders(endpoint string, ctx *context.TeaContext) string {
endpoint = strings.ReplaceAll(endpoint, "{owner}", ctx.Owner)
endpoint = strings.ReplaceAll(endpoint, "{repo}", ctx.Repo)
// Get current branch if available
if ctx.LocalRepo != nil {
if branch, err := ctx.LocalRepo.Head(); err == nil {
branchName := branch.Name().Short()
endpoint = strings.ReplaceAll(endpoint, "{branch}", branchName)
}
}
return endpoint
}

635
cmd/api_test.go Normal file
View File

@@ -0,0 +1,635 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
stdctx "context"
"encoding/json"
"io"
"os"
"path/filepath"
"testing"
"code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/context"
tea_git "code.gitea.io/tea/modules/git"
gogit "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v3"
)
func TestParseTypedValue(t *testing.T) {
t.Run("null", func(t *testing.T) {
v, err := parseTypedValue("null")
require.NoError(t, err)
assert.Nil(t, v)
})
t.Run("bool true", func(t *testing.T) {
v, err := parseTypedValue("true")
require.NoError(t, err)
assert.Equal(t, true, v)
})
t.Run("bool false", func(t *testing.T) {
v, err := parseTypedValue("false")
require.NoError(t, err)
assert.Equal(t, false, v)
})
t.Run("integer", func(t *testing.T) {
v, err := parseTypedValue("42")
require.NoError(t, err)
assert.Equal(t, int64(42), v)
})
t.Run("float", func(t *testing.T) {
v, err := parseTypedValue("3.14")
require.NoError(t, err)
assert.Equal(t, 3.14, v)
})
t.Run("string", func(t *testing.T) {
v, err := parseTypedValue("hello")
require.NoError(t, err)
assert.Equal(t, "hello", v)
})
t.Run("JSON array", func(t *testing.T) {
v, err := parseTypedValue("[1,2,3]")
require.NoError(t, err)
assert.Equal(t, []any{float64(1), float64(2), float64(3)}, v)
})
t.Run("JSON object", func(t *testing.T) {
v, err := parseTypedValue(`{"key":"val"}`)
require.NoError(t, err)
assert.Equal(t, map[string]any{"key": "val"}, v)
})
t.Run("invalid JSON array falls back to string", func(t *testing.T) {
v, err := parseTypedValue("[not json")
require.NoError(t, err)
assert.Equal(t, "[not json", v)
})
t.Run("invalid JSON object falls back to string", func(t *testing.T) {
v, err := parseTypedValue("{not json")
require.NoError(t, err)
assert.Equal(t, "{not json", v)
})
t.Run("file reference", func(t *testing.T) {
tmpFile := filepath.Join(t.TempDir(), "test.txt")
require.NoError(t, os.WriteFile(tmpFile, []byte("file content\n"), 0o644))
v, err := parseTypedValue("@" + tmpFile)
require.NoError(t, err)
assert.Equal(t, "file content", v)
})
t.Run("file reference without trailing newline", func(t *testing.T) {
tmpFile := filepath.Join(t.TempDir(), "test.txt")
require.NoError(t, os.WriteFile(tmpFile, []byte("no newline"), 0o644))
v, err := parseTypedValue("@" + tmpFile)
require.NoError(t, err)
assert.Equal(t, "no newline", v)
})
t.Run("empty file reference", func(t *testing.T) {
tmpFile := filepath.Join(t.TempDir(), "empty.txt")
require.NoError(t, os.WriteFile(tmpFile, []byte(""), 0o644))
v, err := parseTypedValue("@" + tmpFile)
require.NoError(t, err)
assert.Equal(t, "", v)
})
t.Run("nonexistent file reference", func(t *testing.T) {
_, err := parseTypedValue("@/nonexistent/file.txt")
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to read")
})
t.Run("negative integer", func(t *testing.T) {
v, err := parseTypedValue("-42")
require.NoError(t, err)
assert.Equal(t, int64(-42), v)
})
t.Run("negative float", func(t *testing.T) {
v, err := parseTypedValue("-3.14")
require.NoError(t, err)
assert.Equal(t, -3.14, v)
})
t.Run("scientific notation", func(t *testing.T) {
v, err := parseTypedValue("1.5e10")
require.NoError(t, err)
assert.Equal(t, 1.5e10, v)
})
t.Run("empty string", func(t *testing.T) {
v, err := parseTypedValue("")
require.NoError(t, err)
assert.Equal(t, "", v)
})
t.Run("string starting with number", func(t *testing.T) {
v, err := parseTypedValue("123abc")
require.NoError(t, err)
assert.Equal(t, "123abc", v)
})
t.Run("nested JSON object", func(t *testing.T) {
v, err := parseTypedValue(`{"user":{"name":"alice","id":1}}`)
require.NoError(t, err)
expected := map[string]any{
"user": map[string]any{
"name": "alice",
"id": float64(1),
},
}
assert.Equal(t, expected, v)
})
t.Run("complex JSON array", func(t *testing.T) {
v, err := parseTypedValue(`[{"id":1},{"id":2}]`)
require.NoError(t, err)
expected := []any{
map[string]any{"id": float64(1)},
map[string]any{"id": float64(2)},
}
assert.Equal(t, expected, v)
})
t.Run("quoted string prevents type parsing", func(t *testing.T) {
v, err := parseTypedValue(`"null"`)
require.NoError(t, err)
assert.Equal(t, "null", v)
})
t.Run("quoted true becomes string", func(t *testing.T) {
v, err := parseTypedValue(`"true"`)
require.NoError(t, err)
assert.Equal(t, "true", v)
})
t.Run("quoted false becomes string", func(t *testing.T) {
v, err := parseTypedValue(`"false"`)
require.NoError(t, err)
assert.Equal(t, "false", v)
})
t.Run("quoted number becomes string", func(t *testing.T) {
v, err := parseTypedValue(`"123"`)
require.NoError(t, err)
assert.Equal(t, "123", v)
})
t.Run("quoted empty string", func(t *testing.T) {
v, err := parseTypedValue(`""`)
require.NoError(t, err)
assert.Equal(t, "", v)
})
t.Run("quoted string with spaces", func(t *testing.T) {
v, err := parseTypedValue(`"hello world"`)
require.NoError(t, err)
assert.Equal(t, "hello world", v)
})
t.Run("single quote not treated as quote", func(t *testing.T) {
v, err := parseTypedValue(`'hello'`)
require.NoError(t, err)
assert.Equal(t, "'hello'", v)
})
t.Run("unmatched quote at start only", func(t *testing.T) {
v, err := parseTypedValue(`"hello`)
require.NoError(t, err)
assert.Equal(t, `"hello`, v)
})
t.Run("unmatched quote at end only", func(t *testing.T) {
v, err := parseTypedValue(`hello"`)
require.NoError(t, err)
assert.Equal(t, `hello"`, v)
})
t.Run("quoted string with escaped quote", func(t *testing.T) {
v, err := parseTypedValue(`"hello \"world\""`)
require.NoError(t, err)
assert.Equal(t, `hello "world"`, v)
})
t.Run("quoted string with backslash-n", func(t *testing.T) {
v, err := parseTypedValue(`"line1\nline2"`)
require.NoError(t, err)
assert.Equal(t, "line1\nline2", v)
})
t.Run("quoted string with tab escape", func(t *testing.T) {
v, err := parseTypedValue(`"col1\tcol2"`)
require.NoError(t, err)
assert.Equal(t, "col1\tcol2", v)
})
t.Run("quoted string with backslash", func(t *testing.T) {
v, err := parseTypedValue(`"path\\to\\file"`)
require.NoError(t, err)
assert.Equal(t, `path\to\file`, v)
})
t.Run("invalid escape sequence in quoted string", func(t *testing.T) {
_, err := parseTypedValue(`"bad \z escape"`)
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid quoted string")
})
}
// runApiWithArgs configures a test login, parses the command line, and captures
// the prepared request without opening sockets or making HTTP requests.
func runApiWithArgs(t *testing.T, args []string) (method string, body []byte, err error) {
t.Helper()
var capturedMethod string
var capturedBody []byte
config.SetConfigForTesting(config.LocalConfig{
Logins: []config.Login{{
Name: "testLogin",
URL: "https://gitea.example.com",
Token: "test-token",
User: "testUser",
Default: true,
}},
})
// Use the apiFlags factory to get fresh flag instances, avoiding shared
// hasBeenSet state between tests. Append minimal login/repo flags needed
// for the test harness.
cmd := cli.Command{
Name: "api",
DisableSliceFlagSeparator: true,
Action: func(_ stdctx.Context, cmd *cli.Command) error {
ctx, err := context.InitCommand(cmd)
if err != nil {
return err
}
request, err := prepareAPIRequest(cmd, ctx)
if err != nil {
return err
}
capturedMethod = request.Method
capturedBody = append([]byte(nil), request.Body...)
return nil
},
Flags: append(apiFlags(), []cli.Flag{
&cli.StringFlag{Name: "login", Aliases: []string{"l"}},
&cli.StringFlag{Name: "repo", Aliases: []string{"r"}},
&cli.StringFlag{Name: "remote", Aliases: []string{"R"}},
}...),
Writer: io.Discard,
ErrWriter: io.Discard,
}
fullArgs := append([]string{"api", "--login", "testLogin"}, args...)
runErr := cmd.Run(stdctx.Background(), fullArgs)
return capturedMethod, capturedBody, runErr
}
func TestApiCommaInFieldValue(t *testing.T) {
_, body, err := runApiWithArgs(t, []string{"-f", "body=hello, world", "-X", "POST", "/test"})
require.NoError(t, err)
var parsed map[string]any
require.NoError(t, json.Unmarshal(body, &parsed))
assert.Equal(t, "hello, world", parsed["body"])
}
func TestApiRawDataFlag(t *testing.T) {
_, body, err := runApiWithArgs(t, []string{"-d", `{"title":"test","body":"hello"}`, "/test"})
require.NoError(t, err)
var parsed map[string]any
require.NoError(t, json.Unmarshal(body, &parsed))
assert.Equal(t, "test", parsed["title"])
assert.Equal(t, "hello", parsed["body"])
}
func TestApiDataFieldMutualExclusion(t *testing.T) {
_, _, err := runApiWithArgs(t, []string{"-d", `{"title":"test"}`, "-f", "key=val", "/test"})
require.Error(t, err)
assert.Contains(t, err.Error(), "--data/-d cannot be combined with --field/-f or --Field/-F")
}
func TestApiMethodAutoDefault(t *testing.T) {
t.Run("POST when body provided without explicit method", func(t *testing.T) {
method, _, err := runApiWithArgs(t, []string{"-d", `{"title":"test"}`, "/test"})
require.NoError(t, err)
assert.Equal(t, "POST", method)
})
t.Run("explicit method overrides auto-POST", func(t *testing.T) {
method, _, err := runApiWithArgs(t, []string{"-d", `{"title":"test"}`, "-X", "PATCH", "/test"})
require.NoError(t, err)
assert.Equal(t, "PATCH", method)
})
t.Run("GET when no body", func(t *testing.T) {
method, _, err := runApiWithArgs(t, []string{"/test"})
require.NoError(t, err)
assert.Equal(t, "GET", method)
})
}
func TestApiMultipleFields(t *testing.T) {
t.Run("multiple -f flags", func(t *testing.T) {
_, body, err := runApiWithArgs(t, []string{
"-f", "title=Test Issue",
"-f", "body=Description here",
"-X", "POST",
"/test",
})
require.NoError(t, err)
var parsed map[string]any
require.NoError(t, json.Unmarshal(body, &parsed))
assert.Equal(t, "Test Issue", parsed["title"])
assert.Equal(t, "Description here", parsed["body"])
})
t.Run("multiple -F flags with different types", func(t *testing.T) {
_, body, err := runApiWithArgs(t, []string{
"-F", "milestone=5",
"-F", "closed=true",
"-F", "title=Test",
"-X", "POST",
"/test",
})
require.NoError(t, err)
var parsed map[string]any
require.NoError(t, json.Unmarshal(body, &parsed))
assert.Equal(t, float64(5), parsed["milestone"])
assert.Equal(t, true, parsed["closed"])
assert.Equal(t, "Test", parsed["title"])
})
t.Run("combining -f and -F flags", func(t *testing.T) {
_, body, err := runApiWithArgs(t, []string{
"-f", "title=Test",
"-F", "milestone=3",
"-F", "closed=false",
"-X", "POST",
"/test",
})
require.NoError(t, err)
var parsed map[string]any
require.NoError(t, json.Unmarshal(body, &parsed))
assert.Equal(t, "Test", parsed["title"])
assert.Equal(t, float64(3), parsed["milestone"])
assert.Equal(t, false, parsed["closed"])
})
t.Run("-F with JSON array", func(t *testing.T) {
_, body, err := runApiWithArgs(t, []string{
"-F", `labels=["bug","enhancement"]`,
"-X", "POST",
"/test",
})
require.NoError(t, err)
var parsed map[string]any
require.NoError(t, json.Unmarshal(body, &parsed))
assert.Equal(t, []any{"bug", "enhancement"}, parsed["labels"])
})
t.Run("-F with JSON object", func(t *testing.T) {
_, body, err := runApiWithArgs(t, []string{
"-F", `assignee={"login":"alice","id":123}`,
"-X", "POST",
"/test",
})
require.NoError(t, err)
var parsed map[string]any
require.NoError(t, json.Unmarshal(body, &parsed))
assignee, ok := parsed["assignee"].(map[string]any)
require.True(t, ok)
assert.Equal(t, "alice", assignee["login"])
assert.Equal(t, float64(123), assignee["id"])
})
t.Run("-F with quoted string to prevent type parsing", func(t *testing.T) {
_, body, err := runApiWithArgs(t, []string{
"-F", `status="null"`,
"-F", `enabled="true"`,
"-F", `count="42"`,
"-X", "POST",
"/test",
})
require.NoError(t, err)
var parsed map[string]any
require.NoError(t, json.Unmarshal(body, &parsed))
assert.Equal(t, "null", parsed["status"])
assert.Equal(t, "true", parsed["enabled"])
assert.Equal(t, "42", parsed["count"])
})
}
func TestApiDataFromFile(t *testing.T) {
t.Run("read JSON from file", func(t *testing.T) {
tmpFile := filepath.Join(t.TempDir(), "data.json")
jsonData := `{"title":"From File","body":"File content"}`
require.NoError(t, os.WriteFile(tmpFile, []byte(jsonData), 0o644))
_, body, err := runApiWithArgs(t, []string{"-d", "@" + tmpFile, "/test"})
require.NoError(t, err)
var parsed map[string]any
require.NoError(t, json.Unmarshal(body, &parsed))
assert.Equal(t, "From File", parsed["title"])
assert.Equal(t, "File content", parsed["body"])
})
t.Run("invalid JSON in --data flag", func(t *testing.T) {
_, _, err := runApiWithArgs(t, []string{"-d", `{invalid json}`, "/test"})
require.Error(t, err)
assert.Contains(t, err.Error(), "not valid JSON")
})
t.Run("invalid JSON from file includes filename", func(t *testing.T) {
tmpFile := filepath.Join(t.TempDir(), "bad.json")
require.NoError(t, os.WriteFile(tmpFile, []byte("not json"), 0o644))
_, _, err := runApiWithArgs(t, []string{"-d", "@" + tmpFile, "/test"})
require.Error(t, err)
assert.Contains(t, err.Error(), "not valid JSON")
assert.Contains(t, err.Error(), "bad.json")
})
}
func TestApiErrorHandling(t *testing.T) {
t.Run("missing endpoint argument", func(t *testing.T) {
_, _, err := runApiWithArgs(t, []string{})
require.Error(t, err)
assert.Contains(t, err.Error(), "endpoint argument required")
})
t.Run("invalid field format", func(t *testing.T) {
_, _, err := runApiWithArgs(t, []string{"-f", "invalidformat", "-X", "POST", "/test"})
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid field format")
})
t.Run("invalid Field format", func(t *testing.T) {
_, _, err := runApiWithArgs(t, []string{"-F", "noequalsign", "-X", "POST", "/test"})
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid field format")
})
t.Run("empty field key with -f", func(t *testing.T) {
_, _, err := runApiWithArgs(t, []string{"-f", "=value", "-X", "POST", "/test"})
require.Error(t, err)
assert.Contains(t, err.Error(), "field key cannot be empty")
})
t.Run("empty field key with -F", func(t *testing.T) {
_, _, err := runApiWithArgs(t, []string{"-F", "=123", "-X", "POST", "/test"})
require.Error(t, err)
assert.Contains(t, err.Error(), "field key cannot be empty")
})
t.Run("duplicate field key in -f flags", func(t *testing.T) {
_, _, err := runApiWithArgs(t, []string{"-f", "key=first", "-f", "key=second", "-X", "POST", "/test"})
require.Error(t, err)
assert.Contains(t, err.Error(), "duplicate field key")
})
t.Run("duplicate field key in -F flags", func(t *testing.T) {
_, _, err := runApiWithArgs(t, []string{"-F", "key=1", "-F", "key=2", "-X", "POST", "/test"})
require.Error(t, err)
assert.Contains(t, err.Error(), "duplicate field key")
})
t.Run("duplicate field key across -f and -F flags", func(t *testing.T) {
_, _, err := runApiWithArgs(t, []string{"-f", "key=string", "-F", "key=123", "-X", "POST", "/test"})
require.Error(t, err)
assert.Contains(t, err.Error(), "duplicate field key")
})
}
func TestExpandPlaceholders(t *testing.T) {
t.Run("replaces owner and repo", func(t *testing.T) {
ctx := &context.TeaContext{
Owner: "myorg",
Repo: "myrepo",
}
result := expandPlaceholders("/repos/{owner}/{repo}/issues", ctx)
assert.Equal(t, "/repos/myorg/myrepo/issues", result)
})
t.Run("replaces multiple occurrences", func(t *testing.T) {
ctx := &context.TeaContext{
Owner: "alice",
Repo: "proj",
}
result := expandPlaceholders("/repos/{owner}/{repo}/branches?owner={owner}", ctx)
assert.Equal(t, "/repos/alice/proj/branches?owner=alice", result)
})
t.Run("no placeholders returns unchanged", func(t *testing.T) {
ctx := &context.TeaContext{
Owner: "alice",
Repo: "proj",
}
result := expandPlaceholders("/api/v1/version", ctx)
assert.Equal(t, "/api/v1/version", result)
})
t.Run("empty owner and repo produce empty replacements", func(t *testing.T) {
ctx := &context.TeaContext{}
result := expandPlaceholders("/repos/{owner}/{repo}", ctx)
assert.Equal(t, "/repos//", result)
})
t.Run("branch left unreplaced when no local repo", func(t *testing.T) {
ctx := &context.TeaContext{
Owner: "alice",
Repo: "proj",
}
result := expandPlaceholders("/repos/{owner}/{repo}/branches/{branch}", ctx)
assert.Equal(t, "/repos/alice/proj/branches/{branch}", result)
})
t.Run("replaces branch from local repo HEAD", func(t *testing.T) {
tmpDir := t.TempDir()
repo, err := gogit.PlainInit(tmpDir, false)
require.NoError(t, err)
// Create an initial commit so HEAD points to a branch.
wt, err := repo.Worktree()
require.NoError(t, err)
tmpFile := filepath.Join(tmpDir, "init.txt")
require.NoError(t, os.WriteFile(tmpFile, []byte("init"), 0o644))
_, err = wt.Add("init.txt")
require.NoError(t, err)
_, err = wt.Commit("initial commit", &gogit.CommitOptions{
Author: &object.Signature{Name: "test", Email: "test@test.com"},
})
require.NoError(t, err)
// Create and checkout a feature branch.
headRef, err := repo.Head()
require.NoError(t, err)
branchRef := plumbing.NewBranchReferenceName("feature/my-branch")
ref := plumbing.NewHashReference(branchRef, headRef.Hash())
require.NoError(t, repo.Storer.SetReference(ref))
require.NoError(t, wt.Checkout(&gogit.CheckoutOptions{Branch: branchRef}))
ctx := &context.TeaContext{
Owner: "alice",
Repo: "proj",
LocalRepo: &tea_git.TeaRepo{Repository: repo},
}
result := expandPlaceholders("/repos/{owner}/{repo}/branches/{branch}", ctx)
assert.Equal(t, "/repos/alice/proj/branches/feature/my-branch", result)
})
}
func TestIsTextContentType(t *testing.T) {
tests := []struct {
name string
contentType string
want bool
}{
{"empty string defaults to text", "", true},
{"plain text", "text/plain", true},
{"html", "text/html", true},
{"json", "application/json", true},
{"json with charset", "application/json; charset=utf-8", true},
{"xml", "application/xml", true},
{"javascript", "application/javascript", true},
{"yaml", "application/yaml", true},
{"toml", "application/toml", true},
{"binary", "application/octet-stream", false},
{"image", "image/png", false},
{"pdf", "application/pdf", false},
{"zip", "application/zip", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isTextContentType(tt.contentType)
assert.Equal(t, tt.want, got)
})
}
}

28
cmd/attachments.go Normal file
View File

@@ -0,0 +1,28 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"code.gitea.io/tea/cmd/attachments"
"code.gitea.io/tea/cmd/flags"
"github.com/urfave/cli/v3"
)
// CmdReleaseAttachments represents a release attachment (file attachment)
var CmdReleaseAttachments = cli.Command{
Name: "assets",
Aliases: []string{"asset", "a"},
Category: catEntities,
Usage: "Manage release assets",
Description: "Manage release assets",
ArgsUsage: " ", // command does not accept arguments
Action: attachments.RunReleaseAttachmentList,
Commands: []*cli.Command{
&attachments.CmdReleaseAttachmentList,
&attachments.CmdReleaseAttachmentCreate,
&attachments.CmdReleaseAttachmentDelete,
},
Flags: flags.AllDefaultFlags,
}

71
cmd/attachments/create.go Normal file
View File

@@ -0,0 +1,71 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package attachments
import (
stdctx "context"
"fmt"
"os"
"path/filepath"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/cmd/releases"
"code.gitea.io/tea/modules/context"
"github.com/urfave/cli/v3"
)
// CmdReleaseAttachmentCreate represents a sub command of Release Attachments to create a release attachment
var CmdReleaseAttachmentCreate = cli.Command{
Name: "create",
Aliases: []string{"c"},
Usage: "Create one or more release attachments",
Description: `Create one or more release attachments`,
ArgsUsage: "<release-tag> <asset> [<asset>...]",
Action: runReleaseAttachmentCreate,
Flags: flags.AllDefaultFlags,
}
func runReleaseAttachmentCreate(_ stdctx.Context, cmd *cli.Command) error {
ctx, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := ctx.Login.Client()
if ctx.Args().Len() < 2 {
return fmt.Errorf("no release tag or assets specified.\nUsage:\t%s", ctx.Command.UsageText)
}
tag := ctx.Args().First()
if len(tag) == 0 {
return fmt.Errorf("release tag needed to create attachment")
}
release, err := releases.GetReleaseByTag(ctx.Owner, ctx.Repo, tag, client)
if err != nil {
return err
}
for _, asset := range ctx.Args().Slice()[1:] {
var file *os.File
if file, err = os.Open(asset); err != nil {
return err
}
filePath := filepath.Base(asset)
if _, _, err = ctx.Login.Client().CreateReleaseAttachment(ctx.Owner, ctx.Repo, release.ID, file, filePath); err != nil {
file.Close()
return err
}
file.Close()
}
return nil
}

89
cmd/attachments/delete.go Normal file
View File

@@ -0,0 +1,89 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package attachments
import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/cmd/releases"
"code.gitea.io/tea/modules/context"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
)
// CmdReleaseAttachmentDelete represents a sub command of Release Attachments to delete a release attachment
var CmdReleaseAttachmentDelete = cli.Command{
Name: "delete",
Aliases: []string{"rm"},
Usage: "Delete one or more release attachments",
Description: `Delete one or more release attachments`,
ArgsUsage: "<release tag> <attachment name> [<attachment name>...]",
Action: runReleaseAttachmentDelete,
Flags: append([]cli.Flag{
&cli.BoolFlag{
Name: "confirm",
Aliases: []string{"y"},
Usage: "Confirm deletion (required)",
},
}, flags.AllDefaultFlags...),
}
func runReleaseAttachmentDelete(_ stdctx.Context, cmd *cli.Command) error {
ctx, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := ctx.Login.Client()
if ctx.Args().Len() < 2 {
return fmt.Errorf("no release tag or attachment names specified.\nUsage:\t%s", ctx.Command.UsageText)
}
tag := ctx.Args().First()
if len(tag) == 0 {
return fmt.Errorf("release tag needed to delete attachment")
}
if !ctx.Bool("confirm") {
fmt.Println("Are you sure? Please confirm with -y or --confirm.")
return nil
}
release, err := releases.GetReleaseByTag(ctx.Owner, ctx.Repo, tag, client)
if err != nil {
return err
}
existing, _, err := client.ListReleaseAttachments(ctx.Owner, ctx.Repo, release.ID, gitea.ListReleaseAttachmentsOptions{
ListOptions: gitea.ListOptions{Page: -1},
})
if err != nil {
return err
}
for _, name := range ctx.Args().Slice()[1:] {
var attachment *gitea.Attachment
for _, a := range existing {
if a.Name == name {
attachment = a
}
}
if attachment == nil {
return fmt.Errorf("release does not have attachment named '%s'", name)
}
_, err = client.DeleteReleaseAttachment(ctx.Owner, ctx.Repo, release.ID, attachment.ID)
if err != nil {
return err
}
}
return nil
}

62
cmd/attachments/list.go Normal file
View File

@@ -0,0 +1,62 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package attachments
import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/cmd/releases"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
)
// CmdReleaseAttachmentList represents a sub command of release attachment to list release attachments
var CmdReleaseAttachmentList = cli.Command{
Name: "list",
Aliases: []string{"ls"},
Usage: "List Release Attachments",
Description: "List Release Attachments",
ArgsUsage: "<release-tag>", // command does not accept arguments
Action: RunReleaseAttachmentList,
Flags: append([]cli.Flag{
&flags.PaginationPageFlag,
&flags.PaginationLimitFlag,
}, flags.AllDefaultFlags...),
}
// RunReleaseAttachmentList list release attachments
func RunReleaseAttachmentList(_ stdctx.Context, cmd *cli.Command) error {
ctx, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := ctx.Login.Client()
tag := ctx.Args().First()
if len(tag) == 0 {
return fmt.Errorf("release tag needed to list attachments")
}
release, err := releases.GetReleaseByTag(ctx.Owner, ctx.Repo, tag, client)
if err != nil {
return err
}
attachments, _, err := ctx.Login.Client().ListReleaseAttachments(ctx.Owner, ctx.Repo, release.ID, gitea.ListReleaseAttachmentsOptions{
ListOptions: flags.GetListOptions(cmd),
})
if err != nil {
return err
}
return print.ReleaseAttachmentsList(attachments, ctx.Output)
}

View File

@@ -1,138 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
"fmt"
"io"
"net/http"
"os"
"os/exec"
"github.com/adrg/xdg"
"github.com/urfave/cli/v2"
)
// CmdAutocomplete manages autocompletion
var CmdAutocomplete = cli.Command{
Name: "shellcompletion",
Aliases: []string{"autocomplete"},
Category: catSetup,
Usage: "Install shell completion for tea",
Description: "Install shell completion for tea",
ArgsUsage: "<shell type> (bash, zsh, powershell, fish)",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "install",
Usage: "Persist in shell config instead of printing commands",
},
},
Action: runAutocompleteAdd,
}
func runAutocompleteAdd(ctx *cli.Context) error {
var remoteFile, localFile, cmds string
shell := ctx.Args().First()
switch shell {
case "zsh":
remoteFile = "contrib/autocomplete.zsh"
localFile = "autocomplete.zsh"
cmds = "echo 'PROG=tea _CLI_ZSH_AUTOCOMPLETE_HACK=1 source \"%s\"' >> ~/.zshrc && source ~/.zshrc"
case "bash":
remoteFile = "contrib/autocomplete.sh"
localFile = "autocomplete.sh"
cmds = "echo 'PROG=tea source \"%s\"' >> ~/.bashrc && source ~/.bashrc"
case "powershell":
remoteFile = "contrib/autocomplete.ps1"
localFile = "tea.ps1"
cmds = "\"& %s\" >> $profile"
case "fish":
// fish is different, in that urfave/cli provides a generator for the shell script needed.
// this also means that the fish completion can become out of sync with the tea binary!
// writing to this directory suffices, as fish reads files there on startup, no cmds needed.
return writeFishAutoCompleteFile(ctx)
default:
return fmt.Errorf("Must specify valid %s", ctx.Command.ArgsUsage)
}
localPath, err := xdg.ConfigFile("tea/" + localFile)
if err != nil {
return err
}
cmds = fmt.Sprintf(cmds, localPath)
if err = writeRemoteAutoCompleteFile(remoteFile, localPath); err != nil {
return err
}
if ctx.Bool("install") {
fmt.Println("Installing in your shellrc")
installer := exec.Command(shell, "-c", cmds)
if shell == "powershell" {
installer = exec.Command("powershell.exe", "-Command", cmds)
}
out, err := installer.CombinedOutput()
if err != nil {
return fmt.Errorf("Couldn't run the commands: %s %s", err, out)
}
} else {
fmt.Println("\n# Run the following commands to install autocompletion (or use --install)")
fmt.Println(cmds)
}
return nil
}
func writeRemoteAutoCompleteFile(file, destPath string) error {
url := fmt.Sprintf("https://gitea.com/gitea/tea/raw/branch/master/%s", file)
fmt.Println("Fetching " + url)
res, err := http.Get(url)
if err != nil {
return err
}
defer res.Body.Close()
writer, err := os.Create(destPath)
if err != nil {
return err
}
defer writer.Close()
_, err = io.Copy(writer, res.Body)
return err
}
func writeFishAutoCompleteFile(ctx *cli.Context) error {
// NOTE: to make sure this file is in sync with tea commands, we'd need to
// - check if the file exists
// - if it does, check if the tea version that wrote it is the currently running version
// - if not, rewrite the file
// on each application run
// NOTE: this generates a completion that also suggests file names, which looks kinda messy..
script, err := ctx.App.ToFishCompletion()
if err != nil {
return err
}
localPath, err := xdg.ConfigFile("fish/conf.d/tea_completion.fish")
if err != nil {
return err
}
writer, err := os.Create(localPath)
if err != nil {
return err
}
if _, err = io.WriteString(writer, script); err != nil {
return err
}
fmt.Printf("Installed tab completion to %s\n", localPath)
return nil
}

39
cmd/branches.go Normal file
View File

@@ -0,0 +1,39 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"code.gitea.io/tea/cmd/branches"
"github.com/urfave/cli/v3"
)
// CmdBranches represents to login a gitea server.
var CmdBranches = cli.Command{
Name: "branches",
Aliases: []string{"branch", "b"},
Category: catEntities,
Usage: "Consult branches",
Description: `Lists branches when called without argument. If a branch is provided, will show it in detail.`,
ArgsUsage: "[<branch name>]",
Action: runBranches,
Commands: []*cli.Command{
&branches.CmdBranchesList,
&branches.CmdBranchesProtect,
&branches.CmdBranchesUnprotect,
&branches.CmdBranchesRename,
},
Flags: append([]cli.Flag{
&cli.BoolFlag{
Name: "comments",
Usage: "Whether to display comments (will prompt if not provided & run interactively)",
},
}, branches.CmdBranchesList.Flags...),
}
func runBranches(ctx context.Context, cmd *cli.Command) error {
return branches.RunBranchesList(ctx, cmd)
}

76
cmd/branches/list.go Normal file
View File

@@ -0,0 +1,76 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package branches
import (
stdctx "context"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
)
var branchFieldsFlag = flags.FieldsFlag(print.BranchFields, []string{
"name", "protected", "user-can-merge", "user-can-push",
})
// CmdBranchesListFlags Flags for command list
var CmdBranchesListFlags = append([]cli.Flag{
branchFieldsFlag,
&flags.PaginationPageFlag,
&flags.PaginationLimitFlag,
}, flags.AllDefaultFlags...)
// CmdBranchesList represents a sub command of branches to list branches
var CmdBranchesList = cli.Command{
Name: "list",
Aliases: []string{"ls"},
Usage: "List branches of the repository",
Description: `List branches of the repository`,
ArgsUsage: " ", // command does not accept arguments
Action: RunBranchesList,
Flags: CmdBranchesListFlags,
}
// RunBranchesList list branches
func RunBranchesList(_ stdctx.Context, cmd *cli.Command) error {
ctx, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
owner := ctx.Owner
if ctx.IsSet("owner") {
owner = ctx.String("owner")
}
var branches []*gitea.Branch
var protections []*gitea.BranchProtection
branches, _, err = ctx.Login.Client().ListRepoBranches(owner, ctx.Repo, gitea.ListRepoBranchesOptions{
ListOptions: flags.GetListOptions(cmd),
})
if err != nil {
return err
}
protections, _, err = ctx.Login.Client().ListBranchProtections(owner, ctx.Repo, gitea.ListBranchProtectionsOptions{
ListOptions: flags.GetListOptions(cmd),
})
if err != nil {
return err
}
fields, err := branchFieldsFlag.GetValues(cmd)
if err != nil {
return err
}
return print.BranchesList(branches, protections, ctx.Output, fields)
}

107
cmd/branches/protect.go Normal file
View File

@@ -0,0 +1,107 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package branches
import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
)
// CmdBranchesProtectFlags Flags for command protect/unprotect
var CmdBranchesProtectFlags = append([]cli.Flag{
branchFieldsFlag,
&flags.PaginationPageFlag,
&flags.PaginationLimitFlag,
}, flags.AllDefaultFlags...)
// CmdBranchesProtect represents a sub command of branches to protect a branch
var CmdBranchesProtect = cli.Command{
Name: "protect",
Aliases: []string{"P"},
Usage: "Protect branches",
Description: `Block actions push/merge on specified branches`,
ArgsUsage: "<branch>",
Action: RunBranchesProtect,
Flags: CmdBranchesProtectFlags,
}
// CmdBranchesUnprotect represents a sub command of branches to protect a branch
var CmdBranchesUnprotect = cli.Command{
Name: "unprotect",
Aliases: []string{"U"},
Usage: "Unprotect branches",
Description: `Suppress existing protections on specified branches`,
ArgsUsage: "<branch>",
Action: RunBranchesProtect,
Flags: CmdBranchesProtectFlags,
}
// RunBranchesProtect function to protect/unprotect a list of branches
func RunBranchesProtect(_ stdctx.Context, cmd *cli.Command) error {
ctx, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
if !cmd.Args().Present() {
return fmt.Errorf("must specify at least one branch")
}
owner := ctx.Owner
if ctx.IsSet("owner") {
owner = ctx.String("owner")
}
for _, branch := range ctx.Args().Slice() {
var err error
command := ctx.Command.Name
if command == "protect" {
_, _, err = ctx.Login.Client().CreateBranchProtection(owner, ctx.Repo, gitea.CreateBranchProtectionOption{
BranchName: branch,
RuleName: "",
EnablePush: false,
EnablePushWhitelist: false,
PushWhitelistUsernames: []string{},
PushWhitelistTeams: []string{},
PushWhitelistDeployKeys: false,
EnableMergeWhitelist: false,
MergeWhitelistUsernames: []string{},
MergeWhitelistTeams: []string{},
EnableStatusCheck: false,
StatusCheckContexts: []string{},
RequiredApprovals: 1,
EnableApprovalsWhitelist: false,
ApprovalsWhitelistUsernames: []string{},
ApprovalsWhitelistTeams: []string{},
BlockOnRejectedReviews: false,
BlockOnOfficialReviewRequests: false,
BlockOnOutdatedBranch: false,
DismissStaleApprovals: false,
RequireSignedCommits: false,
ProtectedFilePatterns: "",
UnprotectedFilePatterns: "",
})
} else if command == "unprotect" {
_, err = ctx.Login.Client().DeleteBranchProtection(owner, ctx.Repo, branch)
} else {
return fmt.Errorf("command %s is not supported", command)
}
if err != nil {
return err
}
}
return nil
}

78
cmd/branches/rename.go Normal file
View File

@@ -0,0 +1,78 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package branches
import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
)
// CmdBranchesRenameFlags Flags for command rename
var CmdBranchesRenameFlags = append([]cli.Flag{
branchFieldsFlag,
&flags.PaginationPageFlag,
&flags.PaginationLimitFlag,
}, flags.AllDefaultFlags...)
// CmdBranchesRename represents a sub command of branches to rename a branch
var CmdBranchesRename = cli.Command{
Name: "rename",
Aliases: []string{"rn"},
Usage: "Rename a branch",
Description: `Rename a branch in a repository`,
ArgsUsage: "<old_branch_name> <new_branch_name>",
Action: RunBranchesRename,
Flags: CmdBranchesRenameFlags,
}
// RunBranchesRename function to rename a branch
func RunBranchesRename(_ stdctx.Context, cmd *cli.Command) error {
ctx, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
if err := ValidateRenameArgs(ctx.Args().Slice()); err != nil {
return err
}
oldBranchName := ctx.Args().Get(0)
newBranchName := ctx.Args().Get(1)
owner := ctx.Owner
if ctx.IsSet("owner") {
owner = ctx.String("owner")
}
successful, _, err := ctx.Login.Client().RenameRepoBranch(owner, ctx.Repo, oldBranchName, gitea.RenameRepoBranchOption{
Name: newBranchName,
})
if err != nil {
return fmt.Errorf("failed to rename branch: %w", err)
}
if !successful {
return fmt.Errorf("failed to rename branch")
}
fmt.Printf("Successfully renamed branch '%s' to '%s'\n", oldBranchName, newBranchName)
return nil
}
// ValidateRenameArgs validates arguments for the rename command
func ValidateRenameArgs(args []string) error {
if len(args) != 2 {
return fmt.Errorf("must specify exactly two arguments: <old_branch_name> <new_branch_name>")
}
return nil
}

View File

@@ -0,0 +1,46 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package branches
import (
"testing"
)
func TestBranchesRenameArgs(t *testing.T) {
tests := []struct {
name string
args []string
wantErr bool
}{
{
name: "valid args",
args: []string{"main", "develop"},
wantErr: false,
},
{
name: "missing both args",
args: []string{},
wantErr: true,
},
{
name: "missing new branch name",
args: []string{"main"},
wantErr: true,
},
{
name: "too many args",
args: []string{"main", "develop", "extra"},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateRenameArgs(tt.args)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateRenameArgs() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@@ -1,6 +1,5 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package cmd package cmd

View File

@@ -1,21 +1,22 @@
// Copyright 2021 The Gitea Authors. All rights reserved. // Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package cmd package cmd
import ( import (
stdctx "context"
"fmt" "fmt"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/debug"
"code.gitea.io/tea/modules/git" "code.gitea.io/tea/modules/git"
"code.gitea.io/tea/modules/interact" "code.gitea.io/tea/modules/interact"
"code.gitea.io/tea/modules/task" "code.gitea.io/tea/modules/task"
"code.gitea.io/tea/modules/utils" "code.gitea.io/tea/modules/utils"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdRepoClone represents a sub command of repos to create a local copy // CmdRepoClone represents a sub command of repos to create a local copy
@@ -46,18 +47,21 @@ When a host is specified in the repo-slug, it will override the login specified
}, },
} }
func runRepoClone(cmd *cli.Context) error { func runRepoClone(ctx stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) teaCmd, err := context.InitCommand(cmd)
if err != nil {
return err
}
args := ctx.Args() args := teaCmd.Args()
if args.Len() < 1 { if args.Len() < 1 {
return cli.ShowCommandHelp(cmd, "clone") return cli.ShowCommandHelp(ctx, cmd, "clone")
} }
dir := args.Get(1) dir := args.Get(1)
var ( var (
login *config.Login = ctx.Login login *config.Login = teaCmd.Login
owner string = ctx.Login.User owner string
repo string repo string
) )
@@ -68,12 +72,19 @@ func runRepoClone(cmd *cli.Context) error {
return err return err
} }
debug.Printf("Cloning repository %s into %s", url.String(), dir)
owner, repo = utils.GetOwnerAndRepo(url.Path, login.User) owner, repo = utils.GetOwnerAndRepo(url.Path, login.User)
if url.Host != "" { if url.Host != "" {
login = config.GetLoginByHost(url.Host) var lookupErr error
if login == nil { login, lookupErr = config.GetLoginByHost(url.Host)
return fmt.Errorf("No login configured matching host '%s', run `tea login add` first", url.Host) if lookupErr != nil {
return lookupErr
} }
if login == nil {
return fmt.Errorf("no login configured matching host '%s', run 'tea login add' first", url.Host)
}
debug.Printf("Matched login '%s' for host '%s'", login.Name, url.Host)
} }
_, err = task.RepoClone( _, err = task.RepoClone(
@@ -82,7 +93,7 @@ func runRepoClone(cmd *cli.Context) error {
owner, owner,
repo, repo,
interact.PromptPassword, interact.PromptPassword,
ctx.Int("depth"), teaCmd.Int("depth"),
) )
return err return err

107
cmd/cmd.go Normal file
View File

@@ -0,0 +1,107 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// Tea is command line tool for Gitea.
package cmd // import "code.gitea.io/tea"
import (
"fmt"
"code.gitea.io/tea/modules/version"
"github.com/urfave/cli/v3"
)
// App creates and returns a tea Command with all subcommands set
// it was separated from main so docs can be generated for it
func App() *cli.Command {
// make parsing tea --version easier, by printing /just/ the version string
cli.VersionPrinter = func(c *cli.Command) { fmt.Fprintln(c.Writer, c.Version) }
return &cli.Command{
Name: "tea",
Usage: "command line tool to interact with Gitea",
Description: appDescription,
CustomHelpTemplate: helpTemplate,
Version: version.Format(),
Commands: []*cli.Command{
&CmdLogin,
&CmdLogout,
&CmdWhoami,
&CmdIssues,
&CmdPulls,
&CmdLabels,
&CmdMilestones,
&CmdReleases,
&CmdTrackedTimes,
&CmdOrgs,
&CmdRepos,
&CmdBranches,
&CmdActions,
&CmdWebhooks,
&CmdAddComment,
&CmdOpen,
&CmdNotifications,
&CmdRepoClone,
&CmdAdmin,
&CmdApi,
&CmdGenerateManPage,
},
EnableShellCompletion: true,
}
}
var appDescription = `tea is a productivity helper for Gitea. It can be used to manage most entities on
one or multiple Gitea instances & provides local helpers like 'tea pr checkout'.
tea tries to make use of context provided by the repository in $PWD if available.
tea works best in a upstream/fork workflow, when the local main branch tracks the
upstream repo. tea assumes that local git state is published on the remote before
doing operations with tea. Configuration is persisted in $XDG_CONFIG_HOME/tea.
`
var helpTemplate = fmt.Sprintf("\033[1m%s\033[0m", `
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}`) + `
{{if .Version}}{{if not .HideVersion}}version {{.Version}}{{end}}{{end}}
USAGE
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .Commands}} command [subcommand] [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
DESCRIPTION
{{.Description | nindent 3 | trim}}{{end}}{{if .VisibleCommands}}
COMMANDS{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
OPTIONS
{{range $index, $option := .VisibleFlags}}{{if $index}}
{{end}}{{$option}}{{end}}{{end}}
EXAMPLES
tea login add # add a login once to get started
tea pulls # list open pulls for the repo in $PWD
tea pulls --repo $HOME/foo # list open pulls for the repo in $HOME/foo
tea pulls --remote upstream # list open pulls for the repo pointed at by
# your local "upstream" git remote
# list open pulls for any gitea repo at the given login instance
tea pulls --repo gitea/tea --login gitea.com
tea milestone issues 0.7.0 # view open issues for milestone '0.7.0'
tea issue 189 # view contents of issue 189
tea open 189 # open web ui for issue 189
tea open milestones # open web ui for milestones
# send gitea desktop notifications every 5 minutes (bash + libnotify)
while :; do tea notifications --mine -o simple | xargs -i notify-send {}; sleep 300; done
ABOUT
Written & maintained by The Gitea Authors.
If you find a bug or want to contribute, we'll welcome you at https://gitea.com/gitea/tea.
More info about Gitea itself on https://about.gitea.com.
`

View File

@@ -1,24 +1,26 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package cmd package cmd
import ( import (
stdctx "context"
"errors"
"fmt" "fmt"
"io/ioutil" "io"
"strings" "strings"
"code.gitea.io/sdk/gitea"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/interact" "code.gitea.io/tea/modules/interact"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"code.gitea.io/tea/modules/theme"
"code.gitea.io/tea/modules/utils" "code.gitea.io/tea/modules/utils"
"code.gitea.io/sdk/gitea" "charm.land/huh/v2"
"github.com/AlecAivazis/survey/v2" "github.com/urfave/cli/v3"
"github.com/urfave/cli/v2"
) )
// CmdAddComment is the main command to operate with notifications // CmdAddComment is the main command to operate with notifications
@@ -33,13 +35,18 @@ var CmdAddComment = cli.Command{
Flags: flags.AllDefaultFlags, Flags: flags.AllDefaultFlags,
} }
func runAddComment(cmd *cli.Context) error { func runAddComment(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
args := ctx.Args() args := ctx.Args()
if args.Len() == 0 { if args.Len() == 0 {
return fmt.Errorf("Please specify issue / pr index") return fmt.Errorf("please specify issue / pr index")
} }
idx, err := utils.ArgToIndex(ctx.Args().First()) idx, err := utils.ArgToIndex(ctx.Args().First())
@@ -50,23 +57,28 @@ func runAddComment(cmd *cli.Context) error {
body := strings.Join(ctx.Args().Tail(), " ") body := strings.Join(ctx.Args().Tail(), " ")
if interact.IsStdinPiped() { if interact.IsStdinPiped() {
// custom solution until https://github.com/AlecAivazis/survey/issues/328 is fixed // custom solution until https://github.com/AlecAivazis/survey/issues/328 is fixed
if bodyStdin, err := ioutil.ReadAll(ctx.App.Reader); err != nil { if bodyStdin, err := io.ReadAll(ctx.Reader); err != nil {
return err return err
} else if len(bodyStdin) != 0 { } else if len(bodyStdin) != 0 {
body = strings.Join([]string{body, string(bodyStdin)}, "\n\n") body = strings.Join([]string{body, string(bodyStdin)}, "\n\n")
} }
} else if len(body) == 0 { } else if len(body) == 0 {
if err = survey.AskOne(interact.NewMultiline(interact.Multiline{ if err := huh.NewForm(
Message: "Comment:", huh.NewGroup(
Syntax: "md", huh.NewText().
UseEditor: config.GetPreferences().Editor, Title("Comment(markdown):").
}), &body); err != nil { ExternalEditor(config.GetPreferences().Editor).
EditorExtension("md").
Value(&body),
),
).WithTheme(theme.GetTheme()).
Run(); err != nil {
return err return err
} }
} }
if len(body) == 0 { if len(body) == 0 {
return fmt.Errorf("No comment body provided") return errors.New("no comment content provided")
} }
client := ctx.Login.Client() client := ctx.Login.Client()

93
cmd/detail_json.go Normal file
View File

@@ -0,0 +1,93 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"encoding/json"
"io"
"time"
"code.gitea.io/sdk/gitea"
)
type detailLabelData struct {
Name string `json:"name"`
Color string `json:"color"`
Description string `json:"description"`
}
type detailCommentData struct {
ID int64 `json:"id"`
Author string `json:"author"`
Created time.Time `json:"created"`
Body string `json:"body"`
}
type detailReviewData struct {
ID int64 `json:"id"`
Reviewer string `json:"reviewer"`
State gitea.ReviewStateType `json:"state"`
Body string `json:"body"`
Created time.Time `json:"created"`
}
func buildDetailLabels(labels []*gitea.Label) []detailLabelData {
labelSlice := make([]detailLabelData, 0, len(labels))
for _, label := range labels {
labelSlice = append(labelSlice, detailLabelData{
Name: label.Name,
Color: label.Color,
Description: label.Description,
})
}
return labelSlice
}
func buildDetailAssignees(assignees []*gitea.User) []string {
assigneeSlice := make([]string, 0, len(assignees))
for _, assignee := range assignees {
assigneeSlice = append(assigneeSlice, username(assignee))
}
return assigneeSlice
}
func buildDetailComments(comments []*gitea.Comment) []detailCommentData {
commentSlice := make([]detailCommentData, 0, len(comments))
for _, comment := range comments {
commentSlice = append(commentSlice, detailCommentData{
ID: comment.ID,
Author: username(comment.Poster),
Body: comment.Body,
Created: comment.Created,
})
}
return commentSlice
}
func buildDetailReviews(reviews []*gitea.PullReview) []detailReviewData {
reviewSlice := make([]detailReviewData, 0, len(reviews))
for _, review := range reviews {
reviewSlice = append(reviewSlice, detailReviewData{
ID: review.ID,
Reviewer: username(review.Reviewer),
State: review.State,
Body: review.Body,
Created: review.Submitted,
})
}
return reviewSlice
}
func username(user *gitea.User) string {
if user == nil {
return "ghost"
}
return user.UserName
}
func writeIndentedJSON(w io.Writer, data any) error {
encoder := json.NewEncoder(w)
encoder.SetIndent("", "\t")
return encoder.Encode(data)
}

View File

@@ -1,6 +1,5 @@
// Copyright 2021 The Gitea Authors. All rights reserved. // Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package flags package flags
@@ -9,7 +8,7 @@ import (
"strings" "strings"
"code.gitea.io/tea/modules/utils" "code.gitea.io/tea/modules/utils"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CsvFlag is a wrapper around cli.StringFlag, with an added GetValues() method // CsvFlag is a wrapper around cli.StringFlag, with an added GetValues() method
@@ -39,13 +38,13 @@ func NewCsvFlag(name, usage string, aliases, availableValues, defaults []string)
} }
// GetValues returns the value of the flag, parsed as a commaseparated list // GetValues returns the value of the flag, parsed as a commaseparated list
func (f CsvFlag) GetValues(ctx *cli.Context) ([]string, error) { func (f CsvFlag) GetValues(cmd *cli.Command) ([]string, error) {
val := ctx.String(f.Name) val := cmd.String(f.Name)
selection := strings.Split(val, ",") selection := strings.Split(val, ",")
if f.AvailableFields != nil && val != "" { if f.AvailableFields != nil && val != "" {
for _, field := range selection { for _, field := range selection {
if !utils.Contains(f.AvailableFields, field) { if !utils.Contains(f.AvailableFields, field) {
return nil, fmt.Errorf("Invalid field '%s'", field) return nil, fmt.Errorf("invalid field '%s'", field)
} }
} }
} }

View File

@@ -1,11 +1,14 @@
// Copyright 2019 The Gitea Authors. All rights reserved. // Copyright 2025 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package flags package flags
import ( import (
"github.com/urfave/cli/v2" "errors"
"fmt"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v3"
) )
// LoginFlag provides flag to specify tea login profile // LoginFlag provides flag to specify tea login profile
@@ -33,21 +36,71 @@ var RemoteFlag = cli.StringFlag{
var OutputFlag = cli.StringFlag{ var OutputFlag = cli.StringFlag{
Name: "output", Name: "output",
Aliases: []string{"o"}, Aliases: []string{"o"},
Usage: "Output format. (csv, simple, table, tsv, yaml)", Usage: "Output format. (simple, table, csv, tsv, yaml, json)",
}
var (
// ErrPage indicates that the provided page value is invalid (less than -1 or equal to 0).
ErrPage = errors.New("page cannot be smaller than 1")
// ErrLimit indicates that the provided limit value is invalid (negative).
ErrLimit = errors.New("limit cannot be negative")
)
const (
defaultPageValue = 1
defaultLimitValue = 30
)
// GetListOptions returns list options derived from the active command.
func GetListOptions(cmd *cli.Command) gitea.ListOptions {
page := cmd.Int("page")
if page == 0 {
page = defaultPageValue
}
pageSize := cmd.Int("limit")
if pageSize == 0 {
pageSize = defaultLimitValue
}
return gitea.ListOptions{
Page: page,
PageSize: pageSize,
}
}
// PaginationFlags provides all pagination related flags
var PaginationFlags = []cli.Flag{
&PaginationPageFlag,
&PaginationLimitFlag,
} }
// PaginationPageFlag provides flag for pagination options // PaginationPageFlag provides flag for pagination options
var PaginationPageFlag = cli.StringFlag{ var PaginationPageFlag = cli.IntFlag{
Name: "page", Name: "page",
Aliases: []string{"p"}, Aliases: []string{"p"},
Usage: "specify page, default is 1", Usage: "specify page",
Value: defaultPageValue,
Validator: func(i int) error {
if i < 1 && i != -1 {
return ErrPage
}
return nil
},
} }
// PaginationLimitFlag provides flag for pagination options // PaginationLimitFlag provides flag for pagination options
var PaginationLimitFlag = cli.StringFlag{ var PaginationLimitFlag = cli.IntFlag{
Name: "limit", Name: "limit",
Aliases: []string{"lm"}, Aliases: []string{"lm"},
Usage: "specify limit of items per page", Usage: "specify limit of items per page",
Value: defaultLimitValue,
Validator: func(i int) error {
if i < 0 {
return ErrLimit
}
return nil
},
} }
// LoginOutputFlags defines login and output flags that should // LoginOutputFlags defines login and output flags that should
@@ -104,3 +157,34 @@ var NotificationStateFlag = NewCsvFlag(
func FieldsFlag(availableFields, defaultFields []string) *CsvFlag { func FieldsFlag(availableFields, defaultFields []string) *CsvFlag {
return NewCsvFlag("fields", "fields to print", []string{"f"}, availableFields, defaultFields) return NewCsvFlag("fields", "fields to print", []string{"f"}, availableFields, defaultFields)
} }
// ParseState parses a state string and returns the corresponding gitea.StateType
func ParseState(stateStr string) (gitea.StateType, error) {
switch stateStr {
case "all":
return gitea.StateAll, nil
case "", "open":
return gitea.StateOpen, nil
case "closed":
return gitea.StateClosed, nil
default:
return "", fmt.Errorf("unknown state '%s'", stateStr)
}
}
// ParseIssueKind parses a kind string and returns the corresponding gitea.IssueType.
// If kindStr is empty, returns the provided defaultKind.
func ParseIssueKind(kindStr string, defaultKind gitea.IssueType) (gitea.IssueType, error) {
switch kindStr {
case "":
return defaultKind, nil
case "all":
return gitea.IssueTypeAll, nil
case "issue", "issues":
return gitea.IssueTypeIssue, nil
case "pull", "pulls", "pr":
return gitea.IssueTypePull, nil
default:
return "", fmt.Errorf("unknown kind '%s'", kindStr)
}
}

152
cmd/flags/generic_test.go Normal file
View File

@@ -0,0 +1,152 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package flags
import (
"context"
"io"
"testing"
"code.gitea.io/sdk/gitea"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v3"
)
func TestPaginationFlags(t *testing.T) {
var (
defaultPage = PaginationPageFlag.Value
defaultLimit = PaginationLimitFlag.Value
)
cases := []struct {
name string
args []string
expectedPage int
expectedLimit int
}{
{
name: "no flags",
args: []string{"test"},
expectedPage: defaultPage,
expectedLimit: defaultLimit,
},
{
name: "only paging",
args: []string{"test", "--page", "5"},
expectedPage: 5,
expectedLimit: defaultLimit,
},
{
name: "only limit",
args: []string{"test", "--limit", "10"},
expectedPage: defaultPage,
expectedLimit: 10,
},
{
name: "only limit",
args: []string{"test", "--limit", "10"},
expectedPage: defaultPage,
expectedLimit: 10,
},
{
name: "both flags",
args: []string{"test", "--page", "2", "--limit", "20"},
expectedPage: 2,
expectedLimit: 20,
},
{ // TODO: Should no paging be applied as -1 or a separate flag? It's not obvious that page=-1 turns off paging and limit is ignored
name: "no paging",
args: []string{"test", "--limit", "20", "--page", "-1"},
expectedPage: -1,
expectedLimit: 20,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cmd := cli.Command{
Name: "test-paging",
Action: func(_ context.Context, cmd *cli.Command) error {
assert.Equal(t, tc.expectedPage, cmd.Int("page"))
assert.Equal(t, tc.expectedLimit, cmd.Int("limit"))
return nil
},
Flags: PaginationFlags,
}
err := cmd.Run(context.Background(), tc.args)
require.NoError(t, err)
})
}
}
func TestPaginationFailures(t *testing.T) {
cases := []struct {
name string
args []string
expectedError error
}{
{
name: "negative limit",
args: []string{"test", "--limit", "-10"},
expectedError: ErrLimit,
},
{
name: "negative paging",
args: []string{"test", "--page", "-2"},
expectedError: ErrPage,
},
{
name: "zero paging",
args: []string{"test", "--page", "0"},
expectedError: ErrPage,
},
{
// urfave does not validate all flags in one pass
name: "negative paging and paging",
args: []string{"test", "--page", "-2", "--limit", "-10"},
expectedError: ErrPage,
},
}
for _, tc := range cases {
cmd := cli.Command{
Name: "test-paging",
Flags: PaginationFlags,
Writer: io.Discard,
ErrWriter: io.Discard,
}
t.Run(tc.name, func(t *testing.T) {
err := cmd.Run(context.Background(), tc.args)
require.ErrorContains(t, err, tc.expectedError.Error())
// require.ErrorIs(t, err, tc.expectedError)
})
}
}
func TestGetListOptionsDoesNotLeakBetweenCommands(t *testing.T) {
var results []gitea.ListOptions
run := func(args []string) {
t.Helper()
cmd := cli.Command{
Name: "test-paging",
Action: func(_ context.Context, cmd *cli.Command) error {
results = append(results, GetListOptions(cmd))
return nil
},
Flags: PaginationFlags,
}
require.NoError(t, cmd.Run(context.Background(), args))
}
run([]string{"test", "--page", "5", "--limit", "10"})
run([]string{"test"})
require.Len(t, results, 2)
assert.Equal(t, gitea.ListOptions{Page: 5, PageSize: 10}, results[0])
assert.Equal(t, gitea.ListOptions{Page: defaultPageValue, PageSize: defaultLimitValue}, results[1])
}

View File

@@ -1,19 +1,19 @@
// Copyright 2019 The Gitea Authors. All rights reserved. // Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package flags package flags
import ( import (
"fmt" "fmt"
"strings" "strings"
"time"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/task" "code.gitea.io/tea/modules/task"
"github.com/araddon/dateparse" "github.com/araddon/dateparse"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// StateFlag provides flag to specify issue/pr state, defaulting to "open" // StateFlag provides flag to specify issue/pr state, defaulting to "open"
@@ -70,6 +70,10 @@ var IssueListingFlags = append([]cli.Flag{
Name: "mentions", Name: "mentions",
Aliases: []string{"M"}, Aliases: []string{"M"},
}, },
&cli.StringFlag{
Name: "owner",
Aliases: []string{"org"},
},
&cli.StringFlag{ &cli.StringFlag{
Name: "from", Name: "from",
Aliases: []string{"F"}, Aliases: []string{"F"},
@@ -84,8 +88,8 @@ var IssueListingFlags = append([]cli.Flag{
&PaginationLimitFlag, &PaginationLimitFlag,
}, AllDefaultFlags...) }, AllDefaultFlags...)
// IssuePREditFlags defines flags for properties of issues and PRs // issuePRFlags defines shared flags between flags IssuePRCreateFlags and IssuePREditFlags
var IssuePREditFlags = append([]cli.Flag{ var issuePRFlags = append([]cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "title", Name: "title",
Aliases: []string{"t"}, Aliases: []string{"t"},
@@ -94,6 +98,25 @@ var IssuePREditFlags = append([]cli.Flag{
Name: "description", Name: "description",
Aliases: []string{"d"}, Aliases: []string{"d"},
}, },
&cli.StringFlag{
Name: "referenced-version",
Aliases: []string{"v"},
Usage: "commit-hash or tag name to assign",
},
&cli.StringFlag{
Name: "milestone",
Aliases: []string{"m"},
Usage: "Milestone to assign",
},
&cli.StringFlag{
Name: "deadline",
Aliases: []string{"D"},
Usage: "Deadline timestamp to assign",
},
}, LoginRepoFlags...)
// IssuePRCreateFlags defines flags for creation of issues and PRs
var IssuePRCreateFlags = append([]cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "assignees", Name: "assignees",
Aliases: []string{"a"}, Aliases: []string{"a"},
@@ -104,20 +127,10 @@ var IssuePREditFlags = append([]cli.Flag{
Aliases: []string{"L"}, Aliases: []string{"L"},
Usage: "Comma-separated list of labels to assign", Usage: "Comma-separated list of labels to assign",
}, },
&cli.StringFlag{ }, issuePRFlags...)
Name: "deadline",
Aliases: []string{"D"},
Usage: "Deadline timestamp to assign",
},
&cli.StringFlag{
Name: "milestone",
Aliases: []string{"m"},
Usage: "Milestone to assign",
},
}, LoginRepoFlags...)
// GetIssuePREditFlags parses all IssuePREditFlags // GetIssuePRCreateFlags parses all IssuePREditFlags
func GetIssuePREditFlags(ctx *context.TeaContext) (*gitea.CreateIssueOption, error) { func GetIssuePRCreateFlags(ctx *context.TeaContext) (*gitea.CreateIssueOption, error) {
opts := gitea.CreateIssueOption{ opts := gitea.CreateIssueOption{
Title: ctx.String("title"), Title: ctx.String("title"),
Body: ctx.String("description"), Body: ctx.String("description"),
@@ -152,10 +165,74 @@ func GetIssuePREditFlags(ctx *context.TeaContext) (*gitea.CreateIssueOption, err
} }
ms, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, milestoneName) ms, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, milestoneName)
if err != nil { if err != nil {
return nil, fmt.Errorf("Milestone '%s' not found", milestoneName) return nil, fmt.Errorf("milestone '%s' not found", milestoneName)
} }
opts.Milestone = ms.ID opts.Milestone = ms.ID
} }
return &opts, nil return &opts, nil
} }
// IssuePREditFlags defines flags for editing properties of issues and PRs
var IssuePREditFlags = append([]cli.Flag{
&cli.StringFlag{
Name: "add-assignees",
Aliases: []string{"a"},
Usage: "Comma-separated list of usernames to assign",
},
&cli.StringFlag{
Name: "add-labels",
Aliases: []string{"L"},
Usage: "Comma-separated list of labels to assign. Takes precedence over --remove-labels",
},
&cli.StringFlag{
Name: "remove-labels",
Usage: "Comma-separated list of labels to remove",
},
}, issuePRFlags...)
// GetIssuePREditFlags parses all IssuePREditFlags
func GetIssuePREditFlags(ctx *context.TeaContext) (*task.EditIssueOption, error) {
opts := task.EditIssueOption{}
if ctx.IsSet("title") {
val := ctx.String("title")
opts.Title = &val
}
if ctx.IsSet("description") {
val := ctx.String("description")
opts.Body = &val
}
if ctx.IsSet("referenced-version") {
val := ctx.String("referenced-version")
opts.Ref = &val
}
if ctx.IsSet("milestone") {
val := ctx.String("milestone")
opts.Milestone = &val
}
if ctx.IsSet("deadline") {
date := ctx.String("deadline")
if date == "" {
opts.Deadline = &time.Time{}
} else {
t, err := dateparse.ParseAny(date)
if err != nil {
return nil, err
}
opts.Deadline = &t
}
}
if ctx.IsSet("add-assignees") {
val := ctx.String("add-assignees")
opts.AddAssignees = strings.Split(val, ",")
}
if ctx.IsSet("add-labels") {
val := ctx.String("add-labels")
opts.AddLabels = strings.Split(val, ",")
}
if ctx.IsSet("remove-labels") {
val := ctx.String("remove-labels")
opts.RemoveLabels = strings.Split(val, ",")
}
return &opts, nil
}

View File

@@ -1,21 +1,52 @@
// Copyright 2018 The Gitea Authors. All rights reserved. // Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package cmd package cmd
import ( import (
stdctx "context"
"fmt" "fmt"
"time"
"code.gitea.io/sdk/gitea"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/cmd/issues" "code.gitea.io/tea/cmd/issues"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/interact" "code.gitea.io/tea/modules/interact"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"code.gitea.io/tea/modules/utils" "code.gitea.io/tea/modules/utils"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
type labelData = detailLabelData
type issueData struct {
ID int64 `json:"id"`
Index int64 `json:"index"`
Title string `json:"title"`
State gitea.StateType `json:"state"`
Created time.Time `json:"created"`
Labels []labelData `json:"labels"`
User string `json:"user"`
Body string `json:"body"`
Assignees []string `json:"assignees"`
URL string `json:"url"`
ClosedAt *time.Time `json:"closedAt"`
Comments []commentData `json:"comments"`
}
type issueDetailClient interface {
GetIssue(owner, repo string, index int64) (*gitea.Issue, *gitea.Response, error)
GetIssueReactions(owner, repo string, index int64) ([]*gitea.Reaction, *gitea.Response, error)
}
type issueCommentClient interface {
ListIssueComments(owner, repo string, index int64, opt gitea.ListIssueCommentOptions) ([]*gitea.Comment, *gitea.Response, error)
}
type commentData = detailCommentData
// CmdIssues represents to login a gitea server. // CmdIssues represents to login a gitea server.
var CmdIssues = cli.Command{ var CmdIssues = cli.Command{
Name: "issues", Name: "issues",
@@ -25,9 +56,10 @@ var CmdIssues = cli.Command{
Description: `Lists issues when called without argument. If issue index is provided, will show it in detail.`, Description: `Lists issues when called without argument. If issue index is provided, will show it in detail.`,
ArgsUsage: "[<issue index>]", ArgsUsage: "[<issue index>]",
Action: runIssues, Action: runIssues,
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&issues.CmdIssuesList, &issues.CmdIssuesList,
&issues.CmdIssuesCreate, &issues.CmdIssuesCreate,
&issues.CmdIssuesEdit,
&issues.CmdIssuesReopen, &issues.CmdIssuesReopen,
&issues.CmdIssuesClose, &issues.CmdIssuesClose,
}, },
@@ -39,22 +71,43 @@ var CmdIssues = cli.Command{
}, issues.CmdIssuesList.Flags...), }, issues.CmdIssuesList.Flags...),
} }
func runIssues(ctx *cli.Context) error { func runIssues(ctx stdctx.Context, cmd *cli.Command) error {
if ctx.Args().Len() == 1 { if cmd.Args().Len() == 1 {
return runIssueDetail(ctx, ctx.Args().First()) return runIssueDetail(ctx, cmd, cmd.Args().First())
} }
return issues.RunIssuesList(ctx) return issues.RunIssuesList(ctx, cmd)
} }
func runIssueDetail(cmd *cli.Context, index string) error { func runIssueDetail(_ stdctx.Context, cmd *cli.Command, index string) error {
ctx := context.InitCommand(cmd) ctx, idx, err := resolveIssueDetailContext(cmd, index)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
idx, err := utils.ArgToIndex(index)
if err != nil { if err != nil {
return err return err
} }
client := ctx.Login.Client()
return runIssueDetailWithClient(ctx, idx, ctx.Login.Client())
}
func resolveIssueDetailContext(cmd *cli.Command, index string) (*context.TeaContext, int64, error) {
ctx, err := context.InitCommand(cmd)
if err != nil {
return nil, 0, err
}
if ctx.IsSet("owner") {
ctx.Owner = ctx.String("owner")
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return nil, 0, err
}
idx, err := utils.ArgToIndex(index)
if err != nil {
return nil, 0, err
}
return ctx, idx, nil
}
func runIssueDetailWithClient(ctx *context.TeaContext, idx int64, client issueDetailClient) error {
issue, _, err := client.GetIssue(ctx.Owner, ctx.Repo, idx) issue, _, err := client.GetIssue(ctx.Owner, ctx.Repo, idx)
if err != nil { if err != nil {
return err return err
@@ -63,6 +116,14 @@ func runIssueDetail(cmd *cli.Context, index string) error {
if err != nil { if err != nil {
return err return err
} }
if ctx.IsSet("output") {
switch ctx.String("output") {
case "json":
return runIssueDetailAsJSON(ctx, issue)
}
}
print.IssueDetails(issue, reactions) print.IssueDetails(issue, reactions)
if issue.Comments > 0 { if issue.Comments > 0 {
@@ -74,3 +135,39 @@ func runIssueDetail(cmd *cli.Context, index string) error {
return nil return nil
} }
func runIssueDetailAsJSON(ctx *context.TeaContext, issue *gitea.Issue) error {
return runIssueDetailAsJSONWithClient(ctx, issue, ctx.Login.Client())
}
func runIssueDetailAsJSONWithClient(ctx *context.TeaContext, issue *gitea.Issue, c issueCommentClient) error {
opts := gitea.ListIssueCommentOptions{ListOptions: flags.GetListOptions(ctx.Command)}
comments := []*gitea.Comment{}
if ctx.Bool("comments") {
var err error
comments, _, err = c.ListIssueComments(ctx.Owner, ctx.Repo, issue.Index, opts)
if err != nil {
return err
}
}
return writeIndentedJSON(ctx.Writer, buildIssueData(issue, comments))
}
func buildIssueData(issue *gitea.Issue, comments []*gitea.Comment) issueData {
return issueData{
ID: issue.ID,
Index: issue.Index,
Title: issue.Title,
State: issue.State,
Created: issue.Created,
User: username(issue.Poster),
Body: issue.Body,
Labels: buildDetailLabels(issue.Labels),
Assignees: buildDetailAssignees(issue.Assignees),
URL: issue.HTMLURL,
ClosedAt: issue.Closed,
Comments: buildDetailComments(comments),
}
}

View File

@@ -1,10 +1,10 @@
// Copyright 2018 The Gitea Authors. All rights reserved. // Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package issues package issues
import ( import (
stdctx "context"
"fmt" "fmt"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
@@ -13,40 +13,52 @@ import (
"code.gitea.io/tea/modules/utils" "code.gitea.io/tea/modules/utils"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdIssuesClose represents a sub command of issues to close an issue // CmdIssuesClose represents a sub command of issues to close an issue
var CmdIssuesClose = cli.Command{ var CmdIssuesClose = cli.Command{
Name: "close", Name: "close",
Usage: "Change state of an issue to 'closed'", Usage: "Change state of one ore more issues to 'closed'",
Description: `Change state of an issue to 'closed'`, Description: `Change state of one ore more issues to 'closed'`,
ArgsUsage: "<issue index>", ArgsUsage: "<issue index> [<issue index>...]",
Action: func(ctx *cli.Context) error { Action: func(ctx stdctx.Context, cmd *cli.Command) error {
var s = gitea.StateClosed s := gitea.StateClosed
return editIssueState(ctx, gitea.EditIssueOption{State: &s}) return editIssueState(ctx, cmd, gitea.EditIssueOption{State: &s})
}, },
Flags: flags.AllDefaultFlags, Flags: flags.AllDefaultFlags,
} }
// editIssueState abstracts the arg parsing to edit the given issue // editIssueState abstracts the arg parsing to edit the given issue
func editIssueState(cmd *cli.Context, opts gitea.EditIssueOption) error { func editIssueState(_ stdctx.Context, cmd *cli.Command, opts gitea.EditIssueOption) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
if ctx.Args().Len() == 0 { if ctx.Args().Len() == 0 {
return fmt.Errorf(ctx.Command.ArgsUsage) return fmt.Errorf("missing required argument: %s", ctx.Command.ArgsUsage)
} }
index, err := utils.ArgToIndex(ctx.Args().First()) indices, err := utils.ArgsToIndices(ctx.Args().Slice())
if err != nil { if err != nil {
return err return err
} }
issue, _, err := ctx.Login.Client().EditIssue(ctx.Owner, ctx.Repo, index, opts) client := ctx.Login.Client()
if err != nil { for _, index := range indices {
return err issue, _, err := client.EditIssue(ctx.Owner, ctx.Repo, index, opts)
} if err != nil {
return err
}
print.IssueDetails(issue, nil) if len(indices) > 1 {
fmt.Println(issue.HTMLURL)
} else {
print.IssueDetails(issue, nil)
}
}
return nil return nil
} }

View File

@@ -1,16 +1,17 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package issues package issues
import ( import (
stdctx "context"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/interact" "code.gitea.io/tea/modules/interact"
"code.gitea.io/tea/modules/task" "code.gitea.io/tea/modules/task"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdIssuesCreate represents a sub command of issues to create issue // CmdIssuesCreate represents a sub command of issues to create issue
@@ -21,18 +22,27 @@ var CmdIssuesCreate = cli.Command{
Description: `Create an issue on repository`, Description: `Create an issue on repository`,
ArgsUsage: " ", // command does not accept arguments ArgsUsage: " ", // command does not accept arguments
Action: runIssuesCreate, Action: runIssuesCreate,
Flags: flags.IssuePREditFlags, Flags: flags.IssuePRCreateFlags,
} }
func runIssuesCreate(cmd *cli.Context) error { func runIssuesCreate(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
if ctx.NumFlags() == 0 { }
return interact.CreateIssue(ctx.Login, ctx.Owner, ctx.Repo) if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
} }
opts, err := flags.GetIssuePREditFlags(ctx) if ctx.IsInteractiveMode() {
err := interact.CreateIssue(ctx.Login, ctx.Owner, ctx.Repo)
if err != nil && !interact.IsQuitting(err) {
return err
}
return nil
}
opts, err := flags.GetIssuePRCreateFlags(ctx)
if err != nil { if err != nil {
return err return err
} }

80
cmd/issues/edit.go Normal file
View File

@@ -0,0 +1,80 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package issues
import (
"fmt"
stdctx "context"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/interact"
"code.gitea.io/tea/modules/print"
"code.gitea.io/tea/modules/task"
"code.gitea.io/tea/modules/utils"
"github.com/urfave/cli/v3"
)
// CmdIssuesEdit is the subcommand of issues to edit issues
var CmdIssuesEdit = cli.Command{
Name: "edit",
Aliases: []string{"e"},
Usage: "Edit one or more issues",
Description: `Edit one or more issues. To unset a property again,
use an empty string (eg. --milestone "").`,
ArgsUsage: "<idx> [<idx>...]",
Action: runIssuesEdit,
Flags: flags.IssuePREditFlags,
}
func runIssuesEdit(_ stdctx.Context, cmd *cli.Command) error {
ctx, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
if !cmd.Args().Present() {
return fmt.Errorf("must specify at least one issue index")
}
opts, err := flags.GetIssuePREditFlags(ctx)
if err != nil {
return err
}
indices, err := utils.ArgsToIndices(ctx.Args().Slice())
if err != nil {
return err
}
client := ctx.Login.Client()
for _, opts.Index = range indices {
if ctx.IsInteractiveMode() {
var err error
opts, err = interact.EditIssue(*ctx, opts.Index)
if err != nil {
if interact.IsQuitting(err) {
return nil // user quit
}
return err
}
}
issue, err := task.EditIssue(ctx, client, *opts)
if err != nil {
return err
}
if ctx.Args().Len() > 1 {
fmt.Println(issue.HTMLURL)
} else {
print.IssueDetails(issue, nil)
}
}
return nil
}

View File

@@ -1,11 +1,10 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package issues package issues
import ( import (
"fmt" stdctx "context"
"time" "time"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
@@ -14,11 +13,11 @@ import (
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/araddon/dateparse" "github.com/araddon/dateparse"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var issueFieldsFlag = flags.FieldsFlag(print.IssueFields, []string{ var issueFieldsFlag = flags.FieldsFlag(print.IssueFields, []string{
"index", "title", "state", "author", "milestone", "labels", "index", "title", "state", "author", "milestone", "labels", "owner", "repo",
}) })
// CmdIssuesList represents a sub command of issues to list issues // CmdIssuesList represents a sub command of issues to list issues
@@ -33,35 +32,22 @@ var CmdIssuesList = cli.Command{
} }
// RunIssuesList list issues // RunIssuesList list issues
func RunIssuesList(cmd *cli.Context) error { func RunIssuesList(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
state := gitea.StateOpen
switch ctx.String("state") {
case "all":
state = gitea.StateAll
case "", "open":
state = gitea.StateOpen
case "closed":
state = gitea.StateClosed
default:
return fmt.Errorf("unknown state '%s'", ctx.String("state"))
} }
kind := gitea.IssueTypeIssue state, err := flags.ParseState(ctx.String("state"))
switch ctx.String("kind") { if err != nil {
case "", "issues", "issue": return err
kind = gitea.IssueTypeIssue }
case "pulls", "pull", "pr":
kind = gitea.IssueTypePull kind, err := flags.ParseIssueKind(ctx.String("kind"), gitea.IssueTypeIssue)
case "all": if err != nil {
kind = gitea.IssueTypeAll return err
default:
return fmt.Errorf("unknown kind '%s'", ctx.String("kind"))
} }
var err error
var from, until time.Time var from, until time.Time
if ctx.IsSet("from") { if ctx.IsSet("from") {
from, err = dateparse.ParseLocal(ctx.String("from")) from, err = dateparse.ParseLocal(ctx.String("from"))
@@ -75,27 +61,50 @@ func RunIssuesList(cmd *cli.Context) error {
return err return err
} }
} }
owner := ctx.Owner
if ctx.IsSet("owner") {
owner = ctx.String("owner")
}
// ignore error, as we don't do any input validation on these flags // ignore error, as we don't do any input validation on these flags
labels, _ := flags.LabelFilterFlag.GetValues(cmd) labels, _ := flags.LabelFilterFlag.GetValues(cmd)
milestones, _ := flags.MilestoneFilterFlag.GetValues(cmd) milestones, _ := flags.MilestoneFilterFlag.GetValues(cmd)
var issues []*gitea.Issue
issues, _, err := ctx.Login.Client().ListRepoIssues(ctx.Owner, ctx.Repo, gitea.ListIssueOption{ if ctx.Repo != "" {
ListOptions: ctx.GetListOptions(), issues, _, err = ctx.Login.Client().ListRepoIssues(owner, ctx.Repo, gitea.ListIssueOption{
State: state, ListOptions: flags.GetListOptions(cmd),
Type: kind, State: state,
KeyWord: ctx.String("keyword"), Type: kind,
CreatedBy: ctx.String("author"), KeyWord: ctx.String("keyword"),
AssignedBy: ctx.String("assigned-to"), CreatedBy: ctx.String("author"),
MentionedBy: ctx.String("mentions"), AssignedBy: ctx.String("assigned-to"),
Labels: labels, MentionedBy: ctx.String("mentions"),
Milestones: milestones, Labels: labels,
Since: from, Milestones: milestones,
Before: until, Since: from,
}) Before: until,
})
if err != nil { if err != nil {
return err return err
}
} else {
issues, _, err = ctx.Login.Client().ListIssues(gitea.ListIssueOption{
ListOptions: flags.GetListOptions(cmd),
State: state,
Type: kind,
KeyWord: ctx.String("keyword"),
CreatedBy: ctx.String("author"),
AssignedBy: ctx.String("assigned-to"),
MentionedBy: ctx.String("mentions"),
Labels: labels,
Milestones: milestones,
Since: from,
Before: until,
Owner: owner,
})
if err != nil {
return err
}
} }
fields, err := issueFieldsFlag.GetValues(cmd) fields, err := issueFieldsFlag.GetValues(cmd)
@@ -103,6 +112,5 @@ func RunIssuesList(cmd *cli.Context) error {
return err return err
} }
print.IssuesPullsList(issues, ctx.Output, fields) return print.IssuesPullsList(issues, ctx.Output, fields)
return nil
} }

View File

@@ -1,26 +1,27 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package issues package issues
import ( import (
"context"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdIssuesReopen represents a sub command of issues to open an issue // CmdIssuesReopen represents a sub command of issues to open an issue
var CmdIssuesReopen = cli.Command{ var CmdIssuesReopen = cli.Command{
Name: "reopen", Name: "reopen",
Aliases: []string{"open"}, Aliases: []string{"open"},
Usage: "Change state of an issue to 'open'", Usage: "Change state of one or more issues to 'open'",
Description: `Change state of an issue to 'open'`, Description: `Change state of one or more issues to 'open'`,
ArgsUsage: "<issue index>", ArgsUsage: "<issue index> [<issue index>...]",
Action: func(ctx *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
var s = gitea.StateOpen s := gitea.StateOpen
return editIssueState(ctx, gitea.EditIssueOption{State: &s}) return editIssueState(ctx, cmd, gitea.EditIssueOption{State: &s})
}, },
Flags: flags.AllDefaultFlags, Flags: flags.AllDefaultFlags,
} }

358
cmd/issues_test.go Normal file
View File

@@ -0,0 +1,358 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"bytes"
"encoding/json"
"fmt"
"testing"
"time"
"code.gitea.io/sdk/gitea"
"code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/context"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v3"
)
const (
testOwner = "testOwner"
testRepo = "testRepo"
)
type fakeIssueCommentClient struct {
owner string
repo string
index int64
comments []*gitea.Comment
}
func (f *fakeIssueCommentClient) ListIssueComments(owner, repo string, index int64, _ gitea.ListIssueCommentOptions) ([]*gitea.Comment, *gitea.Response, error) {
f.owner = owner
f.repo = repo
f.index = index
return f.comments, nil, nil
}
type fakeIssueDetailClient struct {
owner string
repo string
index int64
issue *gitea.Issue
reactions []*gitea.Reaction
}
func (f *fakeIssueDetailClient) GetIssue(owner, repo string, index int64) (*gitea.Issue, *gitea.Response, error) {
f.owner = owner
f.repo = repo
f.index = index
return f.issue, nil, nil
}
func (f *fakeIssueDetailClient) GetIssueReactions(owner, repo string, index int64) ([]*gitea.Reaction, *gitea.Response, error) {
f.owner = owner
f.repo = repo
f.index = index
return f.reactions, nil, nil
}
func toCommentPointers(comments []gitea.Comment) []*gitea.Comment {
result := make([]*gitea.Comment, 0, len(comments))
for i := range comments {
comment := comments[i]
result = append(result, &comment)
}
return result
}
func createTestIssue(comments int, isClosed bool) gitea.Issue {
issue := gitea.Issue{
ID: 42,
Index: 1,
Title: "Test issue",
State: gitea.StateOpen,
Body: "This is a test",
Created: time.Date(2025, 31, 10, 23, 59, 59, 999999999, time.UTC),
Updated: time.Date(2025, 1, 11, 0, 0, 0, 0, time.UTC),
Labels: []*gitea.Label{
{
Name: "example/Label1",
Color: "very red",
Description: "This is an example label",
},
{
Name: "example/Label2",
Color: "hardly red",
Description: "This is another example label",
},
},
Comments: comments,
Poster: &gitea.User{
UserName: "testUser",
},
Assignees: []*gitea.User{
{UserName: "testUser"},
{UserName: "testUser3"},
},
HTMLURL: "<space holder>",
Closed: nil, // 2025-11-10T21:20:19Z
}
if isClosed {
closed := time.Date(2025, 11, 10, 21, 20, 19, 0, time.UTC)
issue.Closed = &closed
}
if isClosed {
issue.State = gitea.StateClosed
} else {
issue.State = gitea.StateOpen
}
return issue
}
func createTestIssueComments(comments int) []gitea.Comment {
baseID := 900
var result []gitea.Comment
for commentID := 0; commentID < comments; commentID++ {
result = append(result, gitea.Comment{
ID: int64(baseID + commentID),
Poster: &gitea.User{
UserName: "Freddy",
},
Body: fmt.Sprintf("This is a test comment #%v", commentID),
Created: time.Date(2025, 11, 3, 12, 0, 0, 0, time.UTC).
Add(time.Duration(commentID) * time.Hour),
})
}
return result
}
func TestRunIssueDetailAsJSON(t *testing.T) {
type TestCase struct {
name string
issue gitea.Issue
comments []gitea.Comment
flagComments bool
}
cmd := cli.Command{
Name: "t",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "comments",
Value: false,
},
&cli.StringFlag{
Name: "output",
Value: "json",
},
},
}
testContext := context.TeaContext{
Owner: testOwner,
Repo: testRepo,
Login: &config.Login{
Name: "testLogin",
URL: "http://127.0.0.1:8081",
},
Command: &cmd,
}
testCases := []TestCase{
{
name: "Simple issue with no comments, no comments requested",
issue: createTestIssue(0, true),
comments: []gitea.Comment{},
flagComments: false,
},
{
name: "Simple issue with no comments, comments requested",
issue: createTestIssue(0, true),
comments: []gitea.Comment{},
flagComments: true,
},
{
name: "Simple issue with comments, no comments requested",
issue: createTestIssue(2, true),
comments: createTestIssueComments(2),
flagComments: false,
},
{
name: "Simple issue with comments, comments requested",
issue: createTestIssue(2, true),
comments: createTestIssueComments(2),
flagComments: true,
},
{
name: "Simple issue with comments, comments requested, not closed",
issue: createTestIssue(2, false),
comments: createTestIssueComments(2),
flagComments: true,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
client := &fakeIssueCommentClient{
comments: toCommentPointers(testCase.comments),
}
testContext.Login.URL = "https://gitea.example.com"
testCase.issue.HTMLURL = fmt.Sprintf("%s/%s/%s/issues/%d/", testContext.Login.URL, testOwner, testRepo, testCase.issue.Index)
var outBuffer bytes.Buffer
testContext.Writer = &outBuffer
var errBuffer bytes.Buffer
testContext.ErrWriter = &errBuffer
if testCase.flagComments {
require.NoError(t, testContext.Set("comments", "true"))
} else {
require.NoError(t, testContext.Set("comments", "false"))
}
err := runIssueDetailAsJSONWithClient(&testContext, &testCase.issue, client)
require.NoError(t, err, "Failed to run issue detail as JSON")
if testCase.flagComments {
assert.Equal(t, testOwner, client.owner)
assert.Equal(t, testRepo, client.repo)
assert.Equal(t, testCase.issue.Index, client.index)
}
out := outBuffer.String()
require.NotEmpty(t, out, "Unexpected empty output from runIssueDetailAsJSON")
// setting expectations
var expectedLabels []labelData
expectedLabels = []labelData{}
for _, l := range testCase.issue.Labels {
expectedLabels = append(expectedLabels, labelData{
Name: l.Name,
Color: l.Color,
Description: l.Description,
})
}
var expectedAssignees []string
expectedAssignees = []string{}
for _, a := range testCase.issue.Assignees {
expectedAssignees = append(expectedAssignees, a.UserName)
}
var expectedClosedAt *time.Time
if testCase.issue.Closed != nil {
expectedClosedAt = testCase.issue.Closed
}
var expectedComments []commentData
expectedComments = []commentData{}
if testCase.flagComments {
for _, c := range testCase.comments {
expectedComments = append(expectedComments, commentData{
ID: c.ID,
Author: c.Poster.UserName,
Body: c.Body,
Created: c.Created,
})
}
}
expected := issueData{
ID: testCase.issue.ID,
Index: testCase.issue.Index,
Title: testCase.issue.Title,
State: testCase.issue.State,
Created: testCase.issue.Created,
User: testCase.issue.Poster.UserName,
Body: testCase.issue.Body,
URL: testCase.issue.HTMLURL,
ClosedAt: expectedClosedAt,
Labels: expectedLabels,
Assignees: expectedAssignees,
Comments: expectedComments,
}
// validating reality
var actual issueData
dec := json.NewDecoder(bytes.NewReader(outBuffer.Bytes()))
dec.DisallowUnknownFields()
err = dec.Decode(&actual)
require.NoError(t, err, "Failed to unmarshal output into struct")
assert.Equal(t, expected, actual, "Expected structs differ from expected one")
})
}
}
func TestRunIssueDetailUsesOwnerFlag(t *testing.T) {
issueIndex := int64(12)
expectedOwner := "overrideOwner"
expectedRepo := "overrideRepo"
issue := &gitea.Issue{
ID: 99,
Index: issueIndex,
Title: "Owner override test",
State: gitea.StateOpen,
Created: time.Date(2025, 11, 1, 10, 0, 0, 0, time.UTC),
Poster: &gitea.User{
UserName: "tester",
},
HTMLURL: "https://example.test/issues/12",
}
config.SetConfigForTesting(config.LocalConfig{
Logins: []config.Login{{
Name: "testLogin",
URL: "https://gitea.example.com",
Token: "token",
User: "loginUser",
Default: true,
}},
})
cmd := cli.Command{
Name: "issues",
Flags: []cli.Flag{
&flags.LoginFlag,
&flags.RepoFlag,
&flags.RemoteFlag,
&flags.OutputFlag,
&cli.StringFlag{Name: "owner"},
&cli.BoolFlag{Name: "comments"},
},
}
var outBuffer bytes.Buffer
var errBuffer bytes.Buffer
cmd.Writer = &outBuffer
cmd.ErrWriter = &errBuffer
require.NoError(t, cmd.Set("login", "testLogin"))
require.NoError(t, cmd.Set("repo", expectedRepo))
require.NoError(t, cmd.Set("owner", expectedOwner))
require.NoError(t, cmd.Set("comments", "false"))
teaCtx, idx, err := resolveIssueDetailContext(&cmd, fmt.Sprintf("%d", issueIndex))
require.NoError(t, err)
client := &fakeIssueDetailClient{
issue: issue,
reactions: []*gitea.Reaction{},
}
err = runIssueDetailWithClient(teaCtx, idx, client)
require.NoError(t, err, "Expected runIssueDetail to succeed")
assert.Equal(t, expectedOwner, client.owner)
assert.Equal(t, expectedRepo, client.repo)
assert.Equal(t, issueIndex, client.index)
}

View File

@@ -1,14 +1,14 @@
// Copyright 2019 The Gitea Authors. All rights reserved. // Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"code.gitea.io/tea/cmd/labels" "code.gitea.io/tea/cmd/labels"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdLabels represents to operate repositories' labels. // CmdLabels represents to operate repositories' labels.
@@ -20,7 +20,7 @@ var CmdLabels = cli.Command{
Description: `Manage issue labels`, Description: `Manage issue labels`,
ArgsUsage: " ", // command does not accept arguments ArgsUsage: " ", // command does not accept arguments
Action: runLabels, Action: runLabels,
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&labels.CmdLabelsList, &labels.CmdLabelsList,
&labels.CmdLabelCreate, &labels.CmdLabelCreate,
&labels.CmdLabelUpdate, &labels.CmdLabelUpdate,
@@ -29,13 +29,13 @@ var CmdLabels = cli.Command{
Flags: labels.CmdLabelsList.Flags, Flags: labels.CmdLabelsList.Flags,
} }
func runLabels(ctx *cli.Context) error { func runLabels(ctx context.Context, cmd *cli.Command) error {
if ctx.Args().Len() == 1 { if cmd.Args().Len() == 1 {
return runLabelsDetails(ctx) return runLabelsDetails(cmd)
} }
return labels.RunLabelsList(ctx) return labels.RunLabelsList(ctx, cmd)
} }
func runLabelsDetails(ctx *cli.Context) error { func runLabelsDetails(cmd *cli.Command) error {
return fmt.Errorf("Not yet implemented") return fmt.Errorf("not yet implemented")
} }

View File

@@ -1,11 +1,11 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package labels package labels
import ( import (
"bufio" "bufio"
stdctx "context"
"log" "log"
"os" "os"
"strings" "strings"
@@ -14,7 +14,7 @@ import (
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdLabelCreate represents a sub command of labels to create label. // CmdLabelCreate represents a sub command of labels to create label.
@@ -45,45 +45,53 @@ var CmdLabelCreate = cli.Command{
}, flags.AllDefaultFlags...), }, flags.AllDefaultFlags...),
} }
func runLabelCreate(cmd *cli.Context) error { func runLabelCreate(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
labelFile := ctx.String("file") labelFile := ctx.String("file")
var err error
if len(labelFile) == 0 { if len(labelFile) == 0 {
_, _, err = ctx.Login.Client().CreateLabel(ctx.Owner, ctx.Repo, gitea.CreateLabelOption{ _, _, err := ctx.Login.Client().CreateLabel(ctx.Owner, ctx.Repo, gitea.CreateLabelOption{
Name: ctx.String("name"), Name: ctx.String("name"),
Color: ctx.String("color"), Color: ctx.String("color"),
Description: ctx.String("description"), Description: ctx.String("description"),
}) })
} else { return err
f, err := os.Open(labelFile)
if err != nil {
return err
}
defer f.Close()
scanner := bufio.NewScanner(f)
var i = 1
for scanner.Scan() {
line := scanner.Text()
color, name, description := splitLabelLine(line)
if color == "" || name == "" {
log.Printf("Line %d ignored because lack of enough fields: %s\n", i, line)
} else {
_, _, err = ctx.Login.Client().CreateLabel(ctx.Owner, ctx.Repo, gitea.CreateLabelOption{
Name: name,
Color: color,
Description: description,
})
}
i++
}
} }
return err f, err := os.Open(labelFile)
if err != nil {
return err
}
defer f.Close()
scanner := bufio.NewScanner(f)
i := 1
for scanner.Scan() {
line := scanner.Text()
color, name, description := splitLabelLine(line)
if color == "" || name == "" {
log.Printf("Line %d ignored because lack of enough fields: %s\n", i, line)
} else {
_, _, err = ctx.Login.Client().CreateLabel(ctx.Owner, ctx.Repo, gitea.CreateLabelOption{
Name: name,
Color: color,
Description: description,
})
if err != nil {
return err
}
}
i++
}
return nil
} }
func splitLabelLine(line string) (string, string, string) { func splitLabelLine(line string) (string, string, string) {

View File

@@ -1,6 +1,5 @@
// Copyright 2019 The Gitea Authors. All rights reserved. // Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package labels package labels
@@ -21,7 +20,7 @@ func TestParseLabelLine(t *testing.T) {
` `
scanner := bufio.NewScanner(strings.NewReader(labels)) scanner := bufio.NewScanner(strings.NewReader(labels))
var i = 1 i := 1
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
color, name, description := splitLabelLine(line) color, name, description := splitLabelLine(line)

View File

@@ -1,14 +1,16 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package labels package labels
import ( import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdLabelDelete represents a sub command of labels to delete label. // CmdLabelDelete represents a sub command of labels to delete label.
@@ -20,17 +22,37 @@ var CmdLabelDelete = cli.Command{
ArgsUsage: " ", // command does not accept arguments ArgsUsage: " ", // command does not accept arguments
Action: runLabelDelete, Action: runLabelDelete,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&cli.IntFlag{ &cli.Int64Flag{
Name: "id", Name: "id",
Usage: "label id", Usage: "label id",
Required: true,
}, },
}, flags.AllDefaultFlags...), }, flags.AllDefaultFlags...),
} }
func runLabelDelete(cmd *cli.Context) error { func runLabelDelete(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
_, err := ctx.Login.Client().DeleteLabel(ctx.Owner, ctx.Repo, ctx.Int64("id")) labelID := ctx.Int64("id")
return err client := ctx.Login.Client()
// Verify the label exists first
label, _, err := client.GetRepoLabel(ctx.Owner, ctx.Repo, labelID)
if err != nil {
return fmt.Errorf("failed to get label %d: %w", labelID, err)
}
_, err = client.DeleteLabel(ctx.Owner, ctx.Repo, labelID)
if err != nil {
return fmt.Errorf("failed to delete label '%s' (id: %d): %w", label.Name, labelID, err)
}
fmt.Printf("Label '%s' (id: %d) deleted successfully\n", label.Name, labelID)
return nil
} }

View File

@@ -1,17 +1,18 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package labels package labels
import ( import (
stdctx "context"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"code.gitea.io/tea/modules/task" "code.gitea.io/tea/modules/task"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdLabelsList represents a sub command of labels to list labels // CmdLabelsList represents a sub command of labels to list labels
@@ -34,13 +35,18 @@ var CmdLabelsList = cli.Command{
} }
// RunLabelsList list labels. // RunLabelsList list labels.
func RunLabelsList(cmd *cli.Context) error { func RunLabelsList(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := ctx.Login.Client() client := ctx.Login.Client()
labels, _, err := client.ListRepoLabels(ctx.Owner, ctx.Repo, gitea.ListLabelsOptions{ labels, _, err := client.ListRepoLabels(ctx.Owner, ctx.Repo, gitea.ListLabelsOptions{
ListOptions: ctx.GetListOptions(), ListOptions: flags.GetListOptions(cmd),
}) })
if err != nil { if err != nil {
return err return err
@@ -50,6 +56,5 @@ func RunLabelsList(cmd *cli.Context) error {
return task.LabelsExport(labels, ctx.String("save")) return task.LabelsExport(labels, ctx.String("save"))
} }
print.LabelsList(labels, ctx.Output) return print.LabelsList(labels, ctx.Output)
return nil
} }

View File

@@ -1,15 +1,16 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package labels package labels
import ( import (
stdctx "context"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdLabelUpdate represents a sub command of labels to update label. // CmdLabelUpdate represents a sub command of labels to update label.
@@ -20,7 +21,7 @@ var CmdLabelUpdate = cli.Command{
ArgsUsage: " ", // command does not accept arguments ArgsUsage: " ", // command does not accept arguments
Action: runLabelUpdate, Action: runLabelUpdate,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&cli.IntFlag{ &cli.Int64Flag{
Name: "id", Name: "id",
Usage: "label id", Usage: "label id",
}, },
@@ -39,9 +40,14 @@ var CmdLabelUpdate = cli.Command{
}, flags.AllDefaultFlags...), }, flags.AllDefaultFlags...),
} }
func runLabelUpdate(cmd *cli.Context) error { func runLabelUpdate(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
id := ctx.Int64("id") id := ctx.Int64("id")
var pName, pColor, pDescription *string var pName, pColor, pDescription *string
@@ -60,13 +66,11 @@ func runLabelUpdate(cmd *cli.Context) error {
pDescription = &description pDescription = &description
} }
var err error
_, _, err = ctx.Login.Client().EditLabel(ctx.Owner, ctx.Repo, id, gitea.EditLabelOption{ _, _, err = ctx.Login.Client().EditLabel(ctx.Owner, ctx.Repo, id, gitea.EditLabelOption{
Name: pName, Name: pName,
Color: pColor, Color: pColor,
Description: pDescription, Description: pDescription,
}) })
if err != nil { if err != nil {
return err return err
} }

View File

@@ -1,17 +1,17 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"code.gitea.io/tea/cmd/login" "code.gitea.io/tea/cmd/login"
"code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdLogin represents to login a gitea server. // CmdLogin represents to login a gitea server.
@@ -23,24 +23,29 @@ var CmdLogin = cli.Command{
Description: `Log in to a Gitea server`, Description: `Log in to a Gitea server`,
ArgsUsage: "[<login name>]", ArgsUsage: "[<login name>]",
Action: runLogins, Action: runLogins,
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&login.CmdLoginList, &login.CmdLoginList,
&login.CmdLoginAdd, &login.CmdLoginAdd,
&login.CmdLoginEdit, &login.CmdLoginEdit,
&login.CmdLoginDelete, &login.CmdLoginDelete,
&login.CmdLoginSetDefault, &login.CmdLoginSetDefault,
&login.CmdLoginHelper,
&login.CmdLoginOAuthRefresh,
}, },
} }
func runLogins(ctx *cli.Context) error { func runLogins(ctx context.Context, cmd *cli.Command) error {
if ctx.Args().Len() == 1 { if cmd.Args().Len() == 1 {
return runLoginDetail(ctx.Args().First()) return runLoginDetail(cmd.Args().First())
} }
return login.RunLoginList(ctx) return login.RunLoginList(ctx, cmd)
} }
func runLoginDetail(name string) error { func runLoginDetail(name string) error {
l := config.GetLoginByName(name) l, err := config.GetLoginByName(name)
if err != nil {
return err
}
if l == nil { if l == nil {
fmt.Printf("Login '%s' do not exist\n\n", name) fmt.Printf("Login '%s' do not exist\n\n", name)
return nil return nil

View File

@@ -1,14 +1,17 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package login package login
import ( import (
"context"
"fmt"
"code.gitea.io/tea/modules/auth"
"code.gitea.io/tea/modules/interact" "code.gitea.io/tea/modules/interact"
"code.gitea.io/tea/modules/task" "code.gitea.io/tea/modules/task"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdLoginAdd represents to login a gitea server. // CmdLoginAdd represents to login a gitea server.
@@ -27,56 +30,136 @@ var CmdLoginAdd = cli.Command{
Name: "url", Name: "url",
Aliases: []string{"u"}, Aliases: []string{"u"},
Value: "https://gitea.com", Value: "https://gitea.com",
EnvVars: []string{"GITEA_SERVER_URL"}, Sources: cli.EnvVars("GITEA_SERVER_URL"),
Usage: "Server URL", Usage: "Server URL",
}, },
&cli.BoolFlag{
Name: "no-version-check",
Aliases: []string{"nv"},
Usage: "Do not check version of Gitea instance",
},
&cli.StringFlag{ &cli.StringFlag{
Name: "token", Name: "token",
Aliases: []string{"t"}, Aliases: []string{"t"},
Value: "", Value: "",
EnvVars: []string{"GITEA_SERVER_TOKEN"}, Sources: cli.EnvVars("GITEA_SERVER_TOKEN"),
Usage: "Access token. Can be obtained from Settings > Applications", Usage: "Access token. Can be obtained from Settings > Applications",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "user", Name: "user",
Value: "", Value: "",
EnvVars: []string{"GITEA_SERVER_USER"}, Sources: cli.EnvVars("GITEA_SERVER_USER"),
Usage: "User for basic auth (will create token)", Usage: "User for basic auth (will create token)",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "password", Name: "password",
Aliases: []string{"pwd"}, Aliases: []string{"pwd"},
Value: "", Value: "",
EnvVars: []string{"GITEA_SERVER_PASSWORD"}, Sources: cli.EnvVars("GITEA_SERVER_PASSWORD"),
Usage: "Password for basic auth (will create token)", Usage: "Password for basic auth (will create token)",
}, },
&cli.StringFlag{
Name: "otp",
Sources: cli.EnvVars("GITEA_SERVER_OTP"),
Usage: "OTP token for auth, if necessary",
},
&cli.StringFlag{
Name: "scopes",
Sources: cli.EnvVars("GITEA_SCOPES"),
Usage: "Token scopes to add when creating a new token, separated by a comma",
},
&cli.StringFlag{ &cli.StringFlag{
Name: "ssh-key", Name: "ssh-key",
Aliases: []string{"s"}, Aliases: []string{"s"},
Usage: "Path to a SSH key to use, overrides auto-discovery", Usage: "Path to a SSH key/certificate to use, overrides auto-discovery",
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "insecure", Name: "insecure",
Aliases: []string{"i"}, Aliases: []string{"i"},
Usage: "Disable TLS verification", Usage: "Disable TLS verification",
}, },
&cli.StringFlag{
Name: "ssh-agent-principal",
Aliases: []string{"c"},
Usage: "Use SSH certificate with specified principal to login (needs a running ssh-agent with certificate loaded)",
},
&cli.StringFlag{
Name: "ssh-agent-key",
Aliases: []string{"a"},
Usage: "Use SSH public key or SSH fingerprint to login (needs a running ssh-agent with ssh key loaded)",
},
&cli.BoolFlag{
Name: "helper",
Aliases: []string{"j"},
Usage: "Add helper",
},
&cli.BoolFlag{
Name: "oauth",
Aliases: []string{"o"},
Usage: "Use interactive OAuth2 flow for authentication",
},
&cli.StringFlag{
Name: "client-id",
Usage: "OAuth client ID (for use with --oauth)",
},
&cli.StringFlag{
Name: "redirect-url",
Usage: "OAuth redirect URL (for use with --oauth)",
},
}, },
Action: runLoginAdd, Action: runLoginAdd,
} }
func runLoginAdd(ctx *cli.Context) error { func runLoginAdd(_ context.Context, cmd *cli.Command) error {
// if no args create login interactive // if no args create login interactive
if ctx.NumFlags() == 0 { if cmd.NumFlags() == 0 {
return interact.CreateLogin() if err := interact.CreateLogin(); err != nil && !interact.IsQuitting(err) {
return fmt.Errorf("error adding login: %w", err)
}
return nil
}
// if OAuth flag is provided, use OAuth2 PKCE flow
if cmd.Bool("oauth") {
opts := auth.OAuthOptions{
Name: cmd.String("name"),
URL: cmd.String("url"),
Insecure: cmd.Bool("insecure"),
}
// Only set clientID if provided
if cmd.String("client-id") != "" {
opts.ClientID = cmd.String("client-id")
}
// Only set redirect URL if provided
if cmd.String("redirect-url") != "" {
opts.RedirectURL = cmd.String("redirect-url")
}
return auth.OAuthLoginWithFullOptions(opts)
}
sshAgent := false
if cmd.String("ssh-agent-key") != "" || cmd.String("ssh-agent-principal") != "" {
sshAgent = true
} }
// else use args to add login // else use args to add login
return task.CreateLogin( return task.CreateLogin(
ctx.String("name"), cmd.String("name"),
ctx.String("token"), cmd.String("token"),
ctx.String("user"), cmd.String("user"),
ctx.String("password"), cmd.String("password"),
ctx.String("ssh-key"), cmd.String("otp"),
ctx.String("url"), cmd.String("scopes"),
ctx.Bool("insecure")) cmd.String("ssh-key"),
cmd.String("url"),
cmd.String("ssh-agent-principal"),
cmd.String("ssh-agent-key"),
cmd.Bool("insecure"),
sshAgent,
!cmd.Bool("no-version-check"),
cmd.Bool("helper"),
)
} }

View File

@@ -1,16 +1,16 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package login package login
import ( import (
"context"
"fmt" "fmt"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/config"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdLoginSetDefault represents to login a gitea server. // CmdLoginSetDefault represents to login a gitea server.
@@ -23,8 +23,8 @@ var CmdLoginSetDefault = cli.Command{
Flags: []cli.Flag{&flags.OutputFlag}, Flags: []cli.Flag{&flags.OutputFlag},
} }
func runLoginSetDefault(ctx *cli.Context) error { func runLoginSetDefault(_ context.Context, cmd *cli.Command) error {
if ctx.Args().Len() == 0 { if cmd.Args().Len() == 0 {
l, err := config.GetDefaultLogin() l, err := config.GetDefaultLogin()
if err != nil { if err != nil {
return err return err
@@ -33,6 +33,6 @@ func runLoginSetDefault(ctx *cli.Context) error {
return nil return nil
} }
name := ctx.Args().First() name := cmd.Args().First()
return config.SetDefaultLogin(name) return config.SetDefaultLogin(name)
} }

View File

@@ -1,16 +1,15 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package login package login
import ( import (
"errors" "context"
"log" "fmt"
"code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/config"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdLoginDelete is a command to delete a login // CmdLoginDelete is a command to delete a login
@@ -24,20 +23,20 @@ var CmdLoginDelete = cli.Command{
} }
// RunLoginDelete runs the action of a login delete command // RunLoginDelete runs the action of a login delete command
func RunLoginDelete(ctx *cli.Context) error { func RunLoginDelete(_ context.Context, cmd *cli.Command) error {
logins, err := config.GetLogins() logins, err := config.GetLogins()
if err != nil { if err != nil {
log.Fatal(err) return err
} }
var name string var name string
if len(ctx.Args().First()) != 0 { if len(cmd.Args().First()) != 0 {
name = ctx.Args().First() name = cmd.Args().First()
} else if len(logins) == 1 { } else if len(logins) == 1 {
name = logins[0].Name name = logins[0].Name
} else { } else {
return errors.New("Please specify a login name") return fmt.Errorf("please specify a login name")
} }
return config.DeleteLogin(name) return config.DeleteLogin(name)

View File

@@ -1,15 +1,18 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package login package login
import ( import (
"context"
"os"
"os/exec"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/config"
"github.com/skratchdot/open-golang/open" "github.com/skratchdot/open-golang/open"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdLoginEdit represents to login a gitea server. // CmdLoginEdit represents to login a gitea server.
@@ -23,6 +26,15 @@ var CmdLoginEdit = cli.Command{
Flags: []cli.Flag{&flags.OutputFlag}, Flags: []cli.Flag{&flags.OutputFlag},
} }
func runLoginEdit(_ *cli.Context) error { func runLoginEdit(_ context.Context, _ *cli.Command) error {
if e, ok := os.LookupEnv("EDITOR"); ok && e != "" {
cmd := exec.Command(e, config.GetConfigPath())
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
}
return open.Start(config.GetConfigPath()) return open.Start(config.GetConfigPath())
} }

145
cmd/login/helper.go Normal file
View File

@@ -0,0 +1,145 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package login
import (
"bufio"
"context"
"fmt"
"net/url"
"os"
"strings"
"code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/task"
"github.com/urfave/cli/v3"
)
// CmdLoginHelper represents to login a gitea helper.
var CmdLoginHelper = cli.Command{
Name: "helper",
Aliases: []string{"git-credential"},
Usage: "Git helper",
Description: `Git helper`,
Hidden: true,
Commands: []*cli.Command{
{
Name: "store",
Description: "Command drops",
Aliases: []string{"erase"},
Action: func(_ context.Context, _ *cli.Command) error {
return nil
},
},
{
Name: "setup",
Description: "Setup helper to tea authenticate",
Action: func(_ context.Context, _ *cli.Command) error {
logins, err := config.GetLogins()
if err != nil {
return err
}
for _, login := range logins {
added, err := task.SetupHelper(login)
if err != nil {
return err
} else if added {
fmt.Printf("Added \"%s\"\n", login.Name)
} else {
fmt.Printf("\"%s\" has already been added!\n", login.Name)
}
}
return nil
},
},
{
Name: "get",
Description: "Get token to auth",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "login",
Aliases: []string{"l"},
Usage: "Use a specific login",
},
},
Action: func(_ context.Context, cmd *cli.Command) error {
wants := map[string]string{}
s := bufio.NewScanner(os.Stdin)
for s.Scan() {
line := s.Text()
if line == "" {
break
}
parts := strings.SplitN(line, "=", 2)
if len(parts) < 2 {
continue
}
key, value := parts[0], parts[1]
if key == "url" {
u, err := url.Parse(value)
if err != nil {
return err
}
wants["protocol"] = u.Scheme
wants["host"] = u.Host
wants["path"] = u.Path
wants["username"] = u.User.Username()
wants["password"], _ = u.User.Password()
} else {
wants[key] = value
}
}
if len(wants["host"]) == 0 {
return fmt.Errorf("hostname is required")
} else if len(wants["protocol"]) == 0 {
wants["protocol"] = "http"
}
// Use --login flag if provided, otherwise fall back to host lookup
var userConfig *config.Login
if loginName := cmd.String("login"); loginName != "" {
var lookupErr error
userConfig, lookupErr = config.GetLoginByName(loginName)
if lookupErr != nil {
return lookupErr
}
if userConfig == nil {
return fmt.Errorf("login '%s' not found", loginName)
}
} else {
var lookupErr error
userConfig, lookupErr = config.GetLoginByHost(wants["host"])
if lookupErr != nil {
return lookupErr
}
if userConfig == nil {
return fmt.Errorf("no login found for host '%s'", wants["host"])
}
}
if len(userConfig.GetAccessToken()) == 0 {
return fmt.Errorf("user not set")
}
host, err := url.Parse(userConfig.URL)
if err != nil {
return err
}
// Refresh token if expired or near expiry (updates userConfig in place)
if err = userConfig.RefreshOAuthTokenIfNeeded(); err != nil {
return err
}
_, err = fmt.Fprintf(os.Stdout, "protocol=%s\nhost=%s\nusername=%s\npassword=%s\n", host.Scheme, host.Host, userConfig.User, userConfig.GetAccessToken())
if err != nil {
return err
}
return nil
},
},
},
}

View File

@@ -1,15 +1,16 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package login package login
import ( import (
"context"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/config" "code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdLoginList represents to login a gitea server. // CmdLoginList represents to login a gitea server.
@@ -24,11 +25,10 @@ var CmdLoginList = cli.Command{
} }
// RunLoginList list all logins // RunLoginList list all logins
func RunLoginList(cmd *cli.Context) error { func RunLoginList(_ context.Context, cmd *cli.Command) error {
logins, err := config.GetLogins() logins, err := config.GetLogins()
if err != nil { if err != nil {
return err return err
} }
print.LoginsList(logins, cmd.String("output")) return print.LoginsList(logins, cmd.String("output"))
return nil
} }

View File

@@ -0,0 +1,71 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package login
import (
"context"
"fmt"
"code.gitea.io/tea/modules/auth"
"code.gitea.io/tea/modules/config"
"github.com/urfave/cli/v3"
)
// CmdLoginOAuthRefresh represents a command to refresh an OAuth token
var CmdLoginOAuthRefresh = cli.Command{
Name: "oauth-refresh",
Usage: "Refresh an OAuth token",
Description: "Manually refresh an expired OAuth token. If the refresh token is also expired, opens a browser for re-authentication.",
ArgsUsage: "[<login name>]",
Action: runLoginOAuthRefresh,
}
func runLoginOAuthRefresh(_ context.Context, cmd *cli.Command) error {
var loginName string
// Get login name from args or use default
if cmd.Args().Len() > 0 {
loginName = cmd.Args().First()
} else {
// Get default login
login, err := config.GetDefaultLogin()
if err != nil {
return fmt.Errorf("no login specified and no default login found: %s", err)
}
loginName = login.Name
}
// Get the login from config
login, err := config.GetLoginByName(loginName)
if err != nil {
return err
}
if login == nil {
return fmt.Errorf("login '%s' not found", loginName)
}
// Check if the login has a refresh token
if login.GetRefreshToken() == "" {
return fmt.Errorf("login '%s' does not have a refresh token. It may have been created using a different authentication method", loginName)
}
// Try to refresh the token
err = auth.RefreshAccessToken(login)
if err == nil {
fmt.Printf("Successfully refreshed OAuth token for %s\n", loginName)
return nil
}
// Refresh failed - fall back to browser-based re-authentication
fmt.Printf("Token refresh failed: %s\n", err)
fmt.Println("Opening browser for re-authentication...")
if err := auth.ReauthenticateLogin(login); err != nil {
return fmt.Errorf("re-authentication failed: %s", err)
}
fmt.Printf("Successfully re-authenticated %s\n", loginName)
return nil
}

View File

@@ -1,13 +1,12 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package cmd package cmd
import ( import (
"code.gitea.io/tea/cmd/login" "code.gitea.io/tea/cmd/login"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdLogout represents to logout a gitea server. // CmdLogout represents to logout a gitea server.

62
cmd/man.go Normal file
View File

@@ -0,0 +1,62 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
"os"
"path/filepath"
docs "github.com/urfave/cli-docs/v3"
"github.com/urfave/cli/v3"
)
// DocRenderFlags are the flags for documentation generation, used by `./docs/docs.go` and the `generate-man-page` sub command
var DocRenderFlags = []cli.Flag{
&cli.StringFlag{
Name: "out",
Usage: "Path to output docs to, otherwise prints to stdout",
Aliases: []string{"o"},
},
}
// CmdGenerateManPage is the sub command to generate the `tea` man page
var CmdGenerateManPage = cli.Command{
Name: "man",
Usage: "Generate man page",
Hidden: true,
Flags: DocRenderFlags,
Action: func(ctx context.Context, cmd *cli.Command) error {
return RenderDocs(cmd, cmd.Root(), docs.ToMan)
},
}
// RenderDocs renders the documentation for `target` using the supplied `render` function
func RenderDocs(cmd, target *cli.Command, render func(*cli.Command) (string, error)) error {
out, err := render(target)
if err != nil {
return err
}
outPath := cmd.String("out")
if outPath == "" {
fmt.Print(out)
return nil
}
if err = os.MkdirAll(filepath.Dir(outPath), os.ModePerm); err != nil {
return err
}
fi, err := os.Create(outPath)
if err != nil {
return err
}
defer fi.Close()
if _, err = fi.WriteString(out); err != nil {
return err
}
return nil
}

View File

@@ -1,15 +1,15 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package cmd package cmd
import ( import (
stdctx "context"
"code.gitea.io/tea/cmd/milestones" "code.gitea.io/tea/cmd/milestones"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"github.com/urfave/cli/v3"
"github.com/urfave/cli/v2"
) )
// CmdMilestones represents to operate repositories milestones. // CmdMilestones represents to operate repositories milestones.
@@ -21,7 +21,7 @@ var CmdMilestones = cli.Command{
Description: `List and create milestones`, Description: `List and create milestones`,
ArgsUsage: "[<milestone name>]", ArgsUsage: "[<milestone name>]",
Action: runMilestones, Action: runMilestones,
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&milestones.CmdMilestonesList, &milestones.CmdMilestonesList,
&milestones.CmdMilestonesCreate, &milestones.CmdMilestonesCreate,
&milestones.CmdMilestonesClose, &milestones.CmdMilestonesClose,
@@ -32,16 +32,21 @@ var CmdMilestones = cli.Command{
Flags: milestones.CmdMilestonesList.Flags, Flags: milestones.CmdMilestonesList.Flags,
} }
func runMilestones(ctx *cli.Context) error { func runMilestones(ctx stdctx.Context, cmd *cli.Command) error {
if ctx.Args().Len() == 1 { if cmd.Args().Len() == 1 {
return runMilestoneDetail(ctx, ctx.Args().First()) return runMilestoneDetail(ctx, cmd, cmd.Args().First())
} }
return milestones.RunMilestonesList(ctx) return milestones.RunMilestonesList(ctx, cmd)
} }
func runMilestoneDetail(cmd *cli.Context, name string) error { func runMilestoneDetail(_ stdctx.Context, cmd *cli.Command, name string) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := ctx.Login.Client() client := ctx.Login.Client()
milestone, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, name) milestone, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, name)

View File

@@ -1,26 +1,26 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package milestones package milestones
import ( import (
"code.gitea.io/tea/cmd/flags" "context"
"github.com/urfave/cli/v2" "code.gitea.io/tea/cmd/flags"
"github.com/urfave/cli/v3"
) )
// CmdMilestonesClose represents a sub command of milestones to close an milestone // CmdMilestonesClose represents a sub command of milestones to close an milestone
var CmdMilestonesClose = cli.Command{ var CmdMilestonesClose = cli.Command{
Name: "close", Name: "close",
Usage: "Change state of an milestone to 'closed'", Usage: "Change state of one or more milestones to 'closed'",
Description: `Change state of an milestone to 'closed'`, Description: `Change state of one or more milestones to 'closed'`,
ArgsUsage: "<milestone name>", ArgsUsage: "<milestone name> [<milestone name>...]",
Action: func(ctx *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
if ctx.Bool("force") { if cmd.Bool("force") {
return deleteMilestone(ctx) return deleteMilestone(ctx, cmd)
} }
return editMilestoneStatus(ctx, true) return editMilestoneStatus(ctx, cmd, true)
}, },
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{

View File

@@ -1,20 +1,20 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package milestones package milestones
import ( import (
"time" "time"
stdctx "context"
"code.gitea.io/sdk/gitea"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/interact" "code.gitea.io/tea/modules/interact"
"code.gitea.io/tea/modules/task" "code.gitea.io/tea/modules/task"
"code.gitea.io/sdk/gitea"
"github.com/araddon/dateparse" "github.com/araddon/dateparse"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdMilestonesCreate represents a sub command of milestones to create milestone // CmdMilestonesCreate represents a sub command of milestones to create milestone
@@ -49,8 +49,11 @@ var CmdMilestonesCreate = cli.Command{
}, flags.AllDefaultFlags...), }, flags.AllDefaultFlags...),
} }
func runMilestonesCreate(cmd *cli.Context) error { func runMilestonesCreate(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
if err != nil {
return err
}
date := ctx.String("deadline") date := ctx.String("deadline")
deadline := &time.Time{} deadline := &time.Time{}
@@ -67,8 +70,11 @@ func runMilestonesCreate(cmd *cli.Context) error {
state = gitea.StateClosed state = gitea.StateClosed
} }
if ctx.NumFlags() == 0 { if ctx.IsInteractiveMode() {
return interact.CreateMilestone(ctx.Login, ctx.Owner, ctx.Repo) if err := interact.CreateMilestone(ctx.Login, ctx.Owner, ctx.Repo); err != nil && !interact.IsQuitting(err) {
return err
}
return nil
} }
return task.CreateMilestone( return task.CreateMilestone(

View File

@@ -1,14 +1,15 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package milestones package milestones
import ( import (
stdctx "context"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdMilestonesDelete represents a sub command of milestones to delete an milestone // CmdMilestonesDelete represents a sub command of milestones to delete an milestone
@@ -22,11 +23,16 @@ var CmdMilestonesDelete = cli.Command{
Flags: flags.AllDefaultFlags, Flags: flags.AllDefaultFlags,
} }
func deleteMilestone(cmd *cli.Context) error { func deleteMilestone(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := ctx.Login.Client() client := ctx.Login.Client()
_, err := client.DeleteMilestoneByName(ctx.Owner, ctx.Repo, ctx.Args().First()) _, err = client.DeleteMilestoneByName(ctx.Owner, ctx.Repo, ctx.Args().First())
return err return err
} }

View File

@@ -1,19 +1,19 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package milestones package milestones
import ( import (
"fmt" "fmt"
stdctx "context"
"code.gitea.io/sdk/gitea"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"code.gitea.io/tea/modules/utils" "code.gitea.io/tea/modules/utils"
"github.com/urfave/cli/v3"
"code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v2"
) )
var msIssuesFieldsFlag = flags.FieldsFlag(print.IssueFields, []string{ var msIssuesFieldsFlag = flags.FieldsFlag(print.IssueFields, []string{
@@ -28,7 +28,7 @@ var CmdMilestonesIssues = cli.Command{
Description: "manage issue/pull of an milestone", Description: "manage issue/pull of an milestone",
ArgsUsage: "<milestone name>", ArgsUsage: "<milestone name>",
Action: runMilestoneIssueList, Action: runMilestoneIssueList,
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&CmdMilestoneAddIssue, &CmdMilestoneAddIssue,
&CmdMilestoneRemoveIssue, &CmdMilestoneRemoveIssue,
}, },
@@ -70,40 +70,39 @@ var CmdMilestoneRemoveIssue = cli.Command{
Flags: flags.AllDefaultFlags, Flags: flags.AllDefaultFlags,
} }
func runMilestoneIssueList(cmd *cli.Context) error { func runMilestoneIssueList(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := ctx.Login.Client() client := ctx.Login.Client()
state := gitea.StateOpen state, err := flags.ParseState(ctx.String("state"))
switch ctx.String("state") { if err != nil {
case "all": return err
state = gitea.StateAll
case "closed":
state = gitea.StateClosed
} }
kind := gitea.IssueTypeAll kind, err := flags.ParseIssueKind(ctx.String("kind"), gitea.IssueTypeAll)
switch ctx.String("kind") { if err != nil {
case "issue": return err
kind = gitea.IssueTypeIssue
case "pull":
kind = gitea.IssueTypePull
} }
if ctx.Args().Len() != 1 { if ctx.Args().Len() != 1 {
return fmt.Errorf("Must specify milestone name") return fmt.Errorf("milestone name is required")
} }
milestone := ctx.Args().First() milestone := ctx.Args().First()
// make sure milestone exist // make sure milestone exist
_, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, milestone) _, _, err = client.GetMilestoneByName(ctx.Owner, ctx.Repo, milestone)
if err != nil { if err != nil {
return err return err
} }
issues, _, err := client.ListRepoIssues(ctx.Owner, ctx.Repo, gitea.ListIssueOption{ issues, _, err := client.ListRepoIssues(ctx.Owner, ctx.Repo, gitea.ListIssueOption{
ListOptions: ctx.GetListOptions(), ListOptions: flags.GetListOptions(cmd),
Milestones: []string{milestone}, Milestones: []string{milestone},
Type: kind, Type: kind,
State: state, State: state,
@@ -116,13 +115,17 @@ func runMilestoneIssueList(cmd *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
print.IssuesPullsList(issues, ctx.Output, fields) return print.IssuesPullsList(issues, ctx.Output, fields)
return nil
} }
func runMilestoneIssueAdd(cmd *cli.Context) error { func runMilestoneIssueAdd(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := ctx.Login.Client() client := ctx.Login.Client()
if ctx.Args().Len() != 2 { if ctx.Args().Len() != 2 {
return fmt.Errorf("need two arguments") return fmt.Errorf("need two arguments")
@@ -138,18 +141,26 @@ func runMilestoneIssueAdd(cmd *cli.Context) error {
// make sure milestone exist // make sure milestone exist
mile, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, mileName) mile, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, mileName)
if err != nil { if err != nil {
return err return fmt.Errorf("failed to get milestone '%s': %w", mileName, err)
} }
_, _, err = client.EditIssue(ctx.Owner, ctx.Repo, idx, gitea.EditIssueOption{ _, _, err = client.EditIssue(ctx.Owner, ctx.Repo, idx, gitea.EditIssueOption{
Milestone: &mile.ID, Milestone: &mile.ID,
}) })
return err if err != nil {
return fmt.Errorf("failed to add issue #%d to milestone '%s': %w", idx, mileName, err)
}
return nil
} }
func runMilestoneIssueRemove(cmd *cli.Context) error { func runMilestoneIssueRemove(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := ctx.Login.Client() client := ctx.Login.Client()
if ctx.Args().Len() != 2 { if ctx.Args().Len() != 2 {
return fmt.Errorf("need two arguments") return fmt.Errorf("need two arguments")
@@ -159,25 +170,28 @@ func runMilestoneIssueRemove(cmd *cli.Context) error {
issueIndex := ctx.Args().Get(1) issueIndex := ctx.Args().Get(1)
idx, err := utils.ArgToIndex(issueIndex) idx, err := utils.ArgToIndex(issueIndex)
if err != nil { if err != nil {
return err return fmt.Errorf("invalid issue index '%s': %w", issueIndex, err)
} }
issue, _, err := client.GetIssue(ctx.Owner, ctx.Repo, idx) issue, _, err := client.GetIssue(ctx.Owner, ctx.Repo, idx)
if err != nil { if err != nil {
return err return fmt.Errorf("failed to get issue #%d: %w", idx, err)
} }
if issue.Milestone == nil { if issue.Milestone == nil {
return fmt.Errorf("issue is not assigned to a milestone") return fmt.Errorf("issue #%d is not assigned to a milestone", idx)
} }
if issue.Milestone.Title != mileName { if issue.Milestone.Title != mileName {
return fmt.Errorf("issue is not assigned to this milestone") return fmt.Errorf("issue #%d is assigned to milestone '%s', not '%s'", idx, issue.Milestone.Title, mileName)
} }
zero := int64(0) zero := int64(0)
_, _, err = client.EditIssue(ctx.Owner, ctx.Repo, idx, gitea.EditIssueOption{ _, _, err = client.EditIssue(ctx.Owner, ctx.Repo, idx, gitea.EditIssueOption{
Milestone: &zero, Milestone: &zero,
}) })
return err if err != nil {
return fmt.Errorf("failed to remove issue #%d from milestone '%s': %w", idx, mileName, err)
}
return nil
} }

View File

@@ -1,16 +1,17 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package milestones package milestones
import ( import (
stdctx "context"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print" "code.gitea.io/tea/modules/print"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var fieldsFlag = flags.FieldsFlag(print.MilestoneFields, []string{ var fieldsFlag = flags.FieldsFlag(print.MilestoneFields, []string{
@@ -38,36 +39,36 @@ var CmdMilestonesList = cli.Command{
} }
// RunMilestonesList list milestones // RunMilestonesList list milestones
func RunMilestonesList(cmd *cli.Context) error { func RunMilestonesList(_ stdctx.Context, cmd *cli.Command) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
fields, err := fieldsFlag.GetValues(cmd) fields, err := fieldsFlag.GetValues(cmd)
if err != nil { if err != nil {
return err return err
} }
state := gitea.StateOpen state, err := flags.ParseState(ctx.String("state"))
switch ctx.String("state") { if err != nil {
case "all": return err
state = gitea.StateAll }
if !cmd.IsSet("fields") { // add to default fields if state == gitea.StateAll && !cmd.IsSet("fields") {
fields = append(fields, "state") fields = append(fields, "state")
}
case "closed":
state = gitea.StateClosed
} }
client := ctx.Login.Client() client := ctx.Login.Client()
milestones, _, err := client.ListRepoMilestones(ctx.Owner, ctx.Repo, gitea.ListMilestoneOption{ milestones, _, err := client.ListRepoMilestones(ctx.Owner, ctx.Repo, gitea.ListMilestoneOption{
ListOptions: ctx.GetListOptions(), ListOptions: flags.GetListOptions(cmd),
State: state, State: state,
}) })
if err != nil { if err != nil {
return err return err
} }
print.MilestonesList(milestones, ctx.Output, fields) return print.MilestonesList(milestones, ctx.Output, fields)
return nil
} }

View File

@@ -1,43 +1,73 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package milestones package milestones
import ( import (
stdctx "context"
"fmt"
"code.gitea.io/tea/cmd/flags" "code.gitea.io/tea/cmd/flags"
"code.gitea.io/tea/modules/context" "code.gitea.io/tea/modules/context"
"code.gitea.io/tea/modules/print"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdMilestonesReopen represents a sub command of milestones to open an milestone // CmdMilestonesReopen represents a sub command of milestones to open an milestone
var CmdMilestonesReopen = cli.Command{ var CmdMilestonesReopen = cli.Command{
Name: "reopen", Name: "reopen",
Aliases: []string{"open"}, Aliases: []string{"open"},
Usage: "Change state of an milestone to 'open'", Usage: "Change state of one or more milestones to 'open'",
Description: `Change state of an milestone to 'open'`, Description: `Change state of one or more milestones to 'open'`,
ArgsUsage: "<milestone name>", ArgsUsage: "<milestone name> [<milestone name> ...]",
Action: func(ctx *cli.Context) error { Action: func(ctx stdctx.Context, cmd *cli.Command) error {
return editMilestoneStatus(ctx, false) return editMilestoneStatus(ctx, cmd, false)
}, },
Flags: flags.AllDefaultFlags, Flags: flags.AllDefaultFlags,
} }
func editMilestoneStatus(cmd *cli.Context, close bool) error { func editMilestoneStatus(_ stdctx.Context, cmd *cli.Command, close bool) error {
ctx := context.InitCommand(cmd) ctx, err := context.InitCommand(cmd)
ctx.Ensure(context.CtxRequirement{RemoteRepo: true}) if err != nil {
client := ctx.Login.Client() return err
}
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
if ctx.Args().Len() == 0 {
return fmt.Errorf("missing required argument: %s", ctx.Command.ArgsUsage)
}
state := gitea.StateOpen state := gitea.StateOpen
if close { if close {
state = gitea.StateClosed state = gitea.StateClosed
} }
_, _, err := client.EditMilestoneByName(ctx.Owner, ctx.Repo, ctx.Args().First(), gitea.EditMilestoneOption{
State: &state,
Title: ctx.Args().First(),
})
return err client := ctx.Login.Client()
repoURL := ""
if ctx.Args().Len() > 1 {
repoURL, err = ctx.GetRemoteRepoHTMLURL()
if err != nil {
return err
}
}
for _, ms := range ctx.Args().Slice() {
opts := gitea.EditMilestoneOption{
State: &state,
Title: ms,
}
milestone, _, err := client.EditMilestoneByName(ctx.Owner, ctx.Repo, ms, opts)
if err != nil {
return err
}
if ctx.Args().Len() > 1 {
fmt.Printf("%s/milestone/%d\n", repoURL, milestone.ID)
} else {
print.MilestoneDetails(milestone)
}
}
return nil
} }

View File

@@ -1,13 +1,12 @@
// Copyright 2020 The Gitea Authors. All rights reserved. // Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // SPDX-License-Identifier: MIT
// license that can be found in the LICENSE file.
package cmd package cmd
import ( import (
"code.gitea.io/tea/cmd/notifications" "code.gitea.io/tea/cmd/notifications"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdNotifications is the main command to operate with notifications // CmdNotifications is the main command to operate with notifications
@@ -18,7 +17,7 @@ var CmdNotifications = cli.Command{
Usage: "Show notifications", Usage: "Show notifications",
Description: "Show notifications, by default based on the current repo if available", Description: "Show notifications, by default based on the current repo if available",
Action: notifications.RunNotificationsList, Action: notifications.RunNotificationsList,
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&notifications.CmdNotificationsList, &notifications.CmdNotificationsList,
&notifications.CmdNotificationsMarkRead, &notifications.CmdNotificationsMarkRead,
&notifications.CmdNotificationsMarkUnread, &notifications.CmdNotificationsMarkUnread,

Some files were not shown because too many files have changed in this diff Show More