diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml
index e7039053af..c909f78597 100644
--- a/.github/workflows/files-changed.yml
+++ b/.github/workflows/files-changed.yml
@@ -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: |
diff --git a/docs/content/contributing/guidelines-frontend.en-us.md b/docs/content/contributing/guidelines-frontend.en-us.md
index aa1759d9c9..edd89e1231 100644
--- a/docs/content/contributing/guidelines-frontend.en-us.md
+++ b/docs/content/contributing/guidelines-frontend.en-us.md
@@ -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.
diff --git a/docs/content/usage/profile-readme.en-us.md b/docs/content/usage/profile-readme.en-us.md
index fe42fa2723..316a735a1c 100644
--- a/docs/content/usage/profile-readme.en-us.md
+++ b/docs/content/usage/profile-readme.en-us.md
@@ -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)
diff --git a/docs/content/usage/profile-readme.png b/docs/static/images/usage/profile-readme.png
similarity index 100%
rename from docs/content/usage/profile-readme.png
rename to docs/static/images/usage/profile-readme.png
diff --git a/models/activities/action.go b/models/activities/action.go
index c9745e4a8a..15bd9a52ac 100644
--- a/models/activities/action.go
+++ b/models/activities/action.go
@@ -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)
diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go
index cbc7e011d1..a883f4181b 100644
--- a/modules/actions/workflows.go
+++ b/modules/actions/workflows.go
@@ -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
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 92a4fcab9c..59f0d8c880 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -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
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index 77414f1570..faa1215d5b 100644
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -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=你的账号已启用了两步验证。
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=代码仓库包含了所有的项目文件,包括版本历史记录。已经在其他地方托管了?迁移仓库。
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=`于 %[2]s 关闭此合并请求 `
pulls.reopened_at=`重新打开此合并请求 %[2]s`
+pulls.cmd_instruction_hint=`查看 命令行提示。`
+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=分支保护的名称匹配规则。语法请参阅 文档 。如:main, release/**
settings.protect_patterns=规则
settings.protect_protected_file_patterns=受保护的文件模式(使用分号 ';' 分隔):
settings.protect_protected_file_patterns_desc=即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用分号 (';') 分隔多个模式。 见github.com/gobwas/glob文档了解模式语法。例如: .drone.yml
, /docs/**/*.txt
@@ -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?请参阅 快速启动指南
+runs.no_workflows.documentation=更多有关 Gitea Action 的信息,请访问 文档。
runs.no_runs=工作流尚未运行过。
+runs.empty_commit_message=(空白的提交消息)
workflow.disable=禁用工作流
workflow.disable_success=工作流 '%s' 已成功禁用。
diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go
index ba3e978a83..4f27500496 100644
--- a/routers/api/v1/repo/wiki.go
+++ b/routers/api/v1/repo/wiki.go
@@ -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)))
diff --git a/routers/utils/utils.go b/routers/utils/utils.go
index d6856fceac..1f4d11fd3c 100644
--- a/routers/utils/utils.go
+++ b/routers/utils/utils.go
@@ -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", "
")
diff --git a/routers/utils/utils_test.go b/routers/utils/utils_test.go
index 6d19214c88..440aad87c6 100644
--- a/routers/utils/utils_test.go
+++ b/routers/utils/utils_test.go
@@ -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 {
diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go
index 9e65c8ba9c..71fe99c97c 100644
--- a/routers/web/org/teams.go
+++ b/routers/web/org/teams.go
@@ -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 {
diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go
index 6d3dd5a3fe..f52abbfb02 100644
--- a/routers/web/repo/githttp.go
+++ b/routers/web/repo/githttp.go
@@ -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")
}
}
diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go
index e217697cc0..c5c2a88c49 100644
--- a/routers/web/repo/setting/collaboration.go
+++ b/routers/web/repo/setting/collaboration.go
@@ -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
diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go
index 9900de3d2e..77173e58a3 100644
--- a/services/actions/notifier_helper.go
+++ b/services/actions/notifier_helper.go
@@ -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)
+}
diff --git a/services/actions/schedule_tasks.go b/services/actions/schedule_tasks.go
index e7aa4a39ac..79dd84e0cc 100644
--- a/services/actions/schedule_tasks.go
+++ b/services/actions/schedule_tasks.go
@@ -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
}
diff --git a/services/convert/wiki.go b/services/convert/wiki.go
index 1f04843483..767bfdb88d 100644
--- a/services/convert/wiki.go
+++ b/services/convert/wiki.go
@@ -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),
- }
-}
diff --git a/services/repository/setting.go b/services/repository/setting.go
index 6496ac4014..b82f24271e 100644
--- a/services/repository/setting.go
+++ b/services/repository/setting.go
@@ -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
}
diff --git a/services/wiki/wiki_path.go b/services/wiki/wiki_path.go
index e51d6c630c..74c7064043 100644
--- a/services/wiki/wiki_path.go
+++ b/services/wiki/wiki_path.go
@@ -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),
+ }
+}
diff --git a/templates/repo/settings/lfs_file.tmpl b/templates/repo/settings/lfs_file.tmpl
index bb2e25e86c..0aeb2af178 100644
--- a/templates/repo/settings/lfs_file.tmpl
+++ b/templates/repo/settings/lfs_file.tmpl
@@ -33,7 +33,7 @@
{{else if .IsPDFFile}}
{{else}}
- {{ctx.Locale.Tr "repo.file_view_raw"}}
+ {{ctx.Locale.Tr "repo.file_view_raw"}}
{{end}}
{{else if .FileSize}}
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl
index 07b2f58d53..dfb909e743 100644
--- a/templates/repo/settings/options.tmpl
+++ b/templates/repo/settings/options.tmpl
@@ -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"}}
{{end}}
+
+ {{if .Repository.IsMirror}}
- {{if $existingPushMirror}}
{{ctx.Locale.Tr "repo.settings.mirror_settings.mirrored_repository"}} |
@@ -200,8 +201,18 @@
- |
- {{end}}{{/* end if: IsMirror */}}
+
+ {{end}}{{/* end if: IsMirror */}}
+
+
+
+
+ {{ctx.Locale.Tr "repo.settings.mirror_settings.pushed_repository"}} |
+ {{ctx.Locale.Tr "repo.settings.mirror_settings.direction"}} |
+ {{ctx.Locale.Tr "repo.settings.mirror_settings.last_update"}} |
+ |
+
+
{{range .PushMirrors}}
diff --git a/templates/repo/unicode_escape_prompt.tmpl b/templates/repo/unicode_escape_prompt.tmpl
index 8f02a489e9..d0730f23c1 100644
--- a/templates/repo/unicode_escape_prompt.tmpl
+++ b/templates/repo/unicode_escape_prompt.tmpl
@@ -1,7 +1,7 @@
{{if .EscapeStatus}}
{{if .EscapeStatus.HasInvisible}}
-
+
@@ -12,7 +12,7 @@
{{else if .EscapeStatus.HasAmbiguous}}
-
+
diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl
index e6591df7e3..1f9e0b5028 100644
--- a/templates/repo/view_file.tmpl
+++ b/templates/repo/view_file.tmpl
@@ -108,7 +108,7 @@
{{else if .IsPDFFile}}
{{else}}
-
{{ctx.Locale.Tr "repo.file_view_raw"}}
+
{{ctx.Locale.Tr "repo.file_view_raw"}}
{{end}}
{{else if .FileSize}}
diff --git a/web_src/css/base.css b/web_src/css/base.css
index cc1f6a8397..198e87c0e2 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -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 {
diff --git a/web_src/css/repo.css b/web_src/css/repo.css
index dfe0d6c77f..55c6ec4817 100644
--- a/web_src/css/repo.css
+++ b/web_src/css/repo.css
@@ -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) {
diff --git a/web_src/js/features/comp/SearchUserBox.js b/web_src/js/features/comp/SearchUserBox.js
index 960b787fea..992d4ef020 100644
--- a/web_src/js/features/comp/SearchUserBox.js
+++ b/web_src/js/features/comp/SearchUserBox.js
@@ -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 {
diff --git a/web_src/js/features/repo-settings.js b/web_src/js/features/repo-settings.js
index 04974200bb..75e624a6a7 100644
--- a/web_src/js/features/repo-settings.js
+++ b/web_src/js/features/repo-settings.js
@@ -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
});
});
diff --git a/web_src/js/markup/codecopy.js b/web_src/js/markup/codecopy.js
index a12802ef73..078d741253 100644
--- a/web_src/js/markup/codecopy.js
+++ b/web_src/js/markup/codecopy.js
@@ -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);
}
}