mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00: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:
		| @@ -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', () => { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Quentin
					Quentin