Skip to content

Commit f52e31f

Browse files
Clone repository with Tea CLI (#33725)
This PR adds "Tea CLI" as a clone method. <img width="350" alt="Capture d’écran 2025-02-25 à 23 38 47" src="https://github.com/user-attachments/assets/8e86e54a-998b-45d1-9f20-167b449e79b6" /> --------- Signed-off-by: Quentin Guidée <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent b7aac5e commit f52e31f

File tree

4 files changed

+65
-13
lines changed

4 files changed

+65
-13
lines changed

models/repo/repo.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -646,13 +646,15 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
646646
type CloneLink struct {
647647
SSH string
648648
HTTPS string
649+
Tea string
649650
}
650651

651-
// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name.
652+
// ComposeHTTPSCloneURL returns HTTPS clone URL based on the given owner and repository name.
652653
func ComposeHTTPSCloneURL(ctx context.Context, owner, repo string) string {
653654
return fmt.Sprintf("%s%s/%s.git", httplib.GuessCurrentAppURL(ctx), url.PathEscape(owner), url.PathEscape(repo))
654655
}
655656

657+
// ComposeSSHCloneURL returns SSH clone URL based on the given owner and repository name.
656658
func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) string {
657659
sshUser := setting.SSH.User
658660
sshDomain := setting.SSH.Domain
@@ -686,11 +688,17 @@ func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) strin
686688
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
687689
}
688690

691+
// ComposeTeaCloneCommand returns Tea CLI clone command based on the given owner and repository name.
692+
func ComposeTeaCloneCommand(ctx context.Context, owner, repo string) string {
693+
return fmt.Sprintf("tea clone %s/%s", url.PathEscape(owner), url.PathEscape(repo))
694+
}
695+
689696
func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, repoPathName string) *CloneLink {
690-
cl := new(CloneLink)
691-
cl.SSH = ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName)
692-
cl.HTTPS = ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName)
693-
return cl
697+
return &CloneLink{
698+
SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName),
699+
HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName),
700+
Tea: ComposeTeaCloneCommand(ctx, repo.OwnerName, repoPathName),
701+
}
694702
}
695703

696704
// CloneLink returns clone URLs of repository.

templates/repo/clone_panel.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
{{if $.CloneButtonShowSSH}}
1515
<button class="item repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
1616
{{end}}
17+
<button class="item repo-clone-tea" data-link="{{$.CloneButtonOriginLink.Tea}}">Tea CLI</button>
1718
</div>
1819
<div class="divider"></div>
1920

tests/integration/repo_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,13 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) {
130130
link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
131131
assert.True(t, exists, "The template has changed")
132132
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
133+
133134
_, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
134135
assert.False(t, exists)
136+
137+
link, exists = htmlDoc.doc.Find(".repo-clone-tea").Attr("data-link")
138+
assert.True(t, exists, "The template has changed")
139+
assert.Equal(t, "tea clone user2/repo1", link)
135140
}
136141

137142
func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
@@ -146,10 +151,15 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
146151
link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
147152
assert.True(t, exists, "The template has changed")
148153
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
154+
149155
link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
150156
assert.True(t, exists, "The template has changed")
151157
sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port)
152158
assert.Equal(t, sshURL, link)
159+
160+
link, exists = htmlDoc.doc.Find(".repo-clone-tea").Attr("data-link")
161+
assert.True(t, exists, "The template has changed")
162+
assert.Equal(t, "tea clone user2/repo1", link)
153163
}
154164

155165
func TestViewRepoWithSymlinks(t *testing.T) {

web_src/js/features/repo-common.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,49 @@ export function substituteRepoOpenWithUrl(tmpl: string, url: string): string {
5353
function initCloneSchemeUrlSelection(parent: Element) {
5454
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url');
5555

56-
const tabSsh = parent.querySelector('.repo-clone-ssh');
5756
const tabHttps = parent.querySelector('.repo-clone-https');
57+
const tabSsh = parent.querySelector('.repo-clone-ssh');
58+
const tabTea = parent.querySelector('.repo-clone-tea');
5859
const updateClonePanelUi = function() {
59-
const scheme = localStorage.getItem('repo-clone-protocol') || 'https';
60-
const isSSH = scheme === 'ssh' && Boolean(tabSsh) || scheme !== 'ssh' && !tabHttps;
60+
let scheme = localStorage.getItem('repo-clone-protocol');
61+
if (!['https', 'ssh', 'tea'].includes(scheme)) {
62+
scheme = 'https';
63+
}
64+
65+
// Fallbacks if the scheme preference is not available in the tabs, for example: empty repo page, there are only HTTPS and SSH
66+
if (scheme === 'tea' && !tabTea) {
67+
scheme = 'https';
68+
}
69+
if (scheme === 'https' && !tabHttps) {
70+
scheme = 'ssh';
71+
} else if (scheme === 'ssh' && !tabSsh) {
72+
scheme = 'https';
73+
}
74+
75+
const isHttps = scheme === 'https';
76+
const isSsh = scheme === 'ssh';
77+
const isTea = scheme === 'tea';
78+
6179
if (tabHttps) {
6280
tabHttps.textContent = window.origin.split(':')[0].toUpperCase(); // show "HTTP" or "HTTPS"
63-
tabHttps.classList.toggle('active', !isSSH);
81+
tabHttps.classList.toggle('active', isHttps);
6482
}
6583
if (tabSsh) {
66-
tabSsh.classList.toggle('active', isSSH);
84+
tabSsh.classList.toggle('active', isSsh);
85+
}
86+
if (tabTea) {
87+
tabTea.classList.toggle('active', isTea);
88+
}
89+
90+
let tab: Element;
91+
if (isHttps) {
92+
tab = tabHttps;
93+
} else if (isSsh) {
94+
tab = tabSsh;
95+
} else if (isTea) {
96+
tab = tabTea;
6797
}
6898

69-
const tab = isSSH ? tabSsh : tabHttps;
7099
if (!tab) return;
71100
const link = toOriginUrl(tab.getAttribute('data-link'));
72101

@@ -84,12 +113,16 @@ function initCloneSchemeUrlSelection(parent: Element) {
84113

85114
updateClonePanelUi();
86115
// tabSsh or tabHttps might not both exist, eg: guest view, or one is disabled by the server
116+
tabHttps?.addEventListener('click', () => {
117+
localStorage.setItem('repo-clone-protocol', 'https');
118+
updateClonePanelUi();
119+
});
87120
tabSsh?.addEventListener('click', () => {
88121
localStorage.setItem('repo-clone-protocol', 'ssh');
89122
updateClonePanelUi();
90123
});
91-
tabHttps?.addEventListener('click', () => {
92-
localStorage.setItem('repo-clone-protocol', 'https');
124+
tabTea?.addEventListener('click', () => {
125+
localStorage.setItem('repo-clone-protocol', 'tea');
93126
updateClonePanelUi();
94127
});
95128
elCloneUrlInput.addEventListener('focus', () => {

0 commit comments

Comments
 (0)