mirror of
https://github.com/go-gitea/gitea
synced 2025-03-10 04:34:26 +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:
parent
b7aac5ef9a
commit
f52e31f5ce
@ -646,13 +646,15 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
|
|||||||
type CloneLink struct {
|
type CloneLink struct {
|
||||||
SSH string
|
SSH string
|
||||||
HTTPS 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 {
|
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))
|
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 {
|
func ComposeSSHCloneURL(doer *user_model.User, ownerName, repoName string) string {
|
||||||
sshUser := setting.SSH.User
|
sshUser := setting.SSH.User
|
||||||
sshDomain := setting.SSH.Domain
|
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))
|
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 {
|
func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, repoPathName string) *CloneLink {
|
||||||
cl := new(CloneLink)
|
return &CloneLink{
|
||||||
cl.SSH = ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName)
|
SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName),
|
||||||
cl.HTTPS = ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName)
|
HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName),
|
||||||
return cl
|
Tea: ComposeTeaCloneCommand(ctx, repo.OwnerName, repoPathName),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloneLink returns clone URLs of repository.
|
// CloneLink returns clone URLs of repository.
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
{{if $.CloneButtonShowSSH}}
|
{{if $.CloneButtonShowSSH}}
|
||||||
<button class="item repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
|
<button class="item repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
<button class="item repo-clone-tea" data-link="{{$.CloneButtonOriginLink.Tea}}">Tea CLI</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></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")
|
link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
|
||||||
assert.True(t, exists, "The template has changed")
|
assert.True(t, exists, "The template has changed")
|
||||||
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
|
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
|
||||||
|
|
||||||
_, 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")
|
||||||
|
assert.True(t, exists, "The template has changed")
|
||||||
|
assert.Equal(t, "tea clone user2/repo1", link)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
|
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")
|
link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
|
||||||
assert.True(t, exists, "The template has changed")
|
assert.True(t, exists, "The template has changed")
|
||||||
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
|
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
|
||||||
|
|
||||||
link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
|
link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
|
||||||
assert.True(t, exists, "The template has changed")
|
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)
|
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")
|
||||||
|
assert.True(t, exists, "The template has changed")
|
||||||
|
assert.Equal(t, "tea clone user2/repo1", link)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewRepoWithSymlinks(t *testing.T) {
|
func TestViewRepoWithSymlinks(t *testing.T) {
|
||||||
|
@ -53,20 +53,49 @@ export function substituteRepoOpenWithUrl(tmpl: string, url: string): string {
|
|||||||
function initCloneSchemeUrlSelection(parent: Element) {
|
function initCloneSchemeUrlSelection(parent: Element) {
|
||||||
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url');
|
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url');
|
||||||
|
|
||||||
const tabSsh = parent.querySelector('.repo-clone-ssh');
|
|
||||||
const tabHttps = parent.querySelector('.repo-clone-https');
|
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 updateClonePanelUi = function() {
|
||||||
const scheme = localStorage.getItem('repo-clone-protocol') || 'https';
|
let scheme = localStorage.getItem('repo-clone-protocol');
|
||||||
const isSSH = scheme === 'ssh' && Boolean(tabSsh) || scheme !== 'ssh' && !tabHttps;
|
if (!['https', 'ssh', 'tea'].includes(scheme)) {
|
||||||
if (tabHttps) {
|
scheme = 'https';
|
||||||
tabHttps.textContent = window.origin.split(':')[0].toUpperCase(); // show "HTTP" or "HTTPS"
|
}
|
||||||
tabHttps.classList.toggle('active', !isSSH);
|
|
||||||
}
|
// Fallbacks if the scheme preference is not available in the tabs, for example: empty repo page, there are only HTTPS and SSH
|
||||||
if (tabSsh) {
|
if (scheme === 'tea' && !tabTea) {
|
||||||
tabSsh.classList.toggle('active', isSSH);
|
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;
|
if (!tab) return;
|
||||||
const link = toOriginUrl(tab.getAttribute('data-link'));
|
const link = toOriginUrl(tab.getAttribute('data-link'));
|
||||||
|
|
||||||
@ -84,12 +113,16 @@ function initCloneSchemeUrlSelection(parent: Element) {
|
|||||||
|
|
||||||
updateClonePanelUi();
|
updateClonePanelUi();
|
||||||
// tabSsh or tabHttps might not both exist, eg: guest view, or one is disabled by the server
|
// 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', () => {
|
tabSsh?.addEventListener('click', () => {
|
||||||
localStorage.setItem('repo-clone-protocol', 'ssh');
|
localStorage.setItem('repo-clone-protocol', 'ssh');
|
||||||
updateClonePanelUi();
|
updateClonePanelUi();
|
||||||
});
|
});
|
||||||
tabHttps?.addEventListener('click', () => {
|
tabTea?.addEventListener('click', () => {
|
||||||
localStorage.setItem('repo-clone-protocol', 'https');
|
localStorage.setItem('repo-clone-protocol', 'tea');
|
||||||
updateClonePanelUi();
|
updateClonePanelUi();
|
||||||
});
|
});
|
||||||
elCloneUrlInput.addEventListener('focus', () => {
|
elCloneUrlInput.addEventListener('focus', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user