mirror of
https://github.com/go-gitea/gitea
synced 2024-11-11 04:34:25 +00:00
Merge branch 'main' into allow-force-push-protected-branches
This commit is contained in:
commit
945ea90acd
2
.github/workflows/files-changed.yml
vendored
2
.github/workflows/files-changed.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
||||
yaml: ${{ steps.changes.outputs.yaml }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v2
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
|
@ -65,14 +65,17 @@ Recommended implementations:
|
||||
|
||||
* Vue + Vanilla JS
|
||||
* Fomantic-UI (jQuery)
|
||||
* htmx (partial page reloads for otherwise static components)
|
||||
* Vanilla JS
|
||||
|
||||
Discouraged implementations:
|
||||
|
||||
* Vue + Fomantic-UI (jQuery)
|
||||
* jQuery + Vanilla JS
|
||||
* htmx + any other framework which requires heavy JS code, or unnecessary features like htmx scripting (`hx-on`)
|
||||
|
||||
To make UI consistent, Vue components can use Fomantic-UI CSS classes.
|
||||
We use htmx for simple interactions. You can see an example for simple interactions where htmx should be used in this [PR](https://github.com/go-gitea/gitea/pull/28908). Do not use htmx if you require more advanced reactivity, use another framework (Vue/Vanilla JS).
|
||||
Although mixing different frameworks is discouraged,
|
||||
it should also work if the mixing is necessary and the code is well-designed and maintainable.
|
||||
|
||||
|
@ -22,4 +22,4 @@ Making the `.profile` repository private will hide the Profile README.
|
||||
|
||||
Example of user with `.profile/README.md`:
|
||||
|
||||
![profile readme screenshot](./profile-readme.png)
|
||||
![profile readme screenshot](/images/usage/profile-readme.png)
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
@ -446,12 +446,9 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
sess := db.GetEngine(ctx).Where(cond)
|
||||
if setting.Database.Type.IsMySQL() {
|
||||
sess = sess.IndexHint("USE", "JOIN", "IDX_action_c_u_d")
|
||||
}
|
||||
sess = sess.Select("`action`.*"). // this line will avoid select other joined table's columns
|
||||
Join("INNER", "repository", "`repository`.id = `action`.repo_id")
|
||||
sess := db.GetEngine(ctx).Where(cond).
|
||||
Select("`action`.*"). // this line will avoid select other joined table's columns
|
||||
Join("INNER", "repository", "`repository`.id = `action`.repo_id")
|
||||
|
||||
opts.SetDefaultValues()
|
||||
sess = db.SetSessionPagination(sess, &opts)
|
||||
|
@ -146,6 +146,41 @@ func DetectWorkflows(
|
||||
return workflows, schedules, nil
|
||||
}
|
||||
|
||||
func DetectScheduledWorkflows(gitRepo *git.Repository, commit *git.Commit) ([]*DetectedWorkflow, error) {
|
||||
entries, err := ListWorkflows(commit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wfs := make([]*DetectedWorkflow, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
content, err := GetContentFromEntry(entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// one workflow may have multiple events
|
||||
events, err := GetEventsFromContent(content)
|
||||
if err != nil {
|
||||
log.Warn("ignore invalid workflow %q: %v", entry.Name(), err)
|
||||
continue
|
||||
}
|
||||
for _, evt := range events {
|
||||
if evt.IsSchedule() {
|
||||
log.Trace("detect scheduled workflow: %q", entry.Name())
|
||||
dwf := &DetectedWorkflow{
|
||||
EntryName: entry.Name(),
|
||||
TriggerEvent: evt,
|
||||
Content: content,
|
||||
}
|
||||
wfs = append(wfs, dwf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return wfs, nil
|
||||
}
|
||||
|
||||
func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader, evt *jobparser.Event) bool {
|
||||
if !canGithubEventMatch(evt.Name, triggedEvent) {
|
||||
return false
|
||||
|
@ -2013,6 +2013,7 @@ settings.mirror_settings.docs.doc_link_title = How do I mirror repositories?
|
||||
settings.mirror_settings.docs.doc_link_pull_section = the "Pulling from a remote repository" section of the documentation.
|
||||
settings.mirror_settings.docs.pulling_remote_title = Pulling from a remote repository
|
||||
settings.mirror_settings.mirrored_repository = Mirrored repository
|
||||
settings.mirror_settings.pushed_repository = Pushed repository
|
||||
settings.mirror_settings.direction = Direction
|
||||
settings.mirror_settings.direction.pull = Pull
|
||||
settings.mirror_settings.direction.push = Push
|
||||
|
@ -17,6 +17,7 @@ template=模板
|
||||
language=语言选项
|
||||
notifications=通知
|
||||
active_stopwatch=活动时间跟踪器
|
||||
tracked_time_summary=基于问题列表过滤器的跟踪时间概要
|
||||
create_new=创建…
|
||||
user_profile_and_more=个人信息和配置
|
||||
signed_in_as=已登录用户
|
||||
@ -90,6 +91,7 @@ remove=移除
|
||||
remove_all=移除所有
|
||||
remove_label_str=`删除标签 "%s"`
|
||||
edit=编辑
|
||||
view=查看
|
||||
|
||||
enabled=启用
|
||||
disabled=禁用
|
||||
@ -359,6 +361,7 @@ disable_register_prompt=对不起,注册功能已被关闭。请联系网站
|
||||
disable_register_mail=已禁用注册的电子邮件确认。
|
||||
manual_activation_only=请联系您的站点管理员来完成激活。
|
||||
remember_me=记住此设备
|
||||
remember_me.compromised=登录令牌不再有效,因为它可能表明帐户已被破坏。请检查您的帐户是否有异常活动。
|
||||
forgot_password_title=忘记密码
|
||||
forgot_password=忘记密码?
|
||||
sign_up_now=还没帐户?马上注册。
|
||||
@ -862,6 +865,7 @@ revoke_oauth2_grant_description=确定撤销此三方应用程序的授权,并
|
||||
revoke_oauth2_grant_success=成功撤销了访问权限。
|
||||
|
||||
twofa_desc=两步验证可以加强你的账号安全性。
|
||||
twofa_recovery_tip=如果您丢失了您的设备,您将能够使用一次性恢复密钥来重新获得对您账户的访问。
|
||||
twofa_is_enrolled=你的账号<strong>已启用</strong>了两步验证。
|
||||
twofa_not_enrolled=你的账号未开启两步验证。
|
||||
twofa_disable=禁用两步认证
|
||||
@ -884,6 +888,8 @@ webauthn_register_key=添加安全密钥
|
||||
webauthn_nickname=昵称
|
||||
webauthn_delete_key=移除安全密钥
|
||||
webauthn_delete_key_desc=如果删除了安全密钥,则不能再使用它登录。继续?
|
||||
webauthn_key_loss_warning=如果您丢失了您的安全密钥,您将无法访问您的帐户。
|
||||
webauthn_alternative_tip=您可能想要配置额外的身份验证方法。
|
||||
|
||||
manage_account_links=管理绑定过的账号
|
||||
manage_account_links_desc=这些外部帐户已经绑定到您的 Gitea 帐户。
|
||||
@ -920,6 +926,7 @@ visibility.private=私有
|
||||
visibility.private_tooltip=仅对您已加入的组织的成员可见。
|
||||
|
||||
[repo]
|
||||
new_repo_helper=代码仓库包含了所有的项目文件,包括版本历史记录。已经在其他地方托管了?<a href="%s">迁移仓库。</a>
|
||||
owner=拥有者
|
||||
owner_helper=由于最大仓库数量限制,一些组织可能不会显示在下拉列表中。
|
||||
repo_name=仓库名称
|
||||
@ -1782,6 +1789,8 @@ pulls.status_checks_failure=一些检查失败了
|
||||
pulls.status_checks_error=一些检查报告了错误
|
||||
pulls.status_checks_requested=必须
|
||||
pulls.status_checks_details=详情
|
||||
pulls.status_checks_hide_all=隐藏所有检查
|
||||
pulls.status_checks_show_all=显示所有检查
|
||||
pulls.update_branch=通过合并更新分支
|
||||
pulls.update_branch_rebase=通过变基更新分支
|
||||
pulls.update_branch_success=分支更新成功
|
||||
@ -1790,6 +1799,11 @@ pulls.outdated_with_base_branch=此分支相比基础分支已过期
|
||||
pulls.close=关闭合并请求
|
||||
pulls.closed_at=`于 <a id="%[1]s" href="#%[1]s">%[2]s</a> 关闭此合并请求 `
|
||||
pulls.reopened_at=`重新打开此合并请求 <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
pulls.cmd_instruction_hint=`查看 <a class="show-instruction">命令行提示</a>。`
|
||||
pulls.cmd_instruction_checkout_title=检出
|
||||
pulls.cmd_instruction_checkout_desc=从你的仓库中检出一个新的分支并测试变更。
|
||||
pulls.cmd_instruction_merge_title=合并
|
||||
pulls.cmd_instruction_merge_desc=合并变更并更新到 Gitea 上
|
||||
pulls.clear_merge_message=清除合并信息
|
||||
pulls.clear_merge_message_hint=清除合并消息只会删除提交消息内容,并保留生成的 git 附加内容,如“Co-Authored-By …”。
|
||||
|
||||
@ -2301,6 +2315,7 @@ settings.dismiss_stale_approvals_desc=当新的提交更改合并请求内容被
|
||||
settings.require_signed_commits=需要签名提交
|
||||
settings.require_signed_commits_desc=拒绝推送未签名或无法验证的提交到分支
|
||||
settings.protect_branch_name_pattern=受保护的分支名称模式
|
||||
settings.protect_branch_name_pattern_desc=分支保护的名称匹配规则。语法请参阅 <a href="github.com/gobwas/glob">文档</a> 。如:main, release/**
|
||||
settings.protect_patterns=规则
|
||||
settings.protect_protected_file_patterns=受保护的文件模式(使用分号 ';' 分隔):
|
||||
settings.protect_protected_file_patterns_desc=即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用分号 (';') 分隔多个模式。 见<a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a>文档了解模式语法。例如: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>
|
||||
@ -2365,7 +2380,7 @@ settings.lfs_findcommits=查找提交
|
||||
settings.lfs_lfs_file_no_commits=没有找到关于此 LFS 文件的提交
|
||||
settings.lfs_noattribute=此路径在默认分支中没有可锁定的属性
|
||||
settings.lfs_delete=删除 OID 为 %s 的 LFS 文件
|
||||
settings.lfs_delete_warning=删除一个 LFS 文件可能导致签出时显示'对象不存在'的错误。确定继续吗?
|
||||
settings.lfs_delete_warning=删除一个 LFS 文件可能导致检出时显示'对象不存在'的错误。确定继续吗?
|
||||
settings.lfs_findpointerfiles=查找指针文件
|
||||
settings.lfs_locks=锁定
|
||||
settings.lfs_invalid_locking_path=无效路径:%s
|
||||
@ -2846,6 +2861,7 @@ emails.updated=电子邮件已更新
|
||||
emails.not_updated=无法更新请求的电子邮件地址: %v
|
||||
emails.duplicate_active=此电子邮件地址已被另一个用户激活使用。
|
||||
emails.change_email_header=更新电子邮件属性
|
||||
emails.change_email_text=您确定要更新该电子邮件地址吗?
|
||||
|
||||
orgs.org_manage_panel=组织管理
|
||||
orgs.name=名称
|
||||
@ -2870,6 +2886,7 @@ packages.package_manage_panel=软件包管理
|
||||
packages.total_size=总大小:%s
|
||||
packages.unreferenced_size=未引用大小: %s
|
||||
packages.cleanup=清理过期数据
|
||||
packages.cleanup.success=清理过期数据成功
|
||||
packages.owner=所有者
|
||||
packages.creator=创建者
|
||||
packages.name=名称
|
||||
@ -3508,12 +3525,17 @@ runs.commit=提交
|
||||
runs.scheduled=已计划的
|
||||
runs.pushed_by=推送者
|
||||
runs.invalid_workflow_helper=工作流配置文件无效。请检查您的配置文件: %s
|
||||
runs.no_matching_online_runner_helper=没有匹配标签的在线 runner: %s
|
||||
runs.actor=操作者
|
||||
runs.status=状态
|
||||
runs.actors_no_select=所有操作者
|
||||
runs.status_no_select=所有状态
|
||||
runs.no_results=没有匹配的结果。
|
||||
runs.no_workflows=目前还没有工作流。
|
||||
runs.no_workflows.quick_start=不知道如何启动Gitea Action?请参阅 <a target="_blank" rel="noopener noreferrer" href="%s">快速启动指南</a>
|
||||
runs.no_workflows.documentation=更多有关 Gitea Action 的信息,请访问 <a target="_blank" rel="noopener noreferrer" href="%s">文档</a>。
|
||||
runs.no_runs=工作流尚未运行过。
|
||||
runs.empty_commit_message=(空白的提交消息)
|
||||
|
||||
workflow.disable=禁用工作流
|
||||
workflow.disable_success=工作流 '%s' 已成功禁用。
|
||||
|
@ -203,7 +203,7 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi
|
||||
}
|
||||
|
||||
return &api.WikiPage{
|
||||
WikiPageMetaData: convert.ToWikiPageMetaData(wikiName, lastCommit, ctx.Repo.Repository),
|
||||
WikiPageMetaData: wiki_service.ToWikiPageMetaData(wikiName, lastCommit, ctx.Repo.Repository),
|
||||
ContentBase64: content,
|
||||
CommitCount: commitsCount,
|
||||
Sidebar: sidebarContent,
|
||||
@ -333,7 +333,7 @@ func ListWikiPages(ctx *context.APIContext) {
|
||||
ctx.Error(http.StatusInternalServerError, "WikiFilenameToName", err)
|
||||
return
|
||||
}
|
||||
pages = append(pages, convert.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
|
||||
pages = append(pages, wiki_service.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(int64(len(entries)))
|
||||
|
@ -11,14 +11,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// RemoveUsernameParameterSuffix returns the username parameter without the (fullname) suffix - leaving just the username
|
||||
func RemoveUsernameParameterSuffix(name string) string {
|
||||
if index := strings.Index(name, " ("); index >= 0 {
|
||||
name = name[:index]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// SanitizeFlashErrorString will sanitize a flash error string
|
||||
func SanitizeFlashErrorString(x string) string {
|
||||
return strings.ReplaceAll(html.EscapeString(x), "\n", "<br>")
|
||||
|
@ -11,12 +11,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRemoveUsernameParameterSuffix(t *testing.T) {
|
||||
assert.Equal(t, "foobar", RemoveUsernameParameterSuffix("foobar (Foo Bar)"))
|
||||
assert.Equal(t, "foobar", RemoveUsernameParameterSuffix("foobar"))
|
||||
assert.Equal(t, "", RemoveUsernameParameterSuffix(""))
|
||||
}
|
||||
|
||||
func TestIsExternalURL(t *testing.T) {
|
||||
setting.AppURL = "https://try.gitea.io/"
|
||||
type test struct {
|
||||
|
@ -24,7 +24,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/utils"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
@ -127,7 +126,7 @@ func TeamsAction(ctx *context.Context) {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
uname := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("uname")))
|
||||
uname := strings.ToLower(ctx.FormString("uname"))
|
||||
var u *user_model.User
|
||||
u, err = user_model.GetUserByName(ctx, uname)
|
||||
if err != nil {
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -90,11 +90,10 @@ func httpBase(ctx *context.Context) *serviceHandler {
|
||||
|
||||
isWiki := false
|
||||
unitType := unit.TypeCode
|
||||
var wikiRepoName string
|
||||
|
||||
if strings.HasSuffix(reponame, ".wiki") {
|
||||
isWiki = true
|
||||
unitType = unit.TypeWiki
|
||||
wikiRepoName = reponame
|
||||
reponame = reponame[:len(reponame)-5]
|
||||
}
|
||||
|
||||
@ -107,16 +106,16 @@ func httpBase(ctx *context.Context) *serviceHandler {
|
||||
repoExist := true
|
||||
repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, reponame)
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoNotExist(err) {
|
||||
if redirectRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, reponame); err == nil {
|
||||
context.RedirectToRepo(ctx.Base, redirectRepoID)
|
||||
return nil
|
||||
}
|
||||
repoExist = false
|
||||
} else {
|
||||
if !repo_model.IsErrRepoNotExist(err) {
|
||||
ctx.ServerError("GetRepositoryByName", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if redirectRepoID, err := repo_model.LookupRedirect(ctx, owner.ID, reponame); err == nil {
|
||||
context.RedirectToRepo(ctx.Base, redirectRepoID)
|
||||
return nil
|
||||
}
|
||||
repoExist = false
|
||||
}
|
||||
|
||||
// Don't allow pushing if the repo is archived
|
||||
@ -292,22 +291,9 @@ func httpBase(ctx *context.Context) *serviceHandler {
|
||||
|
||||
environ = append(environ, repo_module.EnvRepoID+fmt.Sprintf("=%d", repo.ID))
|
||||
|
||||
w := ctx.Resp
|
||||
r := ctx.Req
|
||||
cfg := &serviceConfig{
|
||||
UploadPack: true,
|
||||
ReceivePack: true,
|
||||
Env: environ,
|
||||
}
|
||||
ctx.Req.URL.Path = strings.ToLower(ctx.Req.URL.Path) // blue: In case some repo name has upper case name
|
||||
|
||||
r.URL.Path = strings.ToLower(r.URL.Path) // blue: In case some repo name has upper case name
|
||||
|
||||
dir := repo_model.RepoPath(username, reponame)
|
||||
if isWiki {
|
||||
dir = repo_model.RepoPath(username, wikiRepoName)
|
||||
}
|
||||
|
||||
return &serviceHandler{cfg, w, r, dir, cfg.Env}
|
||||
return &serviceHandler{repo, isWiki, environ}
|
||||
}
|
||||
|
||||
var (
|
||||
@ -352,32 +338,31 @@ func dummyInfoRefs(ctx *context.Context) {
|
||||
_, _ = ctx.Write(infoRefsCache)
|
||||
}
|
||||
|
||||
type serviceConfig struct {
|
||||
UploadPack bool
|
||||
ReceivePack bool
|
||||
Env []string
|
||||
}
|
||||
|
||||
type serviceHandler struct {
|
||||
cfg *serviceConfig
|
||||
w http.ResponseWriter
|
||||
r *http.Request
|
||||
dir string
|
||||
repo *repo_model.Repository
|
||||
isWiki bool
|
||||
environ []string
|
||||
}
|
||||
|
||||
func (h *serviceHandler) setHeaderNoCache() {
|
||||
h.w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
|
||||
h.w.Header().Set("Pragma", "no-cache")
|
||||
h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
|
||||
func (h *serviceHandler) getRepoDir() string {
|
||||
if h.isWiki {
|
||||
return h.repo.WikiPath()
|
||||
}
|
||||
return h.repo.RepoPath()
|
||||
}
|
||||
|
||||
func (h *serviceHandler) setHeaderCacheForever() {
|
||||
func setHeaderNoCache(ctx *context.Context) {
|
||||
ctx.Resp.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
|
||||
ctx.Resp.Header().Set("Pragma", "no-cache")
|
||||
ctx.Resp.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
|
||||
}
|
||||
|
||||
func setHeaderCacheForever(ctx *context.Context) {
|
||||
now := time.Now().Unix()
|
||||
expires := now + 31536000
|
||||
h.w.Header().Set("Date", fmt.Sprintf("%d", now))
|
||||
h.w.Header().Set("Expires", fmt.Sprintf("%d", expires))
|
||||
h.w.Header().Set("Cache-Control", "public, max-age=31536000")
|
||||
ctx.Resp.Header().Set("Date", fmt.Sprintf("%d", now))
|
||||
ctx.Resp.Header().Set("Expires", fmt.Sprintf("%d", expires))
|
||||
ctx.Resp.Header().Set("Cache-Control", "public, max-age=31536000")
|
||||
}
|
||||
|
||||
func containsParentDirectorySeparator(v string) bool {
|
||||
@ -394,71 +379,71 @@ func containsParentDirectorySeparator(v string) bool {
|
||||
|
||||
func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
|
||||
|
||||
func (h *serviceHandler) sendFile(contentType, file string) {
|
||||
func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string) {
|
||||
if containsParentDirectorySeparator(file) {
|
||||
log.Error("request file path contains invalid path: %v", file)
|
||||
h.w.WriteHeader(http.StatusBadRequest)
|
||||
ctx.Resp.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
reqFile := path.Join(h.dir, file)
|
||||
reqFile := filepath.Join(h.getRepoDir(), file)
|
||||
|
||||
fi, err := os.Stat(reqFile)
|
||||
if os.IsNotExist(err) {
|
||||
h.w.WriteHeader(http.StatusNotFound)
|
||||
ctx.Resp.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
h.w.Header().Set("Content-Type", contentType)
|
||||
h.w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
|
||||
h.w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
|
||||
http.ServeFile(h.w, h.r, reqFile)
|
||||
ctx.Resp.Header().Set("Content-Type", contentType)
|
||||
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
|
||||
ctx.Resp.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
|
||||
http.ServeFile(ctx.Resp, ctx.Req, reqFile)
|
||||
}
|
||||
|
||||
// one or more key=value pairs separated by colons
|
||||
var safeGitProtocolHeader = regexp.MustCompile(`^[0-9a-zA-Z]+=[0-9a-zA-Z]+(:[0-9a-zA-Z]+=[0-9a-zA-Z]+)*$`)
|
||||
|
||||
func prepareGitCmdWithAllowedService(service string, h *serviceHandler) (*git.Command, error) {
|
||||
if service == "receive-pack" && h.cfg.ReceivePack {
|
||||
return git.NewCommand(h.r.Context(), "receive-pack"), nil
|
||||
func prepareGitCmdWithAllowedService(ctx *context.Context, service string) (*git.Command, error) {
|
||||
if service == "receive-pack" {
|
||||
return git.NewCommand(ctx, "receive-pack"), nil
|
||||
}
|
||||
if service == "upload-pack" && h.cfg.UploadPack {
|
||||
return git.NewCommand(h.r.Context(), "upload-pack"), nil
|
||||
if service == "upload-pack" {
|
||||
return git.NewCommand(ctx, "upload-pack"), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("service %q is not allowed", service)
|
||||
}
|
||||
|
||||
func serviceRPC(h *serviceHandler, service string) {
|
||||
func serviceRPC(ctx *context.Context, h *serviceHandler, service string) {
|
||||
defer func() {
|
||||
if err := h.r.Body.Close(); err != nil {
|
||||
if err := ctx.Req.Body.Close(); err != nil {
|
||||
log.Error("serviceRPC: Close: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
expectedContentType := fmt.Sprintf("application/x-git-%s-request", service)
|
||||
if h.r.Header.Get("Content-Type") != expectedContentType {
|
||||
log.Error("Content-Type (%q) doesn't match expected: %q", h.r.Header.Get("Content-Type"), expectedContentType)
|
||||
h.w.WriteHeader(http.StatusUnauthorized)
|
||||
if ctx.Req.Header.Get("Content-Type") != expectedContentType {
|
||||
log.Error("Content-Type (%q) doesn't match expected: %q", ctx.Req.Header.Get("Content-Type"), expectedContentType)
|
||||
ctx.Resp.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
cmd, err := prepareGitCmdWithAllowedService(service, h)
|
||||
cmd, err := prepareGitCmdWithAllowedService(ctx, service)
|
||||
if err != nil {
|
||||
log.Error("Failed to prepareGitCmdWithService: %v", err)
|
||||
h.w.WriteHeader(http.StatusUnauthorized)
|
||||
ctx.Resp.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
|
||||
ctx.Resp.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
|
||||
|
||||
reqBody := h.r.Body
|
||||
reqBody := ctx.Req.Body
|
||||
|
||||
// Handle GZIP.
|
||||
if h.r.Header.Get("Content-Encoding") == "gzip" {
|
||||
if ctx.Req.Header.Get("Content-Encoding") == "gzip" {
|
||||
reqBody, err = gzip.NewReader(reqBody)
|
||||
if err != nil {
|
||||
log.Error("Fail to create gzip reader: %v", err)
|
||||
h.w.WriteHeader(http.StatusInternalServerError)
|
||||
ctx.Resp.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -466,23 +451,23 @@ func serviceRPC(h *serviceHandler, service string) {
|
||||
// set this for allow pre-receive and post-receive execute
|
||||
h.environ = append(h.environ, "SSH_ORIGINAL_COMMAND="+service)
|
||||
|
||||
if protocol := h.r.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
|
||||
if protocol := ctx.Req.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
|
||||
h.environ = append(h.environ, "GIT_PROTOCOL="+protocol)
|
||||
}
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.AddArguments("--stateless-rpc").AddDynamicArguments(h.dir)
|
||||
cmd.SetDescription(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.dir))
|
||||
cmd.AddArguments("--stateless-rpc").AddDynamicArguments(h.getRepoDir())
|
||||
cmd.SetDescription(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.getRepoDir()))
|
||||
if err := cmd.Run(&git.RunOpts{
|
||||
Dir: h.dir,
|
||||
Dir: h.getRepoDir(),
|
||||
Env: append(os.Environ(), h.environ...),
|
||||
Stdout: h.w,
|
||||
Stdout: ctx.Resp,
|
||||
Stdin: reqBody,
|
||||
Stderr: &stderr,
|
||||
UseContextTimeout: true,
|
||||
}); err != nil {
|
||||
if err.Error() != "signal: killed" {
|
||||
log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.dir, err, stderr.String())
|
||||
log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.getRepoDir(), err, stderr.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -492,7 +477,7 @@ func serviceRPC(h *serviceHandler, service string) {
|
||||
func ServiceUploadPack(ctx *context.Context) {
|
||||
h := httpBase(ctx)
|
||||
if h != nil {
|
||||
serviceRPC(h, "upload-pack")
|
||||
serviceRPC(ctx, h, "upload-pack")
|
||||
}
|
||||
}
|
||||
|
||||
@ -500,12 +485,12 @@ func ServiceUploadPack(ctx *context.Context) {
|
||||
func ServiceReceivePack(ctx *context.Context) {
|
||||
h := httpBase(ctx)
|
||||
if h != nil {
|
||||
serviceRPC(h, "receive-pack")
|
||||
serviceRPC(ctx, h, "receive-pack")
|
||||
}
|
||||
}
|
||||
|
||||
func getServiceType(r *http.Request) string {
|
||||
serviceType := r.FormValue("service")
|
||||
func getServiceType(ctx *context.Context) string {
|
||||
serviceType := ctx.Req.FormValue("service")
|
||||
if !strings.HasPrefix(serviceType, "git-") {
|
||||
return ""
|
||||
}
|
||||
@ -534,28 +519,28 @@ func GetInfoRefs(ctx *context.Context) {
|
||||
if h == nil {
|
||||
return
|
||||
}
|
||||
h.setHeaderNoCache()
|
||||
service := getServiceType(h.r)
|
||||
cmd, err := prepareGitCmdWithAllowedService(service, h)
|
||||
setHeaderNoCache(ctx)
|
||||
service := getServiceType(ctx)
|
||||
cmd, err := prepareGitCmdWithAllowedService(ctx, service)
|
||||
if err == nil {
|
||||
if protocol := h.r.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
|
||||
if protocol := ctx.Req.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
|
||||
h.environ = append(h.environ, "GIT_PROTOCOL="+protocol)
|
||||
}
|
||||
h.environ = append(os.Environ(), h.environ...)
|
||||
|
||||
refs, _, err := cmd.AddArguments("--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Env: h.environ, Dir: h.dir})
|
||||
refs, _, err := cmd.AddArguments("--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Env: h.environ, Dir: h.getRepoDir()})
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("%v - %s", err, string(refs)))
|
||||
}
|
||||
|
||||
h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
|
||||
h.w.WriteHeader(http.StatusOK)
|
||||
_, _ = h.w.Write(packetWrite("# service=git-" + service + "\n"))
|
||||
_, _ = h.w.Write([]byte("0000"))
|
||||
_, _ = h.w.Write(refs)
|
||||
ctx.Resp.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
|
||||
ctx.Resp.WriteHeader(http.StatusOK)
|
||||
_, _ = ctx.Resp.Write(packetWrite("# service=git-" + service + "\n"))
|
||||
_, _ = ctx.Resp.Write([]byte("0000"))
|
||||
_, _ = ctx.Resp.Write(refs)
|
||||
} else {
|
||||
updateServerInfo(ctx, h.dir)
|
||||
h.sendFile("text/plain; charset=utf-8", "info/refs")
|
||||
updateServerInfo(ctx, h.getRepoDir())
|
||||
h.sendFile(ctx, "text/plain; charset=utf-8", "info/refs")
|
||||
}
|
||||
}
|
||||
|
||||
@ -564,12 +549,12 @@ func GetTextFile(p string) func(*context.Context) {
|
||||
return func(ctx *context.Context) {
|
||||
h := httpBase(ctx)
|
||||
if h != nil {
|
||||
h.setHeaderNoCache()
|
||||
setHeaderNoCache(ctx)
|
||||
file := ctx.Params("file")
|
||||
if file != "" {
|
||||
h.sendFile("text/plain", "objects/info/"+file)
|
||||
h.sendFile(ctx, "text/plain", "objects/info/"+file)
|
||||
} else {
|
||||
h.sendFile("text/plain", p)
|
||||
h.sendFile(ctx, "text/plain", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -579,8 +564,8 @@ func GetTextFile(p string) func(*context.Context) {
|
||||
func GetInfoPacks(ctx *context.Context) {
|
||||
h := httpBase(ctx)
|
||||
if h != nil {
|
||||
h.setHeaderCacheForever()
|
||||
h.sendFile("text/plain; charset=utf-8", "objects/info/packs")
|
||||
setHeaderCacheForever(ctx)
|
||||
h.sendFile(ctx, "text/plain; charset=utf-8", "objects/info/packs")
|
||||
}
|
||||
}
|
||||
|
||||
@ -588,8 +573,8 @@ func GetInfoPacks(ctx *context.Context) {
|
||||
func GetLooseObject(ctx *context.Context) {
|
||||
h := httpBase(ctx)
|
||||
if h != nil {
|
||||
h.setHeaderCacheForever()
|
||||
h.sendFile("application/x-git-loose-object", fmt.Sprintf("objects/%s/%s",
|
||||
setHeaderCacheForever(ctx)
|
||||
h.sendFile(ctx, "application/x-git-loose-object", fmt.Sprintf("objects/%s/%s",
|
||||
ctx.Params("head"), ctx.Params("hash")))
|
||||
}
|
||||
}
|
||||
@ -598,8 +583,8 @@ func GetLooseObject(ctx *context.Context) {
|
||||
func GetPackFile(ctx *context.Context) {
|
||||
h := httpBase(ctx)
|
||||
if h != nil {
|
||||
h.setHeaderCacheForever()
|
||||
h.sendFile("application/x-git-packed-objects", "objects/pack/pack-"+ctx.Params("file")+".pack")
|
||||
setHeaderCacheForever(ctx)
|
||||
h.sendFile(ctx, "application/x-git-packed-objects", "objects/pack/pack-"+ctx.Params("file")+".pack")
|
||||
}
|
||||
}
|
||||
|
||||
@ -607,7 +592,7 @@ func GetPackFile(ctx *context.Context) {
|
||||
func GetIdxFile(ctx *context.Context) {
|
||||
h := httpBase(ctx)
|
||||
if h != nil {
|
||||
h.setHeaderCacheForever()
|
||||
h.sendFile("application/x-git-packed-objects-toc", "objects/pack/pack-"+ctx.Params("file")+".idx")
|
||||
setHeaderCacheForever(ctx)
|
||||
h.sendFile(ctx, "application/x-git-packed-objects-toc", "objects/pack/pack-"+ctx.Params("file")+".idx")
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/routers/utils"
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
org_service "code.gitea.io/gitea/services/org"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
@ -52,7 +51,7 @@ func Collaboration(ctx *context.Context) {
|
||||
|
||||
// CollaborationPost response for actions for a collaboration of a repository
|
||||
func CollaborationPost(ctx *context.Context) {
|
||||
name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("collaborator")))
|
||||
name := strings.ToLower(ctx.FormString("collaborator"))
|
||||
if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
|
||||
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
|
||||
return
|
||||
@ -144,7 +143,7 @@ func AddTeamPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("team")))
|
||||
name := strings.ToLower(ctx.FormString("team"))
|
||||
if len(name) == 0 {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||
return
|
||||
|
@ -474,3 +474,35 @@ func handleSchedules(
|
||||
|
||||
return actions_model.CreateScheduleTask(ctx, crons)
|
||||
}
|
||||
|
||||
// DetectAndHandleSchedules detects the schedule workflows on the default branch and create schedule tasks
|
||||
func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository) error {
|
||||
gitRepo, err := gitrepo.OpenRepository(context.Background(), repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git.OpenRepository: %w", err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
// Only detect schedule workflows on the default branch
|
||||
commit, err := gitRepo.GetCommit(repo.DefaultBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gitRepo.GetCommit: %w", err)
|
||||
}
|
||||
scheduleWorkflows, err := actions_module.DetectScheduledWorkflows(gitRepo, commit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("detect schedule workflows: %w", err)
|
||||
}
|
||||
if len(scheduleWorkflows) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We need a notifyInput to call handleSchedules
|
||||
// Here we use the commit author as the Doer of the notifyInput
|
||||
commitUser, err := user_model.GetUserByEmail(ctx, commit.Author.Email)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get user by email: %w", err)
|
||||
}
|
||||
notifyInput := newNotifyInput(repo, commitUser, webhook_module.HookEventSchedule)
|
||||
|
||||
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, repo.DefaultBranch)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
@ -65,8 +66,15 @@ func startTasks(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
cfg := row.Repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
|
||||
if cfg.IsWorkflowDisabled(row.Schedule.WorkflowID) {
|
||||
cfg, err := row.Repo.GetUnit(ctx, unit.TypeActions)
|
||||
if err != nil {
|
||||
if repo_model.IsErrUnitTypeNotExist(err) {
|
||||
// Skip the actions unit of this repo is disabled.
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("GetUnit: %w", err)
|
||||
}
|
||||
if cfg.ActionsConfig().IsWorkflowDisabled(row.Schedule.WorkflowID) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,8 @@ package convert
|
||||
import (
|
||||
"time"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
wiki_service "code.gitea.io/gitea/services/wiki"
|
||||
)
|
||||
|
||||
// ToWikiCommit convert a git commit into a WikiCommit
|
||||
@ -46,15 +43,3 @@ func ToWikiCommitList(commits []*git.Commit, total int64) *api.WikiCommitList {
|
||||
Count: total,
|
||||
}
|
||||
}
|
||||
|
||||
// ToWikiPageMetaData converts meta information to a WikiPageMetaData
|
||||
func ToWikiPageMetaData(wikiName wiki_service.WebPath, lastCommit *git.Commit, repo *repo_model.Repository) *api.WikiPageMetaData {
|
||||
subURL := string(wikiName)
|
||||
_, title := wiki_service.WebPathToUserTitle(wikiName)
|
||||
return &api.WikiPageMetaData{
|
||||
Title: title,
|
||||
HTMLURL: util.URLJoin(repo.HTMLURL(), "wiki", subURL),
|
||||
SubURL: subURL,
|
||||
LastCommit: ToWikiCommit(lastCommit),
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
)
|
||||
|
||||
// UpdateRepositoryUnits updates a repository's units
|
||||
@ -33,6 +34,15 @@ func UpdateRepositoryUnits(ctx context.Context, repo *repo_model.Repository, uni
|
||||
}
|
||||
}
|
||||
|
||||
for _, u := range units {
|
||||
if u.Type == unit.TypeActions {
|
||||
if err := actions_service.DetectAndHandleSchedules(ctx, repo); err != nil {
|
||||
log.Error("DetectAndHandleSchedules: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(repo_model.RepoUnit)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -9,7 +9,10 @@ import (
|
||||
"strings"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
||||
// To define the wiki related concepts:
|
||||
@ -155,3 +158,15 @@ func UserTitleToWebPath(base, title string) WebPath {
|
||||
}
|
||||
return WebPath(title)
|
||||
}
|
||||
|
||||
// ToWikiPageMetaData converts meta information to a WikiPageMetaData
|
||||
func ToWikiPageMetaData(wikiName WebPath, lastCommit *git.Commit, repo *repo_model.Repository) *api.WikiPageMetaData {
|
||||
subURL := string(wikiName)
|
||||
_, title := WebPathToUserTitle(wikiName)
|
||||
return &api.WikiPageMetaData{
|
||||
Title: title,
|
||||
HTMLURL: util.URLJoin(repo.HTMLURL(), "wiki", subURL),
|
||||
SubURL: subURL,
|
||||
LastCommit: convert.ToWikiCommit(lastCommit),
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@
|
||||
{{else if .IsPDFFile}}
|
||||
<div class="pdf-content is-loading" data-src="{{$.RawFileLink}}" data-fallback-button-text="{{ctx.Locale.Tr "diff.view_file"}}"></div>
|
||||
{{else}}
|
||||
<a href="{{$.RawFileLink}}" rel="nofollow" class="btn btn-gray btn-radius">{{ctx.Locale.Tr "repo.file_view_raw"}}</a>
|
||||
<a href="{{$.RawFileLink}}" rel="nofollow">{{ctx.Locale.Tr "repo.file_view_raw"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else if .FileSize}}
|
||||
|
@ -105,8 +105,9 @@
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.settings.mirror_settings.docs.no_new_mirrors"}} {{ctx.Locale.Tr "repo.settings.mirror_settings.docs.can_still_use"}}<br>
|
||||
{{end}}
|
||||
|
||||
{{if .Repository.IsMirror}}
|
||||
<table class="ui table">
|
||||
{{if $existingPushMirror}}
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:40%">{{ctx.Locale.Tr "repo.settings.mirror_settings.mirrored_repository"}}</th>
|
||||
@ -200,8 +201,18 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<thead><tr><th colspan="4"></th></tr></thead>
|
||||
{{end}}{{/* end if: IsMirror */}}
|
||||
</table>
|
||||
{{end}}{{/* end if: IsMirror */}}
|
||||
|
||||
<table class="ui table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:40%">{{ctx.Locale.Tr "repo.settings.mirror_settings.pushed_repository"}}</th>
|
||||
<th>{{ctx.Locale.Tr "repo.settings.mirror_settings.direction"}}</th>
|
||||
<th>{{ctx.Locale.Tr "repo.settings.mirror_settings.last_update"}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .PushMirrors}}
|
||||
<tr>
|
||||
|
@ -1,7 +1,7 @@
|
||||
{{if .EscapeStatus}}
|
||||
{{if .EscapeStatus.HasInvisible}}
|
||||
<div class="ui warning message unicode-escape-prompt gt-text-left">
|
||||
<button class="close icon hide-panel button" data-panel-closest=".message">{{svg "octicon-x" 16 "close inside"}}</button>
|
||||
<button class="btn close icon hide-panel" data-panel-closest=".message">{{svg "octicon-x" 16 "close inside"}}</button>
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.invisible_runes_header"}}
|
||||
</div>
|
||||
@ -12,7 +12,7 @@
|
||||
</div>
|
||||
{{else if .EscapeStatus.HasAmbiguous}}
|
||||
<div class="ui warning message unicode-escape-prompt gt-text-left">
|
||||
<button class="close icon hide-panel button" data-panel-closest=".message">{{svg "octicon-x" 16 "close inside"}}</button>
|
||||
<button class="btn close icon hide-panel" data-panel-closest=".message">{{svg "octicon-x" 16 "close inside"}}</button>
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.ambiguous_runes_header"}}
|
||||
</div>
|
||||
|
@ -108,7 +108,7 @@
|
||||
{{else if .IsPDFFile}}
|
||||
<div class="pdf-content is-loading" data-src="{{$.RawFileLink}}" data-fallback-button-text="{{ctx.Locale.Tr "repo.diff.view_file"}}"></div>
|
||||
{{else}}
|
||||
<a href="{{$.RawFileLink}}" rel="nofollow" class="btn btn-gray btn-radius">{{ctx.Locale.Tr "repo.file_view_raw"}}</a>
|
||||
<a href="{{$.RawFileLink}}" rel="nofollow">{{ctx.Locale.Tr "repo.file_view_raw"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else if .FileSize}}
|
||||
|
@ -196,10 +196,14 @@ a.label,
|
||||
.ui.search > .results {
|
||||
background: var(--color-body);
|
||||
border-color: var(--color-secondary);
|
||||
overflow-wrap: anywhere; /* allow text to wrap as fomantic limits this to 18em width */
|
||||
}
|
||||
|
||||
.ui.search > .results .result {
|
||||
background: var(--color-body);
|
||||
border-color: var(--color-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ui.search > .results .result .title {
|
||||
|
@ -2128,14 +2128,16 @@
|
||||
}
|
||||
|
||||
#search-user-box .results .result .image {
|
||||
float: left;
|
||||
margin-right: 8px;
|
||||
order: 0;
|
||||
margin-right: 12px;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
min-width: 2em;
|
||||
min-height: 2em;
|
||||
}
|
||||
|
||||
#search-user-box .results .result .content {
|
||||
margin: 6px 0; /* this trick is used to align with the sibling avatar image */
|
||||
margin: 0; /* remove margin reserved for avatar because we move it to left via `order: 0` */
|
||||
}
|
||||
|
||||
.ui.menu .item > img:not(.ui) {
|
||||
|
@ -17,14 +17,13 @@ export function initCompSearchUserBox() {
|
||||
const searchQuery = $searchUserBox.find('input').val();
|
||||
const searchQueryUppercase = searchQuery.toUpperCase();
|
||||
$.each(response.data, (_i, item) => {
|
||||
let title = item.login;
|
||||
if (item.full_name && item.full_name.length > 0) {
|
||||
title += ` (${htmlEscape(item.full_name)})`;
|
||||
}
|
||||
const resultItem = {
|
||||
title,
|
||||
title: item.login,
|
||||
image: item.avatar_url
|
||||
};
|
||||
if (item.full_name) {
|
||||
resultItem.description = htmlEscape(item.full_name);
|
||||
}
|
||||
if (searchQueryUppercase === item.login.toUpperCase()) {
|
||||
items.unshift(resultItem);
|
||||
} else {
|
||||
|
@ -52,9 +52,9 @@ export function initRepoSettingSearchTeamBox() {
|
||||
onResponse(response) {
|
||||
const items = [];
|
||||
$.each(response.data, (_i, item) => {
|
||||
const title = `${item.name} (${item.permission} access)`;
|
||||
items.push({
|
||||
title,
|
||||
title: item.name,
|
||||
description: `${item.permission} access` // TODO: translate this string
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -12,8 +12,10 @@ export function renderCodeCopy() {
|
||||
if (!els.length) return;
|
||||
|
||||
for (const el of els) {
|
||||
if (!el.textContent) continue;
|
||||
const btn = makeCodeCopyButton();
|
||||
btn.setAttribute('data-clipboard-text', el.textContent);
|
||||
// remove final trailing newline introduced during HTML rendering
|
||||
btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, ''));
|
||||
el.after(btn);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user