mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-08 14:03:24 +09:00
Compare commits
2
Commits
bca44bd736
...
2c8e99bbf7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c8e99bbf7 | ||
|
|
8a3daef525 |
+10
-3
@@ -647,6 +647,10 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
|
||||
|
||||
// CloneLink represents different types of clone URLs of repository.
|
||||
type CloneLink struct {
|
||||
IsWikiRepo bool
|
||||
SupportSSH bool
|
||||
SupportHTTPS bool
|
||||
|
||||
SSH string
|
||||
HTTPS string
|
||||
Tea string
|
||||
@@ -698,9 +702,12 @@ func ComposeTeaCloneCommand(ctx context.Context, owner, repo string) string {
|
||||
|
||||
func (repo *Repository) cloneLink(ctx context.Context, doer *user_model.User, repoPathName string) *CloneLink {
|
||||
return &CloneLink{
|
||||
SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName),
|
||||
HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName),
|
||||
Tea: ComposeTeaCloneCommand(ctx, repo.OwnerName, repoPathName),
|
||||
IsWikiRepo: strings.HasSuffix(repoPathName, ".wiki"),
|
||||
SupportHTTPS: !setting.Repository.DisableHTTPGit,
|
||||
SupportSSH: !setting.SSH.Disabled && (doer != nil || setting.SSH.ExposeAnonymous),
|
||||
SSH: ComposeSSHCloneURL(doer, repo.OwnerName, repoPathName),
|
||||
HTTPS: ComposeHTTPSCloneURL(ctx, repo.OwnerName, repoPathName),
|
||||
Tea: ComposeTeaCloneCommand(ctx, repo.OwnerName, repoPathName),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,8 +77,12 @@ func (c *CommitMessage) MessageTrailer() CommitMessageTrailerValues {
|
||||
}
|
||||
|
||||
var commitMessageTrailerSplit = sync.OnceValue(func() *regexp.Regexp {
|
||||
// the sep is either something like "\n---\n" or "\n\n" in the body, or at the start of the body like "---\n"
|
||||
return regexp.MustCompile(`(?s)^(?P<content>.*?)(?P<sep>^|^\n|^-{3,}\n+|\n-{3,}\n+|\n\n)(?P<trailer>(?:[A-Za-z0-9][-A-Za-z0-9]*:[^\n]*\n?)*\n*)$`)
|
||||
// ref: https://git-scm.com/docs/git-interpret-trailers
|
||||
// TODO: the regexp is not able to perfectly parse the all kinds of trailers
|
||||
// It was just copied from legacy code, it is not exactly the same as how Git parses the trailer and not quite right in some cases.
|
||||
// For the key characters: it follows RFC 822 field name syntax (or RFC 2822/RFC 5322): printable ASCII characters between 33 and 126 except the colon (:),
|
||||
// but maybe we don't want to make it that complicated, so here we only support some common "symbol-like" characters.
|
||||
return regexp.MustCompile(`(?s)^(?P<content>.*?)(?P<sep>^|^\n|^-{3,}\n+|\n+-{3,}\n+|\n{2,})(?P<trailer>(?:[A-Za-z0-9][-\w]*:[^\n]*(\n\s+[^\n]*)*\n?)*\n*)$`)
|
||||
})
|
||||
|
||||
// CommitMessageSplitTrailer tries to split the message by the trailer separator
|
||||
@@ -93,6 +97,41 @@ func CommitMessageSplitTrailer(s string) (content, sep, trailer string) {
|
||||
return v[re.SubexpIndex("content")], v[re.SubexpIndex("sep")], v[re.SubexpIndex("trailer")]
|
||||
}
|
||||
|
||||
// CommitMessageMerge merges two commit messages with their trailers
|
||||
func CommitMessageMerge(m1, m2 string) string {
|
||||
c1, s1, t1 := CommitMessageSplitTrailer(m1)
|
||||
c2, s2, t2 := CommitMessageSplitTrailer(m2)
|
||||
c1, t1 = strings.TrimSpace(c1), strings.TrimSpace(t1)
|
||||
c2, t2 = strings.TrimSpace(c2), strings.TrimSpace(t2)
|
||||
out := strings.Builder{}
|
||||
if c1 != "" && c2 != "" {
|
||||
out.WriteString(c1)
|
||||
out.WriteString("\n\n")
|
||||
out.WriteString(c2)
|
||||
} else if c1 != "" {
|
||||
out.WriteString(c1)
|
||||
} else if c2 != "" {
|
||||
out.WriteString(c2)
|
||||
}
|
||||
if t1 != "" || t2 != "" {
|
||||
sep := util.Iif(t1 == "", s2, s1)
|
||||
sep = util.IfZero(sep, "\n\n")
|
||||
if c1 != "" || c2 != "" {
|
||||
out.WriteString(sep)
|
||||
}
|
||||
if t1 != "" {
|
||||
out.WriteString(t1)
|
||||
}
|
||||
if t1 != "" && t2 != "" {
|
||||
out.WriteString("\n")
|
||||
}
|
||||
if t2 != "" {
|
||||
out.WriteString(t2)
|
||||
}
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func CommitMessageParseTrailer(s string) CommitMessageTrailerValues {
|
||||
ret := CommitMessageTrailerValues{}
|
||||
for line := range strings.SplitSeq(util.NormalizeStringEOL(s), "\n") {
|
||||
|
||||
@@ -26,10 +26,12 @@ func TestCommitMessageTrailer(t *testing.T) {
|
||||
{"a", "a", "", ""},
|
||||
{"a\n\nk", "a\n\nk", "", ""},
|
||||
{"a\n\nk:v", "a", "\n\n", "k:v"},
|
||||
{"a\n\nk:v\n next-line", "a", "\n\n", "k:v\n next-line"},
|
||||
{"a\n\nk:v\n next-line\nother: v", "a", "\n\n", "k:v\n next-line\nother: v"},
|
||||
{"a\n\nk:v\n\n", "a", "\n\n", "k:v\n\n"},
|
||||
{"a\n--\nk:v", "a\n--\nk:v", "", ""},
|
||||
{"a\n---\nk:v", "a", "\n---\n", "k:v"},
|
||||
{"a\n\n---\n\nk:v", "a\n", "\n---\n\n", "k:v"},
|
||||
{"a\n---\nk:v", "a", "\n---\n", "k:v"}, // TODO: should we support such case? No empty line between "---" and the trailer
|
||||
{"a\n\n---\n\nk:v", "a", "\n\n---\n\n", "k:v"},
|
||||
|
||||
{"k: v", "", "", "k: v"},
|
||||
{"\nk:v", "", "\n", "k:v"},
|
||||
@@ -127,3 +129,31 @@ func TestCommitMessageParticipants(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCommitMessageMerge(t *testing.T) {
|
||||
cases := []struct {
|
||||
m1, m2 string
|
||||
out string
|
||||
}{
|
||||
{"", "", ""},
|
||||
{"msg1", "", "msg1"},
|
||||
{"", "msg2", "msg2"},
|
||||
{"msg1", "msg2", "msg1\n\nmsg2"},
|
||||
{"k1: a", "", "k1: a"},
|
||||
{"", "k2: b", "k2: b"},
|
||||
{"k1: a", "k2: b", "k1: a\nk2: b"},
|
||||
{"msg1", "k2: b", "msg1\n\nk2: b"},
|
||||
{"k1: a", "msg2", "msg2\n\nk1: a"},
|
||||
{"msg1\n\nk1: a", "msg2", "msg1\n\nmsg2\n\nk1: a"},
|
||||
{"msg1\n----\nk1: a", "msg2", "msg1\n\nmsg2\n----\nk1: a"},
|
||||
{"msg1\n\n----\n\nk1: a", "msg2", "msg1\n\nmsg2\n\n----\n\nk1: a"},
|
||||
{"msg1", "msg2\n----\nk2: b", "msg1\n\nmsg2\n----\nk2: b"},
|
||||
{"msg1", "msg2\n\nk2: b", "msg1\n\nmsg2\n\nk2: b"},
|
||||
{"msg1\n\nk1: a", "msg2\n\nk2: b", "msg1\n\nmsg2\n\nk1: a\nk2: b"},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
out := CommitMessageMerge(c.m1, c.m2)
|
||||
assert.Equal(t, c.out, out, "idx=%d, m1=%q m2=%q", i, c.m1, c.m2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
pull_model "gitea.dev/models/pull"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/svg"
|
||||
"gitea.dev/modules/templates"
|
||||
"gitea.dev/modules/util"
|
||||
@@ -62,20 +64,23 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxFormProps(ctx *context.Context
|
||||
hasPendingPullRequestMergeTip = ctx.Locale.Tr("repo.pulls.auto_merge_has_pending_schedule", pendingPullRequestMerge.Doer.Name, createdPRMergeStr)
|
||||
}
|
||||
|
||||
defaultMergeTitle, defaultMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, mergeStyle)
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
ctx.ServerError("GetDefaultMergeMessage", err)
|
||||
return
|
||||
}
|
||||
defaultSquashMergeTitle, defaultSquashMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, repo_model.MergeStyleSquash)
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
ctx.ServerError("GetDefaultSquashMergeMessage", err)
|
||||
return
|
||||
}
|
||||
|
||||
var defaultMergeTitle, defaultMergeBody string
|
||||
var defaultSquashMergeTitle, defaultSquashMergeBody string
|
||||
var defaultSquashMergeCommitMessages string
|
||||
if !prInfo.IsPullRequestBroken {
|
||||
defaultSquashMergeCommitMessages = pull_service.GetSquashMergeCommitMessages(ctx, pull)
|
||||
var err error
|
||||
defaultMergeTitle, defaultMergeBody, err = pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, mergeStyle)
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
log.Error("GetDefaultMergeMessage for style %s failed, error: %v", mergeStyle, err)
|
||||
}
|
||||
defaultSquashMergeTitle, defaultSquashMergeBody, err = pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, repo_model.MergeStyleSquash)
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
log.Error("GetDefaultMergeMessage for squash failed, error: %v", err)
|
||||
}
|
||||
defaultSquashMergeCommitMessages, err = pull_service.GetSquashMergeCommitMessages(ctx, pull)
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
log.Error("GetSquashMergeCommitMessages failed, error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
allOverridableChecksOk := !prInfo.MergeBoxData.hasOverridableBlockers
|
||||
@@ -106,7 +111,6 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxFormProps(ctx *context.Context
|
||||
|
||||
// if this pr can be merged now, then hide the auto merge
|
||||
generalHideAutoMerge := prInfo.MergeBoxData.canMergeNow && allOverridableChecksOk
|
||||
|
||||
var mergeStyles []any
|
||||
if pull.IsStatusMergeable() {
|
||||
mergeStyles = []any{
|
||||
@@ -138,7 +142,7 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxFormProps(ctx *context.Context
|
||||
"allowed": prConfig.AllowSquash,
|
||||
"textDoMerge": ctx.Locale.Tr("repo.pulls.squash_merge_pull_request"),
|
||||
"mergeTitleFieldText": defaultSquashMergeTitle,
|
||||
"mergeMessageFieldText": defaultSquashMergeCommitMessages + defaultSquashMergeBody,
|
||||
"mergeMessageFieldText": git.CommitMessageMerge(defaultSquashMergeCommitMessages, defaultSquashMergeBody),
|
||||
"hideAutoMerge": generalHideAutoMerge,
|
||||
},
|
||||
map[string]any{
|
||||
|
||||
@@ -67,7 +67,7 @@ func prepareHomeSidebarRepoTopics(ctx *context.Context) {
|
||||
ctx.Data["Topics"] = topics
|
||||
}
|
||||
|
||||
func prepareOpenWithEditorApps(ctx *context.Context) {
|
||||
func prepareClonePanel(ctx *context.Context) {
|
||||
var tmplApps []map[string]any
|
||||
apps := setting.Config().Repository.OpenWithEditorApps.Value(ctx)
|
||||
for _, app := range apps {
|
||||
@@ -93,6 +93,12 @@ func prepareOpenWithEditorApps(ctx *context.Context) {
|
||||
})
|
||||
}
|
||||
ctx.Data["OpenWithEditorApps"] = tmplApps
|
||||
|
||||
if !setting.Repository.DisableDownloadSourceArchives {
|
||||
// FIXME: here it only uses the shortname in the ref to build the link, it can't distinguish the branch/tag/commit with the same name
|
||||
// in the future, it's better to use something like "/archive/branch/the-name.zip", "/archive/tag/the-name.zip" */}}
|
||||
ctx.Data["DownloadArchiveLinkPrefix"] = ctx.Repo.RepoLink + "/archive/" + util.PathEscapeSegments(ctx.Repo.RefFullName.ShortName())
|
||||
}
|
||||
}
|
||||
|
||||
func prepareHomeSidebarCitationFile(entry *git.TreeEntry) func(ctx *context.Context) {
|
||||
@@ -439,7 +445,7 @@ func Home(ctx *context.Context) {
|
||||
isTreePathRoot := ctx.Repo.TreePath == ""
|
||||
|
||||
prepareFuncs := []func(*context.Context){
|
||||
prepareOpenWithEditorApps,
|
||||
prepareClonePanel,
|
||||
prepareHomeSidebarRepoTopics,
|
||||
checkOutdatedBranch,
|
||||
prepareToRenderDirOrFile(entry),
|
||||
|
||||
@@ -589,6 +589,7 @@ func repoAssignmentPrepareTemplateData(ctx *Context, data *repoAssignmentPrepare
|
||||
ctx.Repo.RepoLink = repo.Link()
|
||||
ctx.Data["RepoLink"] = ctx.Repo.RepoLink
|
||||
ctx.Data["FeedURL"] = ctx.Repo.RepoLink
|
||||
ctx.Data["CloneButtonOriginLink"] = repo.CloneLink(ctx, ctx.Doer) // CloneButtonOriginLink may be rewritten to the WikiCloneLink by the router middleware
|
||||
|
||||
unit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeExternalTracker)
|
||||
if err == nil {
|
||||
@@ -643,18 +644,6 @@ func repoAssignmentPrepareTemplateData(ctx *Context, data *repoAssignmentPrepare
|
||||
// If multiple forks are available or if the user can fork to another account, but there is already a fork: open selection dialog
|
||||
ctx.Data["ShowForkModal"] = len(userAndOrgForks) > 1 || (canSignedUserFork && len(userAndOrgForks) > 0)
|
||||
|
||||
ctx.Data["RepoCloneLink"] = repo.CloneLink(ctx, ctx.Doer)
|
||||
|
||||
cloneButtonShowHTTPS := !setting.Repository.DisableHTTPGit
|
||||
cloneButtonShowSSH := !setting.SSH.Disabled && (ctx.IsSigned || setting.SSH.ExposeAnonymous)
|
||||
if !cloneButtonShowHTTPS && !cloneButtonShowSSH {
|
||||
// We have to show at least one link, so we just show the HTTPS
|
||||
cloneButtonShowHTTPS = true
|
||||
}
|
||||
ctx.Data["CloneButtonShowHTTPS"] = cloneButtonShowHTTPS
|
||||
ctx.Data["CloneButtonShowSSH"] = cloneButtonShowSSH
|
||||
ctx.Data["CloneButtonOriginLink"] = ctx.Data["RepoCloneLink"] // it may be rewritten to the WikiCloneLink by the router middleware
|
||||
|
||||
ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||
if setting.Indexer.RepoIndexerEnabled {
|
||||
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
|
||||
@@ -778,16 +767,6 @@ func repoAssignmentPrepareRepoTransfer(ctx *Context, data *repoAssignmentPrepare
|
||||
}
|
||||
}
|
||||
|
||||
func repoAssignmentHandleGoGet(ctx *Context, data *repoAssignmentPrepareDataStruct) {
|
||||
repo := data.repo
|
||||
if ctx.FormString("go-get") == "1" {
|
||||
ctx.Data["GoGetImport"] = ComposeGoGetImport(ctx, repo.Owner.Name, repo.Name)
|
||||
fullURLPrefix := repo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName)
|
||||
ctx.Data["GoDocDirectory"] = fullURLPrefix + "{/dir}"
|
||||
ctx.Data["GoDocFile"] = fullURLPrefix + "{/dir}/{file}#L{line}"
|
||||
}
|
||||
}
|
||||
|
||||
// RepoAssignment returns a middleware to handle repository assignment
|
||||
func RepoAssignment(ctx *Context) {
|
||||
repoAssignmentPreCheck(ctx)
|
||||
@@ -804,7 +783,6 @@ func RepoAssignment(ctx *Context) {
|
||||
repoAssignmentPrepareRepoTransfer,
|
||||
repoAssignmentPrepareBranches,
|
||||
repoAssignmentPreparePullRequests,
|
||||
repoAssignmentHandleGoGet,
|
||||
}
|
||||
for _, f := range funcs {
|
||||
f(ctx, prepareData)
|
||||
|
||||
+8
-15
@@ -777,30 +777,25 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
|
||||
}
|
||||
|
||||
// GetSquashMergeCommitMessages returns the commit messages between head and merge base (if there is one)
|
||||
func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequest) string {
|
||||
func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequest) (_ string, err error) {
|
||||
if err := pr.LoadIssue(ctx); err != nil {
|
||||
log.Error("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
|
||||
return ""
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := pr.Issue.LoadPoster(ctx); err != nil {
|
||||
log.Error("Cannot load poster %d for pr id %d, index %d Error: %v", pr.Issue.PosterID, pr.ID, pr.Index, err)
|
||||
return ""
|
||||
return "", err
|
||||
}
|
||||
|
||||
if pr.HeadRepo == nil {
|
||||
var err error
|
||||
pr.HeadRepo, err = repo_model.GetRepositoryByID(ctx, pr.HeadRepoID)
|
||||
if err != nil {
|
||||
log.Error("GetRepositoryByIdCtx[%d]: %v", pr.HeadRepoID, err)
|
||||
return ""
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.HeadRepo)
|
||||
if err != nil {
|
||||
log.Error("Unable to open head repository: Error: %v", err)
|
||||
return ""
|
||||
return "", err
|
||||
}
|
||||
defer closer.Close()
|
||||
|
||||
@@ -810,8 +805,7 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
|
||||
} else {
|
||||
pr.HeadCommitID, err = gitRepo.GetRefCommitID(pr.GetGitHeadRefName())
|
||||
if err != nil {
|
||||
log.Error("Unable to get head commit: %s Error: %v", pr.GetGitHeadRefName(), err)
|
||||
return ""
|
||||
return "", err
|
||||
}
|
||||
headCommitRef = git.RefNameFromCommit(pr.HeadCommitID)
|
||||
}
|
||||
@@ -822,8 +816,7 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
|
||||
|
||||
limitedCommits, err := gitRepo.CommitsBetween(headCommitRef, mergeBaseRef, limit)
|
||||
if err != nil {
|
||||
log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err)
|
||||
return ""
|
||||
return "", err
|
||||
}
|
||||
|
||||
mergeMessage := strings.TrimSpace(pr.Issue.Content) // use PR's title and description as squash commit message
|
||||
@@ -831,7 +824,7 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ
|
||||
mergeMessage = formatSquashMergeCommitMessages(limitedCommits) // use PR's commit messages as squash commit message
|
||||
}
|
||||
coAuthors := collectSquashMergeCommitCoAuthors(ctx, gitRepo, pr, headCommitRef, mergeBaseRef, limit, limitedCommits)
|
||||
return buildSquashMergeCommitMessages(mergeMessage, coAuthors)
|
||||
return buildSquashMergeCommitMessages(mergeMessage, coAuthors), nil
|
||||
}
|
||||
|
||||
func buildSquashMergeCommitMessages(mergeMessage string, coAuthors []string) string {
|
||||
|
||||
@@ -84,7 +84,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir
|
||||
return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err)
|
||||
}
|
||||
|
||||
cloneLink := repo.CloneLink(ctx, nil /* no doer so do not generate user-related SSH link */)
|
||||
cloneLink := repo.CloneLinkGeneral(ctx)
|
||||
match := map[string]string{
|
||||
"Name": repo.Name,
|
||||
"Description": util.NormalizeStringEOL(repo.Description),
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
<meta name="description" content="{{if .Repository}}{{.Repository.Name}}{{if .Repository.Description}} - {{.Repository.Description}}{{end}}{{else}}{{MetaDescription}}{{end}}">
|
||||
<meta name="keywords" content="{{MetaKeywords}}">
|
||||
<meta name="referrer" content="same-origin">{{/* required by: 1. "redirect_to" cookie; 2. cross-origin protection */}}
|
||||
{{if .GoGetImport}}
|
||||
<meta name="go-import" content="{{.GoGetImport}} git {{.RepoCloneLink.HTTPS}}">
|
||||
<meta name="go-source" content="{{.GoGetImport}} _ {{.GoDocDirectory}} {{.GoDocFile}}">
|
||||
{{end}}
|
||||
{{if and .EnableFeed .FeedURL}}
|
||||
<link rel="alternate" type="application/atom+xml" title="" href="{{.FeedURL}}.atom">
|
||||
<link rel="alternate" type="application/rss+xml" title="" href="{{.FeedURL}}.rss">
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<!-- there is always at least one button (guaranteed by context/repo.go) -->
|
||||
<div class="ui action small input clone-buttons-combo">
|
||||
{{if $.CloneButtonShowHTTPS}}
|
||||
<button class="ui small button repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">HTTPS</button>
|
||||
<!-- render the clone combo only when a git protocol is available; the URL input would otherwise be empty -->
|
||||
{{$cloneLink := $.CloneButtonOriginLink}}
|
||||
{{if or $cloneLink.SupportHTTPS $cloneLink.SupportSSH}}
|
||||
<div class="ui action small input clone-buttons-combo" data-global-init="initRepoCloneButtonsCombo">
|
||||
{{if $cloneLink.SupportHTTPS}}
|
||||
<button class="ui small button repo-clone-https" data-link="{{$cloneLink.HTTPS}}">HTTPS</button>
|
||||
{{end}}
|
||||
{{if $.CloneButtonShowSSH}}
|
||||
<button class="ui small button repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
|
||||
{{if $cloneLink.SupportSSH}}
|
||||
<button class="ui small button repo-clone-ssh" data-link="{{$cloneLink.SSH}}">SSH</button>
|
||||
{{end}}
|
||||
<input size="10" class="repo-clone-url js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
|
||||
<input size="10" class="repo-clone-url js-clone-url" value="{{Iif $cloneLink.SupportHTTPS $cloneLink.HTTPS $cloneLink.SSH}}" readonly>
|
||||
<button class="ui small icon button" data-clipboard-target=".repo-clone-url" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">
|
||||
{{svg "octicon-copy" 14}}
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -1,48 +1,56 @@
|
||||
<button class="ui compact primary button js-btn-clone-panel">
|
||||
{{$cloneLink := $.CloneButtonOriginLink}}
|
||||
{{$downloadArchiveLinkPrefix := $.DownloadArchiveLinkPrefix}}
|
||||
{{$openWithEditorApps := $.OpenWithEditorApps}}
|
||||
{{$showCloneLinks := or $cloneLink.SupportHTTPS $cloneLink.SupportSSH}}
|
||||
{{$showOpenWithEditorApps := and $showCloneLinks $openWithEditorApps}}{{/* the editor apps need the clone link */}}
|
||||
{{if or $showCloneLinks $showOpenWithEditorApps $downloadArchiveLinkPrefix}}
|
||||
<button class="ui compact primary button" data-global-init="initRepoClonePanel">
|
||||
{{svg "octicon-code" 16}}
|
||||
<span>{{ctx.Locale.Tr "repo.code"}}</span>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
</button>
|
||||
<div class="clone-panel-popup tippy-target">
|
||||
<div class="flex-text-block clone-panel-field">{{svg "octicon-terminal"}} Clone</div>
|
||||
|
||||
<div class="clone-panel-tab">
|
||||
<!-- there is always at least one button (guaranteed by context/repo.go) -->
|
||||
{{if $.CloneButtonShowHTTPS}}
|
||||
<button class="item repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">HTTPS</button>
|
||||
{{end}}
|
||||
{{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>
|
||||
|
||||
<div class="clone-panel-field">
|
||||
<div class="ui input tiny action">
|
||||
<input size="30" class="repo-clone-url js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
|
||||
<div class="ui small compact icon button" data-clipboard-target=".js-clone-url" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">
|
||||
{{svg "octicon-copy" 14}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if not .PageIsWiki}}
|
||||
<div class="flex-items-block clone-panel-list">
|
||||
{{range .OpenWithEditorApps}}
|
||||
<a class="item muted js-clone-url-editor" data-href-template="{{.OpenURL}}">{{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}}</a>
|
||||
{{if $showCloneLinks}}
|
||||
<div class="flex-text-block clone-panel-field">{{svg "octicon-terminal"}} Clone</div>
|
||||
<div class="clone-panel-tab">
|
||||
<!-- tea clone also uses the git HTTPS/SSH transports, so the whole clone section is hidden when both are disabled -->
|
||||
{{if $cloneLink.SupportHTTPS}}
|
||||
<button class="item repo-clone-https" data-link="{{$cloneLink.HTTPS}}">HTTPS</button>
|
||||
{{end}}
|
||||
{{if $cloneLink.SupportSSH}}
|
||||
<button class="item repo-clone-ssh" data-link="{{$cloneLink.SSH}}">SSH</button>
|
||||
{{end}}
|
||||
{{if not $cloneLink.IsWikiRepo}}
|
||||
<button class="item repo-clone-tea" data-link="{{$cloneLink.Tea}}">Tea CLI</button>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if and (not $.DisableDownloadSourceArchives) $.RefFullName}}
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="clone-panel-field">
|
||||
<div class="ui input tiny action">
|
||||
<input size="30" class="repo-clone-url js-clone-url" value="{{Iif $cloneLink.SupportHTTPS $cloneLink.HTTPS $cloneLink.SSH}}" readonly>
|
||||
<div class="ui small compact icon button" data-clipboard-target=".js-clone-url" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">
|
||||
{{svg "octicon-copy" 14}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if $showOpenWithEditorApps}}
|
||||
<div class="flex-items-block clone-panel-list repo-clone-with-apps">
|
||||
{{range $app := $openWithEditorApps}}
|
||||
<a class="item muted js-clone-url-editor" data-href-template="{{$app.OpenURL}}">{{$app.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" $app.DisplayName}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if $downloadArchiveLinkPrefix}}
|
||||
{{if $showOpenWithEditorApps}}<div class="divider"></div>{{end}}
|
||||
<div class="flex-items-block clone-panel-list">
|
||||
{{/* FIXME: here it only uses the shortname in the ref to build the link, it can't distinguish the branch/tag/commit with the same name
|
||||
in the future, it's better to use something like "/archive/branch/the-name.zip", "/archive/tag/the-name.zip" */}}
|
||||
<a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefFullName.ShortName}}.zip" rel="nofollow">{{svg "octicon-file-zip"}} {{ctx.Locale.Tr "repo.download_zip"}}</a>
|
||||
<a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefFullName.ShortName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}} {{ctx.Locale.Tr "repo.download_tar"}}</a>
|
||||
<a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefFullName.ShortName}}.bundle" rel="nofollow">{{svg "octicon-package"}} {{ctx.Locale.Tr "repo.download_bundle"}}</a>
|
||||
<a class="item muted archive-link" href="{{$downloadArchiveLinkPrefix}}.zip" rel="nofollow">{{svg "octicon-file-zip"}} {{ctx.Locale.Tr "repo.download_zip"}}</a>
|
||||
<a class="item muted archive-link" href="{{$downloadArchiveLinkPrefix}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}} {{ctx.Locale.Tr "repo.download_tar"}}</a>
|
||||
<a class="item muted archive-link" href="{{$downloadArchiveLinkPrefix}}.bundle" rel="nofollow">{{svg "octicon-package"}} {{ctx.Locale.Tr "repo.download_bundle"}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="ui segment center">{{ctx.Locale.Tr "repo.no_branch"}}</div>
|
||||
{{else if .CanWriteCode}}
|
||||
<h4 class="ui top attached header">{{ctx.Locale.Tr "repo.quick_guide"}}</h4>
|
||||
<div class="ui attached guide table segment empty-repo-guide">
|
||||
<div class="ui attached segment empty-repo-guide flex-relaxed-list">
|
||||
<div class="item">
|
||||
<h3>{{ctx.Locale.Tr "repo.clone_this_repo"}} <small>{{ctx.Locale.Tr "repo.clone_helper" "http://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository"}}</small></h3>
|
||||
|
||||
@@ -36,12 +36,15 @@
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{template "repo/clone_buttons" .}}
|
||||
{{template "repo/clone_buttons" dict "CloneButtonOriginLink" $.CloneButtonOriginLink}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if not .Repository.IsArchived}}
|
||||
<div class="divider tw-my-0"></div>
|
||||
{{$cloneLink := $.CloneButtonOriginLink}}
|
||||
{{$showGitClientCommands := and (or $cloneLink.SupportHTTPS $cloneLink.SupportSSH) (not .Repository.IsArchived)}}
|
||||
{{/* TODO: when both HTTPS and SSH are disabled, the UI is not that good */}}
|
||||
{{if $showGitClientCommands}}
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="item">
|
||||
<h3>{{ctx.Locale.Tr "repo.create_new_repo_command"}}</h3>
|
||||
@@ -52,19 +55,19 @@ git init{{if ne .Repository.ObjectFormatName "sha1"}} --object-format={{.Reposit
|
||||
{{if ne .Repository.DefaultBranch "master"}}git checkout -b {{.Repository.DefaultBranch}}{{end}}
|
||||
git add README.md
|
||||
git commit -m "first commit"
|
||||
git remote add {{$gitRemoteName}} <span class="js-clone-url">{{$.CloneButtonOriginLink.HTTPS}}</span>
|
||||
git remote add {{$gitRemoteName}} <span class="js-clone-url">{{Iif $cloneLink.SupportHTTPS $cloneLink.HTTPS $cloneLink.SSH}}</span>
|
||||
git push -u {{$gitRemoteName}} {{.Repository.DefaultBranch}}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="item">
|
||||
<h3>{{ctx.Locale.Tr "repo.push_exist_repo"}}</h3>
|
||||
<div class="markup">
|
||||
<pre><code>git remote add {{$gitRemoteName}} <span class="js-clone-url">{{$.CloneButtonOriginLink.HTTPS}}</span>
|
||||
<pre><code>git remote add {{$gitRemoteName}} <span class="js-clone-url">{{Iif $cloneLink.SupportHTTPS $cloneLink.HTTPS $cloneLink.SSH}}</span>
|
||||
git push -u {{$gitRemoteName}} {{.Repository.DefaultBranch}}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="item">HTTPS and SSH clones are disabled, you can only modify the repository via Gitea's web UI.</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
|
||||
@@ -112,7 +112,11 @@
|
||||
{{end}}
|
||||
<!-- Only show clone panel in repository home page -->
|
||||
{{if $isTreePathRoot}}
|
||||
{{template "repo/clone_panel" .}}
|
||||
{{template "repo/clone_panel" (dict
|
||||
"CloneButtonOriginLink" $.CloneButtonOriginLink
|
||||
"OpenWithEditorApps" $.OpenWithEditorApps
|
||||
"DownloadArchiveLinkPrefix" $.DownloadArchiveLinkPrefix
|
||||
)}}
|
||||
{{end}}
|
||||
{{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
|
||||
<a class="ui compact button" href="{{.RepoLink}}/commits/{{.RefTypeNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-text-block">
|
||||
{{template "repo/clone_panel" .}}
|
||||
{{template "repo/clone_panel" dict "CloneButtonOriginLink" $.CloneButtonOriginLink}}
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="ui top header">{{ctx.Locale.Tr "repo.wiki.wiki_page_revisions"}}</h2>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "repo/clone_panel" .}}
|
||||
{{template "repo/clone_panel" dict "CloneButtonOriginLink" $.CloneButtonOriginLink}}
|
||||
</div>
|
||||
<div class="ui dividing header">
|
||||
<div class="flex-text-block tw-flex-wrap tw-justify-end">
|
||||
|
||||
@@ -1321,7 +1321,8 @@ Co-authored-by: user4 <user4@example.com>
|
||||
pullIndex, err := strconv.ParseInt(elems[4], 10, 64)
|
||||
assert.NoError(t, err)
|
||||
pullRequest := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, Index: pullIndex})
|
||||
squashMergeCommitMessage := pull_service.GetSquashMergeCommitMessages(t.Context(), pullRequest)
|
||||
squashMergeCommitMessage, err := pull_service.GetSquashMergeCommitMessages(t.Context(), pullRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedMessage, squashMergeCommitMessage)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {queryElems} from '../utils/dom.ts';
|
||||
import {queryElems, toggleElem} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
@@ -7,6 +7,7 @@ import RepoActivityTopAuthors from '../components/RepoActivityTopAuthors.vue';
|
||||
import {createApp} from 'vue';
|
||||
import {createTippy} from '../modules/tippy.ts';
|
||||
import {localUserSettings} from '../modules/user-settings.ts';
|
||||
import {registerGlobalInitFunc} from '../modules/observer.ts';
|
||||
|
||||
async function onDownloadArchive(e: Event) {
|
||||
e.preventDefault();
|
||||
@@ -51,32 +52,33 @@ export function substituteRepoOpenWithUrl(tmpl: string, url: string): string {
|
||||
return tmpl.replace('{url}', needEncode ? encodeURIComponent(url) : url);
|
||||
}
|
||||
|
||||
function initCloneSchemeUrlSelection(parent: Element) {
|
||||
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url')!;
|
||||
function initRepoCloneButtonsCombo(parent: Element) {
|
||||
// the clone section is not rendered at all when no git transport (HTTPS/SSH) is available
|
||||
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url');
|
||||
if (!elCloneUrlInput) return;
|
||||
|
||||
const tabHttps = parent.querySelector('.repo-clone-https');
|
||||
const tabSsh = parent.querySelector('.repo-clone-ssh');
|
||||
const tabTea = parent.querySelector('.repo-clone-tea');
|
||||
const listOpenWithEditorApps = parent.querySelector('.repo-clone-with-apps');
|
||||
|
||||
// not every tab exists in every panel, eg: the admin may disable HTTP/SSH, and the empty repo page has no Tea CLI tab
|
||||
const tabByScheme: Record<string, Element | null> = {https: tabHttps, ssh: tabSsh, tea: tabTea};
|
||||
const updateClonePanelUi = function() {
|
||||
let scheme = localUserSettings.getString('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';
|
||||
// fall back to the first available tab when the preferred scheme's tab is absent (unset preference, or disabled protocol)
|
||||
if (!tabByScheme[scheme]) {
|
||||
scheme = ['https', 'ssh', 'tea'].find((s) => tabByScheme[s]) ?? '';
|
||||
}
|
||||
|
||||
const isHttps = scheme === 'https';
|
||||
const isSsh = scheme === 'ssh';
|
||||
const isTea = scheme === 'tea';
|
||||
|
||||
if (listOpenWithEditorApps) {
|
||||
toggleElem(listOpenWithEditorApps, !isTea); // don't show the "Open with editor apps" list when "Tea" clone is selected
|
||||
}
|
||||
|
||||
if (tabHttps) {
|
||||
const link = tabHttps.getAttribute('data-link')!;
|
||||
tabHttps.textContent = link.split(':')[0].toUpperCase(); // show "HTTP" or "HTTPS"
|
||||
@@ -89,16 +91,9 @@ function initCloneSchemeUrlSelection(parent: Element) {
|
||||
tabTea.classList.toggle('active', isTea);
|
||||
}
|
||||
|
||||
let tab: Element | null = null;
|
||||
if (isHttps) {
|
||||
tab = tabHttps;
|
||||
} else if (isSsh) {
|
||||
tab = tabSsh;
|
||||
} else if (isTea) {
|
||||
tab = tabTea;
|
||||
}
|
||||
const tab = tabByScheme[scheme];
|
||||
if (!tab) return; // no protocol available at all, leave the (hidden) input untouched
|
||||
|
||||
if (!tab) return;
|
||||
const link = tab.getAttribute('data-link')!;
|
||||
|
||||
for (const el of document.querySelectorAll('.js-clone-url')) {
|
||||
@@ -132,10 +127,10 @@ function initCloneSchemeUrlSelection(parent: Element) {
|
||||
});
|
||||
}
|
||||
|
||||
function initClonePanelButton(btn: HTMLButtonElement) {
|
||||
function initRepoClonePanel(btn: HTMLButtonElement) {
|
||||
const elPanel = btn.nextElementSibling!;
|
||||
// "init" must be before the "createTippy" otherwise the "tippy-target" will be removed from the document
|
||||
initCloneSchemeUrlSelection(elPanel);
|
||||
initRepoCloneButtonsCombo(elPanel);
|
||||
createTippy(btn, {
|
||||
content: elPanel,
|
||||
trigger: 'click',
|
||||
@@ -147,8 +142,8 @@ function initClonePanelButton(btn: HTMLButtonElement) {
|
||||
}
|
||||
|
||||
export function initRepoCloneButtons() {
|
||||
queryElems(document, '.js-btn-clone-panel', initClonePanelButton);
|
||||
queryElems(document, '.clone-buttons-combo', initCloneSchemeUrlSelection);
|
||||
registerGlobalInitFunc('initRepoClonePanel', initRepoClonePanel);
|
||||
registerGlobalInitFunc('initRepoCloneButtonsCombo', initRepoCloneButtonsCombo);
|
||||
}
|
||||
|
||||
export async function updateIssuesMeta(url: string, action: string, issue_ids: string, id: string) {
|
||||
|
||||
Reference in New Issue
Block a user