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
2026-11-01 12:47:10 +00:00
committed by GitHub
parent c62e07f18a
commit d3021b69ee
11 changed files with 36 additions and 31 deletions

View File

@@ -68,7 +68,7 @@ Get more information from [the translation section of our contributing guide](CO
## Official and Third-Party Projects ## 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. 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.

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、插件、主题等。 我们在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 维护了一个 Gitea 相关项目的列表,您可以在那里发现更多的第三方项目,包括 SDK、插件、主题等。

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、插件、主題等。 我們在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 維護了一個 Gitea 相關項目的列表,您可以在那裡發現更多的第三方項目,包括 SDK、插件、主題等。

View File

@@ -637,10 +637,10 @@ ENABLED = true
;; Pre-register OAuth2 applications for some universally useful services ;; Pre-register OAuth2 applications for some universally useful services
;; * https://github.com/hickford/git-credential-oauth ;; * https://github.com/hickford/git-credential-oauth
;; * https://github.com/git-ecosystem/git-credential-manager ;; * 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) ;; * Gitea App (the official Gitea mobile app)
;; * https://gitea.com/gitea/gitea-vscode ;; * 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. ;; 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. ;; If you need to use other schemes (e.g. for desktop applications), you can specify them here as a comma-separated list.

View File

@@ -78,8 +78,8 @@ func BuiltinApplications() map[string]*BuiltinOAuth2Application {
RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"},
} }
m["d57cb8c4-630c-4168-8324-ec79935e18d4"] = &BuiltinOAuth2Application{ m["d57cb8c4-630c-4168-8324-ec79935e18d4"] = &BuiltinOAuth2Application{
ConfigName: "tea", ConfigName: "gitea-cli",
DisplayName: "tea", DisplayName: "gitea-cli",
RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"},
} }
m["b757811a-05c8-4c76-8d74-a5ee3d2073f2"] = &BuiltinOAuth2Application{ m["b757811a-05c8-4c76-8d74-a5ee3d2073f2"] = &BuiltinOAuth2Application{

View File

@@ -605,7 +605,7 @@ type CloneLink struct {
SSH string SSH string
HTTPS string HTTPS string
Tea string CLI string
} }
// ComposeHTTPSCloneURL returns HTTPS clone URL based on the given owner and repository name. // 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)) 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. // ComposeCLICloneCommand returns the gt (gitea-cli) clone command based on the given owner and repository name.
func ComposeTeaCloneCommand(ctx context.Context, owner, repo string) string { func ComposeCLICloneCommand(ctx context.Context, owner, repo string) string {
return fmt.Sprintf("tea clone %s/%s", url.PathEscape(owner), url.PathEscape(repo)) 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 { 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), SupportSSH: !setting.SSH.Disabled && (doer != nil || setting.SSH.ExposeAnonymous),
SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName), SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName),
HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName), HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName),
Tea: ComposeTeaCloneCommand(ctx, repo.OwnerName, repoPathName), CLI: ComposeCLICloneCommand(ctx, repo.OwnerName, repoPathName),
} }
} }

View File

