mirror of
https://github.com/go-gitea/gitea.git
synced 2025-03-04 04:52:11 +01:00
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 <quentin.guidee@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
b7aac5ef9a
commit
f52e31f5ce
@ -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
|
||||
@ -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.
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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) {
|
||||
@ -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) {
|
||||
|
@ -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;
|
||||
if (tabHttps) {
|
||||
tabHttps.textContent = window.origin.split(':')[0].toUpperCase(); // show "HTTP" or "HTTPS"
|
||||
tabHttps.classList.toggle('active', !isSSH);
|
||||
}
|
||||
if (tabSsh) {
|
||||
tabSsh.classList.toggle('active', isSSH);
|
||||
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', isHttps);
|
||||
}
|
||||
if (tabSsh) {
|
||||
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'));
|
||||
|
||||
@ -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', () => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user