Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clone repository with Tea CLI #33725

Merged
merged 11 commits into from
Feb 27, 2025
18 changes: 13 additions & 5 deletions models/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,13 +646,15 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
type CloneLink struct {
SSH string
HTTPS string
Tea string
}

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

// ComposeSSHCloneURL returns SSH clone URL based on the given owner and repository name.
func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) string {
sshUser := setting.SSH.User
sshDomain := setting.SSH.Domain
Expand Down Expand Up @@ -686,11 +688,17 @@ func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) strin
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))
}

func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, repoPathName string) *CloneLink {
cl := new(CloneLink)
cl.SSH = ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName)
cl.HTTPS = ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName)
return cl
return &CloneLink{
SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName),
HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName),
Tea: ComposeTeaCloneCommand(ctx, repo.OwnerName, repoPathName),
}
}

// CloneLink returns clone URLs of repository.
Expand Down
1 change: 1 addition & 0 deletions templates/repo/clone_panel.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
{{if $.CloneButtonShowSSH}}
<button class="item repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
{{end}}
<button class="item repo-clone-tea" data-link="{{$.CloneButtonOriginLink.Tea}}">Tea CLI</button>
</div>
<div class="divider"></div>

Expand Down
10 changes: 10 additions & 0 deletions tests/integration/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,13 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) {
link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
assert.True(t, exists, "The template has changed")
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)

_, 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")
assert.True(t, exists, "The template has changed")
assert.Equal(t, "tea clone user2/repo1", link)
}

func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
Expand All @@ -146,10 +151,15 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
assert.True(t, exists, "The template has changed")
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)

link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
assert.True(t, exists, "The template has changed")
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")
assert.True(t, exists, "The template has changed")
assert.Equal(t, "tea clone user2/repo1", link)
}

func TestViewRepoWithSymlinks(t *testing.T) {
Expand Down
49 changes: 41 additions & 8 deletions web_src/js/features/repo-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,49 @@ export function substituteRepoOpenWithUrl(tmpl: string, url: string): string {
function initCloneSchemeUrlSelection(parent: Element) {
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url');

const tabSsh = parent.querySelector('.repo-clone-ssh');
const tabHttps = parent.querySelector('.repo-clone-https');
const tabSsh = parent.querySelector('.repo-clone-ssh');
const tabTea = parent.querySelector('.repo-clone-tea');
const updateClonePanelUi = function() {
const scheme = localStorage.getItem('repo-clone-protocol') || 'https';
const isSSH = scheme === 'ssh' && Boolean(tabSsh) || scheme !== 'ssh' && !tabHttps;
let scheme = localStorage.getItem('repo-clone-protocol');
if (!['https', 'ssh', 'tea'].includes(scheme)) {
scheme = 'https';
}

// Fallbacks if the scheme preference is not available in the tabs, for example: empty repo page, there are only HTTPS and SSH
if (scheme === 'tea' && !tabTea) {
scheme = 'https';
}
if (scheme === 'https' && !tabHttps) {
scheme = 'ssh';
} else if (scheme === 'ssh' && !tabSsh) {
scheme = 'https';
}

const isHttps = scheme === 'https';
const isSsh = scheme === 'ssh';
const isTea = scheme === 'tea';

if (tabHttps) {
tabHttps.textContent = window.origin.split(':')[0].toUpperCase(); // show "HTTP" or "HTTPS"
tabHttps.classList.toggle('active', !isSSH);
tabHttps.classList.toggle('active', isHttps);
}
if (tabSsh) {
tabSsh.classList.toggle('active', isSSH);
tabSsh.classList.toggle('active', isSsh);
}
if (tabTea) {
tabTea.classList.toggle('active', isTea);
}

let tab: Element;
if (isHttps) {
tab = tabHttps;
} else if (isSsh) {
tab = tabSsh;
} else if (isTea) {
tab = tabTea;
}

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

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

updateClonePanelUi();
// tabSsh or tabHttps might not both exist, eg: guest view, or one is disabled by the server
tabHttps?.addEventListener('click', () => {
localStorage.setItem('repo-clone-protocol', 'https');
updateClonePanelUi();
});
tabSsh?.addEventListener('click', () => {
localStorage.setItem('repo-clone-protocol', 'ssh');
updateClonePanelUi();
});
tabHttps?.addEventListener('click', () => {
localStorage.setItem('repo-clone-protocol', 'https');
tabTea?.addEventListener('click', () => {
localStorage.setItem('repo-clone-protocol', 'tea');
updateClonePanelUi();
});
elCloneUrlInput.addEventListener('focus', () => {
Expand Down
Loading