enhance: replace tea CLI with gt (#40036)

tea has been replaced by [gitea-cli](https://gitea.com/gitea/cli)
(`gt`), which reuses tea's OAuth2 client ID.

- The repository clone panel now offers `gt repo clone owner/repo` under
a "Gitea CLI" tab.
- The built-in OAuth2 application is renamed from `tea` to `gitea-cli`.
`tea` in `[oauth2] DEFAULT_APPLICATIONS` is still accepted as an alias,
so existing configs keep working.
- Instances that already registered the app keep its stored display name
"tea".
This commit is contained in:
bircni authored and GitHub committed 2026-11-01 12:47:10 +00:00
1 parent c62e07f18a
commit d3021b69ee
11 files changed
+36 -31

No files matched your search

+1 -1
View File
@@ -68,7 +68,7 @@ Get more information from [the translation section of our contributing guide](CO
## Official and Third-Party Projects
We provide an official [go-sdk](https://gitea.com/gitea/go-sdk), a CLI tool called [tea](https://gitea.com/gitea/tea) and an [action runner](https://gitea.com/gitea/runner) for Gitea Action.
We provide an official [go-sdk](https://gitea.com/gitea/go-sdk), a CLI tool called [gt](https://gitea.com/gitea/cli) and an [action runner](https://gitea.com/gitea/runner) for Gitea Action.
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea), where you can discover more third-party projects, including SDKs, plugins, themes, and more.
+1 -1
View File
@@ -76,7 +76,7 @@
## 官方和第三方项目
我们提供了一个官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一个名为 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一个 Gitea Action 的 [action runner](https://gitea.com/gitea/runner)。
我们提供了一个官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一个名为 [gt](https://gitea.com/gitea/cli) 的 CLI 工具和一个 Gitea Action 的 [action runner](https://gitea.com/gitea/runner)。
我们在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 维护了一个 Gitea 相关项目的列表,您可以在那里发现更多的第三方项目,包括 SDK、插件、主题等。
+1 -1
View File
@@ -76,7 +76,7 @@
## 官方和第三方項目
我們提供了一個官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一個名為 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一個 Gitea Action 的 [action runner](https://gitea.com/gitea/runner)。
我們提供了一個官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一個名為 [gt](https://gitea.com/gitea/cli) 的 CLI 工具和一個 Gitea Action 的 [action runner](https://gitea.com/gitea/runner)。
我們在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 維護了一個 Gitea 相關項目的列表,您可以在那裡發現更多的第三方項目,包括 SDK、插件、主題等。
+2 -2
View File
@@ -637,10 +637,10 @@ ENABLED = true
;; Pre-register OAuth2 applications for some universally useful services
;; * https://github.com/hickford/git-credential-oauth
;; * https://github.com/git-ecosystem/git-credential-manager
;; * https://gitea.com/gitea/tea
;; * https://gitea.com/gitea/cli
;; * Gitea App (the official Gitea mobile app)
;; * https://gitea.com/gitea/gitea-vscode
;DEFAULT_APPLICATIONS = git-credential-oauth, git-credential-manager, tea, gitea-app, gitea-vscode
;DEFAULT_APPLICATIONS = git-credential-oauth, git-credential-manager, gitea-cli, gitea-app, gitea-vscode
;;
;; By default, OAuth2 applications can only use "http" and "https" as their redirect URI schemes.
;; If you need to use other schemes (e.g. for desktop applications), you can specify them here as a comma-separated list.
+2 -2
View File
@@ -78,8 +78,8 @@ func BuiltinApplications() map[string]*BuiltinOAuth2Application {
RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"},
}
m["d57cb8c4-630c-4168-8324-ec79935e18d4"] = &BuiltinOAuth2Application{
ConfigName: "tea",
DisplayName: "tea",
ConfigName: "gitea-cli",
DisplayName: "gitea-cli",
RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"},
}
m["b757811a-05c8-4c76-8d74-a5ee3d2073f2"] = &BuiltinOAuth2Application{
+5 -5
View File
@@ -605,7 +605,7 @@ type CloneLink struct {
SSH string
HTTPS string
Tea string
CLI string
}
// ComposeHTTPSCloneURL returns HTTPS clone URL based on the given owner and repository name.
@@ -656,9 +656,9 @@ func composeSSHCloneURL(doer *user_model.User, ownerName, repoName string, useUR
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
}
// ComposeTeaCloneCommand returns Tea CLI clone command based on the given owner and repository name.
func ComposeTeaCloneCommand(ctx context.Context, owner, repo string) string {
return fmt.Sprintf("tea clone %s/%s", url.PathEscape(owner), url.PathEscape(repo))
// ComposeCLICloneCommand returns the gt (gitea-cli) clone command based on the given owner and repository name.
func ComposeCLICloneCommand(ctx context.Context, owner, repo string) string {
return fmt.Sprintf("gt repo clone %s/%s", url.PathEscape(owner), url.PathEscape(repo))
}
func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, repoPathName string) *CloneLink {
@@ -668,7 +668,7 @@ func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, re
SupportSSH: !setting.SSH.Disabled && (doer != nil || setting.SSH.ExposeAnonymous),
SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName),
HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName),
Tea: ComposeTeaCloneCommand(ctx, repo.OwnerName, repoPathName),
CLI: ComposeCLICloneCommand(ctx, repo.OwnerName, repoPathName),
}
}
+6 -1
View File
@@ -108,7 +108,7 @@ var OAuth2 = struct {
JWTSigningAlgorithm: "RS256",
JWTSigningPrivateKeyFile: "jwt/private.pem",
MaxTokenLength: math.MaxInt16,
DefaultApplications: []string{"git-credential-oauth", "git-credential-manager", "tea", "gitea-app", "gitea-vscode"},
DefaultApplications: []string{"git-credential-oauth", "git-credential-manager", "gitea-cli", "gitea-app", "gitea-vscode"},
}
func loadOAuth2From(rootCfg ConfigProvider) {
@@ -121,6 +121,11 @@ func loadOAuth2From(rootCfg ConfigProvider) {
if sec.HasKey("DEFAULT_APPLICATIONS") && sec.Key("DEFAULT_APPLICATIONS").String() == "" {
OAuth2.DefaultApplications = nil
}
for i, name := range OAuth2.DefaultApplications {
if name == "tea" { // tea was renamed to gitea-cli, which kept its OAuth2 client ID
OAuth2.DefaultApplications[i] = "gitea-cli"
}
}
if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) {
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)
+2 -2
View File
@@ -64,13 +64,13 @@ func TestOauth2DefaultApplications(t *testing.T) {
cfg, _ := NewConfigProviderFromData(``)
loadOAuth2From(cfg)
assert.Equal(t, []string{"git-credential-oauth", "git-credential-manager", "tea", "gitea-app", "gitea-vscode"}, OAuth2.DefaultApplications)
assert.Equal(t, []string{"git-credential-oauth", "git-credential-manager", "gitea-cli", "gitea-app", "gitea-vscode"}, OAuth2.DefaultApplications)
cfg, _ = NewConfigProviderFromData(`[oauth2]
DEFAULT_APPLICATIONS = tea
`)
loadOAuth2From(cfg)
assert.Equal(t, []string{"tea"}, OAuth2.DefaultApplications)
assert.Equal(t, []string{"gitea-cli"}, OAuth2.DefaultApplications)
cfg, _ = NewConfigProviderFromData(`[oauth2]
DEFAULT_APPLICATIONS =
+2 -2
View File
@@ -13,7 +13,7 @@
{{if $showCloneLinks}}
<div class="flex-text-block clone-panel-field">{{svg "octicon-terminal"}} Clone</div>
<div class="clone-panel-tab">
<!-- tea clone also uses the git HTTPS/SSH transports, so the whole clone section is hidden when both are disabled -->
<!-- gt repo clone also uses the git HTTPS/SSH transports, so the whole clone section is hidden when both are disabled -->
{{if $cloneLink.SupportHTTPS}}
<button type="button" class="item repo-clone-https" data-link="{{$cloneLink.HTTPS}}">HTTPS</button>
{{end}}
@@ -21,7 +21,7 @@
<button type="button" class="item repo-clone-ssh" data-link="{{$cloneLink.SSH}}">SSH</button>
{{end}}
{{if not $cloneLink.IsWikiRepo}}
<button type="button" class="item repo-clone-tea" data-link="{{$cloneLink.Tea}}">Tea CLI</button>
<button type="button" class="item repo-clone-cli" data-link="{{$cloneLink.CLI}}">Gitea CLI</button>
{{end}}
</div>
<div class="divider"></div>
+4 -4
View File
@@ -186,9 +186,9 @@ func testViewRepo1CloneLinkAnonymous(t *testing.T) {
_, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
assert.False(t, exists)
link, exists = htmlDoc.doc.Find(".repo-clone-tea").Attr("data-link")
link, exists = htmlDoc.doc.Find(".repo-clone-cli").Attr("data-link")
assert.True(t, exists, "The template has changed")
assert.Equal(t, "tea clone user2/repo1", link)
assert.Equal(t, "gt repo clone user2/repo1", link)
}
func testViewRepo1CloneLinkAuthorized(t *testing.T) {
@@ -209,9 +209,9 @@ func testViewRepo1CloneLinkAuthorized(t *testing.T) {
sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port)
assert.Equal(t, sshURL, link)
link, exists = htmlDoc.doc.Find(".repo-clone-tea").Attr("data-link")
link, exists = htmlDoc.doc.Find(".repo-clone-cli").Attr("data-link")
assert.True(t, exists, "The template has changed")
assert.Equal(t, "tea clone user2/repo1", link)
assert.Equal(t, "gt repo clone user2/repo1", link)
}
func testViewRepoWithSymlinks(t *testing.T) {
+10 -10
View File
@@ -59,24 +59,24 @@ function initRepoCloneButtonsCombo(parent: Element) {
const tabHttps = parent.querySelector('.repo-clone-https');
const tabSsh = parent.querySelector('.repo-clone-ssh');
const tabTea = parent.querySelector('.repo-clone-tea');
const tabCli = parent.querySelector('.repo-clone-cli');
const listOpenWithEditorApps = parent.querySelector('.repo-clone-with-apps');
// not every tab exists in every panel, eg: the admin may disable HTTP/SSH, and the empty repo page has no Tea CLI tab
const tabByScheme: Record<string, Element | null> = {https: tabHttps, ssh: tabSsh, tea: tabTea};
// not every tab exists in every panel, eg: the admin may disable HTTP/SSH, and the empty repo page has no Gitea CLI tab
const tabByScheme: Record<string, Element | null> = {https: tabHttps, ssh: tabSsh, cli: tabCli};
const updateClonePanelUi = function() {
let scheme = localUserSettings.getString('repo-clone-protocol');
// fall back to the first available tab when the preferred scheme's tab is absent (unset preference, or disabled protocol)
if (!tabByScheme[scheme]) {
scheme = ['https', 'ssh', 'tea'].find((s) => tabByScheme[s]) ?? '';
scheme = ['https', 'ssh', 'cli'].find((s) => tabByScheme[s]) ?? '';
}
const isHttps = scheme === 'https';
const isSsh = scheme === 'ssh';
const isTea = scheme === 'tea';
const isCli = scheme === 'cli';
if (listOpenWithEditorApps) {
toggleElem(listOpenWithEditorApps, !isTea); // don't show the "Open with editor apps" list when "Tea" clone is selected
toggleElem(listOpenWithEditorApps, !isCli); // don't show the "Open with editor apps" list when "Gitea CLI" clone is selected
}
if (tabHttps) {
@@ -87,8 +87,8 @@ function initRepoCloneButtonsCombo(parent: Element) {
if (tabSsh) {
tabSsh.classList.toggle('active', isSsh);
}
if (tabTea) {
tabTea.classList.toggle('active', isTea);
if (tabCli) {
tabCli.classList.toggle('active', isCli);
}
const tab = tabByScheme[scheme];
@@ -118,8 +118,8 @@ function initRepoCloneButtonsCombo(parent: Element) {
localUserSettings.setString('repo-clone-protocol', 'ssh');
updateClonePanelUi();
});
tabTea?.addEventListener('click', () => {
localUserSettings.setString('repo-clone-protocol', 'tea');
tabCli?.addEventListener('click', () => {
localUserSettings.setString('repo-clone-protocol', 'cli');
updateClonePanelUi();
});
elCloneUrlInput.addEventListener('focus', () => {