@@ -108,7 +108,7 @@ var OAuth2 = struct {
JWTSigningAlgorithm: "RS256", JWTSigningAlgorithm: "RS256",
JWTSigningPrivateKeyFile: "jwt/private.pem", JWTSigningPrivateKeyFile: "jwt/private.pem",
MaxTokenLength: math.MaxInt16, 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) { func loadOAuth2From(rootCfg ConfigProvider) {
@@ -121,6 +121,11 @@ func loadOAuth2From(rootCfg ConfigProvider) {
if sec.HasKey("DEFAULT_APPLICATIONS") && sec.Key("DEFAULT_APPLICATIONS").String() == "" { if sec.HasKey("DEFAULT_APPLICATIONS") && sec.Key("DEFAULT_APPLICATIONS").String() == "" {
OAuth2.DefaultApplications = nil 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) { if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) {
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile) OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)

View File

@@ -64,13 +64,13 @@ func TestOauth2DefaultApplications(t *testing.T) {
cfg, _ := NewConfigProviderFromData(``) cfg, _ := NewConfigProviderFromData(``)
loadOAuth2From(cfg) 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] cfg, _ = NewConfigProviderFromData(`[oauth2]
DEFAULT_APPLICATIONS = tea DEFAULT_APPLICATIONS = tea
`) `)
loadOAuth2From(cfg) loadOAuth2From(cfg)
assert.Equal(t, []string{"tea"}, OAuth2.DefaultApplications) assert.Equal(t, []string{"gitea-cli"}, OAuth2.DefaultApplications)
cfg, _ = NewConfigProviderFromData(`[oauth2] cfg, _ = NewConfigProviderFromData(`[oauth2]
DEFAULT_APPLICATIONS = DEFAULT_APPLICATIONS =

View File

@@ -13,7 +13,7 @@
{{if $showCloneLinks}} {{if $showCloneLinks}}
<div class="flex-text-block clone-panel-field">{{svg "octicon-terminal"}} Clone</div> <div class="flex-text-block clone-panel-field">{{svg "octicon-terminal"}} Clone</div>
<div class="clone-panel-tab"> <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}} {{if $cloneLink.SupportHTTPS}}
<button type="button" class="item repo-clone-https" data-link="{{$cloneLink.HTTPS}}">HTTPS</button> <button type="button" class="item repo-clone-https" data-link="{{$cloneLink.HTTPS}}">HTTPS</button>
{{end}} {{end}}
@@ -21,7 +21,7 @@
<button type="button" class="item repo-clone-ssh" data-link="{{$cloneLink.SSH}}">SSH</button> <button type="button" class="item repo-clone-ssh" data-link="{{$cloneLink.SSH}}">SSH</button>
{{end}} {{end}}
{{if not $cloneLink.IsWikiRepo}} {{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}} {{end}}
</div> </div>
<div class="divider"></div> <div class="divider"></div>

View File

@@ -186,9 +186,9 @@ func testViewRepo1CloneLinkAnonymous(t *testing.T) {
_, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link") _, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
assert.False(t, exists) 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.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) { 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) sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port)
assert.Equal(t, sshURL, link) 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.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) { func testViewRepoWithSymlinks(t *testing.T) {

View File

@@ -59,24 +59,24 @@ function initRepoCloneButtonsCombo(parent: Element) {
const tabHttps = parent.querySelector('.repo-clone-https'); const tabHttps = parent.querySelector('.repo-clone-https');
const tabSsh = parent.querySelector('.repo-clone-ssh'); 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'); 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 // 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, tea: tabTea}; const tabByScheme: Record<string, Element | null> = {https: tabHttps, ssh: tabSsh, cli: tabCli};
const updateClonePanelUi = function() { const updateClonePanelUi = function() {
let scheme = localUserSettings.getString('repo-clone-protocol'); 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) // fall back to the first available tab when the preferred scheme's tab is absent (unset preference, or disabled protocol)
if (!tabByScheme[scheme]) { if (!tabByScheme[scheme]) {
scheme = ['https', 'ssh', 'tea'].find((s) => tabByScheme[s]) ?? ''; scheme = ['https', 'ssh', 'cli'].find((s) => tabByScheme[s]) ?? '';
} }
const isHttps = scheme === 'https'; const isHttps = scheme === 'https';
const isSsh = scheme === 'ssh'; const isSsh = scheme === 'ssh';
const isTea = scheme === 'tea'; const isCli = scheme === 'cli';
if (listOpenWithEditorApps) { 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) { if (tabHttps) {
@@ -87,8 +87,8 @@ function initRepoCloneButtonsCombo(parent: Element) {
if (tabSsh) { if (tabSsh) {
tabSsh.classList.toggle('active', isSsh); tabSsh.classList.toggle('active', isSsh);
} }
if (tabTea) { if (tabCli) {
tabTea.classList.toggle('active', isTea); tabCli.classList.toggle('active', isCli);
} }
const tab = tabByScheme[scheme]; const tab = tabByScheme[scheme];
@@ -118,8 +118,8 @@ function initRepoCloneButtonsCombo(parent: Element) {
localUserSettings.setString('repo-clone-protocol', 'ssh'); localUserSettings.setString('repo-clone-protocol', 'ssh');
updateClonePanelUi(); updateClonePanelUi();
}); });
tabTea?.addEventListener('click', () => { tabCli?.addEventListener('click', () => {
localUserSettings.setString('repo-clone-protocol', 'tea'); localUserSettings.setString('repo-clone-protocol', 'cli');
updateClonePanelUi(); updateClonePanelUi();
}); });
elCloneUrlInput.addEventListener('focus', () => { elCloneUrlInput.addEventListener('focus', () => {