Fix various problems (#37129)

* Fix #37128
    * Manually tested with various cases (issue, pr) X (close, reopen)
* Fix #36792
    * Fix the comment
* Fix #36755
    * Add a "sleep 3"
* Follow up #36697
    * Clarify the "attachment uploading" problem and function call

---------

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
This commit is contained in:
wxiaoguang
2026-04-08 01:17:05 +08:00
committed by GitHub
parent 1b200dc3da
commit 73e0e44298
9 changed files with 56 additions and 42 deletions

View File

@@ -175,14 +175,15 @@ RUN_USER = ; git
;; The port number the builtin SSH server should listen on, defaults to SSH_PORT ;; The port number the builtin SSH server should listen on, defaults to SSH_PORT
;SSH_LISTEN_PORT = ;SSH_LISTEN_PORT =
;; ;;
;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'. ;; Root path of SSH user directory for the system's standalone SSH server if Gitea is not using its builtin SSH server.
;; Default is the '.ssh' directory in the run user's home directory.
;SSH_ROOT_PATH = ;SSH_ROOT_PATH =
;; ;;
;; Gitea will create a authorized_keys file by default when it is not using the internal ssh server ;; Gitea will create an authorized_keys file by default when it is not using the builtin SSH server
;; If you intend to use the AuthorizedKeysCommand functionality then you should turn this off. ;; If you intend to use the AuthorizedKeysCommand functionality then you should turn this off.
;SSH_CREATE_AUTHORIZED_KEYS_FILE = true ;SSH_CREATE_AUTHORIZED_KEYS_FILE = true
;; ;;
;; Gitea will create a authorized_principals file by default when it is not using the internal ssh server ;; Gitea will create an authorized_principals file by default when it is not using the builtin SSH server
;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off. ;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off.
;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true ;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true
;; ;;
@@ -1178,16 +1179,16 @@ LEVEL = Info
;[repository.release] ;[repository.release]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. ;; Comma-separated list of allowed release attachment file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
;ALLOWED_TYPES = ;ALLOWED_TYPES =
;; ;;
;; Number of releases that are displayed on release page ;; Number of releases that are displayed on release page
;DEFAULT_PAGING_NUM = 10 ;DEFAULT_PAGING_NUM = 10
;; ;;
;; Max size of each file in megabytes. Defaults to 2GB ;; Max size of each release attachment file in megabytes. Defaults to 2GB
;FILE_MAX_SIZE = 2048 ;FILE_MAX_SIZE = 2048
;; ;;
;; Max number of files per upload. Defaults to 5 ;; Max number of release attachment files per upload. Defaults to 5
;MAX_FILES = 5 ;MAX_FILES = 5
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1994,16 +1995,18 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Whether issue and pull request attachments are enabled. Defaults to `true` ;; Whether issue, pull-request and release attachments are enabled. Defaults to `true`
;; ALLOWED_TYPES/MAX_SIZE/MAX_FILES in this section only affect issue and pull-request attachments, not release attachments.
;; Release attachment has its own config options in [repository.release] section.
;ENABLED = true ;ENABLED = true
;; ;;
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. ;; Comma-separated list of allowed issue/pull-request attachment file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
;ALLOWED_TYPES = .avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip ;ALLOWED_TYPES = .avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip
;; ;;
;; Max size of each file. Defaults to 100MB ;; Max size of each issue/pull-request attachment file. Defaults to 100MB
;MAX_SIZE = 100 ;MAX_SIZE = 100
;; ;;
;; Max number of files per upload. Defaults to 5 ;; Max number of issue/pull-request attachment files per upload. Defaults to 5
;MAX_FILES = 5 ;MAX_FILES = 5
;; ;;
;; Storage type for attachments, `local` for local disk or `minio` for s3 compatible ;; Storage type for attachments, `local` for local disk or `minio` for s3 compatible

View File

@@ -1,2 +1,8 @@
#!/bin/bash #!/bin/bash
# $1 = exit code of the run script, $2 = signal
if [ "$1" -ne 0 ]; then
# avoid immediately restarting the sshd service, which may cause CPU 100% if the error (permission, configuration) is not fixed
echo "openssh failed with exit code $1 - waiting a short delay before attempting a restart"
sleep 3
fi
exit 0 exit 0

View File

@@ -186,7 +186,7 @@ func CreateIssueAttachment(ctx *context.APIContext) {
} }
uploaderFile := attachment_service.NewLimitedUploaderKnownSize(file, header.Size) uploaderFile := attachment_service.NewLimitedUploaderKnownSize(file, header.Size)
attachment, err := attachment_service.UploadAttachmentGeneralSizeLimit(ctx, uploaderFile, setting.Attachment.AllowedTypes, &repo_model.Attachment{ attachment, err := attachment_service.UploadAttachmentForIssue(ctx, uploaderFile, &repo_model.Attachment{
Name: filename, Name: filename,
UploaderID: ctx.Doer.ID, UploaderID: ctx.Doer.ID,
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,

View File

@@ -193,7 +193,7 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
} }
uploaderFile := attachment_service.NewLimitedUploaderKnownSize(file, header.Size) uploaderFile := attachment_service.NewLimitedUploaderKnownSize(file, header.Size)
attachment, err := attachment_service.UploadAttachmentGeneralSizeLimit(ctx, uploaderFile, setting.Attachment.AllowedTypes, &repo_model.Attachment{ attachment, err := attachment_service.UploadAttachmentForIssue(ctx, uploaderFile, &repo_model.Attachment{
Name: filename, Name: filename,
UploaderID: ctx.Doer.ID, UploaderID: ctx.Doer.ID,
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,

View File

@@ -242,7 +242,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
} }
// Create a new attachment and save the file // Create a new attachment and save the file
attach, err := attachment_service.UploadAttachmentReleaseSizeLimit(ctx, uploaderFile, setting.Repository.Release.AllowedTypes, &repo_model.Attachment{ attach, err := attachment_service.UploadAttachmentForRelease(ctx, uploaderFile, &repo_model.Attachment{
Name: filename, Name: filename,
UploaderID: ctx.Doer.ID, UploaderID: ctx.Doer.ID,
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,

View File

@@ -23,16 +23,16 @@ import (
// UploadIssueAttachment response for Issue/PR attachments // UploadIssueAttachment response for Issue/PR attachments
func UploadIssueAttachment(ctx *context.Context) { func UploadIssueAttachment(ctx *context.Context) {
uploadAttachment(ctx, ctx.Repo.Repository.ID, setting.Attachment.AllowedTypes) uploadAttachment(ctx, ctx.Repo.Repository.ID, attachment.UploadAttachmentForIssue)
} }
// UploadReleaseAttachment response for uploading release attachments // UploadReleaseAttachment response for uploading release attachments
func UploadReleaseAttachment(ctx *context.Context) { func UploadReleaseAttachment(ctx *context.Context) {
uploadAttachment(ctx, ctx.Repo.Repository.ID, setting.Repository.Release.AllowedTypes) uploadAttachment(ctx, ctx.Repo.Repository.ID, attachment.UploadAttachmentForRelease)
} }
// UploadAttachment response for uploading attachments // UploadAttachment response for uploading attachments
func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) { func uploadAttachment(ctx *context.Context, repoID int64, uploadFunc attachment.UploadAttachmentFunc) {
if !setting.Attachment.Enabled { if !setting.Attachment.Enabled {
ctx.HTTPError(http.StatusNotFound, "attachment is not enabled") ctx.HTTPError(http.StatusNotFound, "attachment is not enabled")
return return
@@ -46,7 +46,7 @@ func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) {
defer file.Close() defer file.Close()
uploaderFile := attachment.NewLimitedUploaderKnownSize(file, header.Size) uploaderFile := attachment.NewLimitedUploaderKnownSize(file, header.Size)
attach, err := attachment.UploadAttachmentReleaseSizeLimit(ctx, uploaderFile, allowedTypes, &repo_model.Attachment{ attach, err := uploadFunc(ctx, uploaderFile, &repo_model.Attachment{
Name: header.Filename, Name: header.Filename,
UploaderID: ctx.Doer.ID, UploaderID: ctx.Doer.ID,
RepoID: repoID, RepoID: repoID,
@@ -56,7 +56,7 @@ func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) {
ctx.HTTPError(http.StatusBadRequest, err.Error()) ctx.HTTPError(http.StatusBadRequest, err.Error())
return return
} }
ctx.ServerError("UploadAttachmentReleaseSizeLimit", err) ctx.ServerError("uploadAttachment(uploadFunc)", err)
return return
} }
@@ -119,7 +119,7 @@ func DeleteAttachment(ctx *context.Context) {
}) })
} }
// GetAttachment serve attachments with the given UUID // ServeAttachment serve attachments with the given UUID
func ServeAttachment(ctx *context.Context, uuid string) { func ServeAttachment(ctx *context.Context, uuid string) {
attach, err := repo_model.GetAttachmentByUUID(ctx, uuid) attach, err := repo_model.GetAttachmentByUUID(ctx, uuid)
if err != nil { if err != nil {

View File

@@ -57,22 +57,26 @@ func NewComment(ctx *context.Context) {
return return
} }
redirect := fmt.Sprintf("%s/%s/%d", ctx.Repo.RepoLink, issueType, issue.Index)
attachments := util.Iif(setting.Attachment.Enabled, form.Files, nil) attachments := util.Iif(setting.Attachment.Enabled, form.Files, nil)
// Can allow empty comments if there are attachments or a status change (close, reopen, approve, reject) // allow empty content if there are attachments
// So, only stop if there is no content, no attachments, and no status change. if form.Content != "" || len(attachments) > 0 {
if form.Content == "" && len(attachments) == 0 && form.Status == "" { comment, err := issue_service.CreateIssueComment(ctx, ctx.Doer, ctx.Repo.Repository, issue, form.Content, attachments)
ctx.JSONError(ctx.Tr("repo.issues.comment_no_content")) if err != nil {
return if errors.Is(err, user_model.ErrBlockedUser) {
} ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user"))
} else {
comment, err := issue_service.CreateIssueComment(ctx, ctx.Doer, ctx.Repo.Repository, issue, form.Content, attachments) ctx.ServerError("CreateIssueComment", err)
if err != nil { }
if errors.Is(err, user_model.ErrBlockedUser) { return
ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user"))
} else {
ctx.ServerError("CreateIssueComment", err)
} }
// redirect to the comment's hashtag
redirect += "#" + comment.HashTag()
} else if form.Status == "" {
// if no status change (close, reopen), it is a plain comment, and content is required
// "approve/reject" are handled differently in SubmitReview
ctx.JSONError(ctx.Tr("repo.issues.comment_no_content"))
return return
} }
@@ -86,6 +90,7 @@ func NewComment(ctx *context.Context) {
!(issue.IsPull && issue.PullRequest.HasMerged) { !(issue.IsPull && issue.PullRequest.HasMerged) {
// Duplication and conflict check should apply to reopen pull request. // Duplication and conflict check should apply to reopen pull request.
var branchOtherUnmergedPR *issues_model.PullRequest var branchOtherUnmergedPR *issues_model.PullRequest
var err error
if form.Status == "reopen" && issue.IsPull { if form.Status == "reopen" && issue.IsPull {
pull := issue.PullRequest pull := issue.PullRequest
branchOtherUnmergedPR, err = issues_model.GetUnmergedPullRequest(ctx, pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch, pull.Flow) branchOtherUnmergedPR, err = issues_model.GetUnmergedPullRequest(ctx, pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch, pull.Flow)
@@ -179,11 +184,6 @@ func NewComment(ctx *context.Context) {
} }
} // end if: handle close or reopen } // end if: handle close or reopen
// Redirect to the comment, add hashtag if it exists
redirect := fmt.Sprintf("%s/%s/%d", ctx.Repo.RepoLink, issueType, issue.Index)
if comment != nil {
redirect += "#" + comment.HashTag()
}
ctx.JSONRedirect(redirect) ctx.JSONRedirect(redirect)
} }

View File

@@ -54,12 +54,17 @@ func NewLimitedUploaderMaxBytesReader(r io.ReadCloser, w http.ResponseWriter) *U
return &UploaderFile{rd: r, size: -1, respWriter: w} return &UploaderFile{rd: r, size: -1, respWriter: w}
} }
func UploadAttachmentGeneralSizeLimit(ctx context.Context, file *UploaderFile, allowedTypes string, attach *repo_model.Attachment) (*repo_model.Attachment, error) { type UploadAttachmentFunc func(ctx context.Context, file *UploaderFile, attach *repo_model.Attachment) (*repo_model.Attachment, error)
return uploadAttachment(ctx, file, allowedTypes, setting.Attachment.MaxSize<<20, attach)
func UploadAttachmentForIssue(ctx context.Context, file *UploaderFile, attach *repo_model.Attachment) (*repo_model.Attachment, error) {
return uploadAttachment(ctx, file, setting.Attachment.AllowedTypes, setting.Attachment.MaxSize<<20, attach)
} }
func UploadAttachmentReleaseSizeLimit(ctx context.Context, file *UploaderFile, allowedTypes string, attach *repo_model.Attachment) (*repo_model.Attachment, error) { func UploadAttachmentForRelease(ctx context.Context, file *UploaderFile, attach *repo_model.Attachment) (*repo_model.Attachment, error) {
return uploadAttachment(ctx, file, allowedTypes, setting.Repository.Release.FileMaxSize<<20, attach) // FIXME: although the release attachment has different settings from the issue attachment,
// it still uses the same attachment table, the same storage and the same upload logic
// So if the "issue attachment [attachment]" is not enabled, it will also affect the release attachment, which is not expected.
return uploadAttachment(ctx, file, setting.Repository.Release.AllowedTypes, setting.Repository.Release.FileMaxSize<<20, attach)
} }
func uploadAttachment(ctx context.Context, file *UploaderFile, allowedTypes string, maxFileSize int64, attach *repo_model.Attachment) (*repo_model.Attachment, error) { func uploadAttachment(ctx context.Context, file *UploaderFile, allowedTypes string, maxFileSize int64, attach *repo_model.Attachment) (*repo_model.Attachment, error) {

View File

@@ -88,7 +88,7 @@ func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *u
for _, attachment := range content.Attachments { for _, attachment := range content.Attachments {
attachmentBuf := bytes.NewReader(attachment.Content) attachmentBuf := bytes.NewReader(attachment.Content)
uploaderFile := attachment_service.NewLimitedUploaderKnownSize(attachmentBuf, attachmentBuf.Size()) uploaderFile := attachment_service.NewLimitedUploaderKnownSize(attachmentBuf, attachmentBuf.Size())
a, err := attachment_service.UploadAttachmentGeneralSizeLimit(ctx, uploaderFile, setting.Attachment.AllowedTypes, &repo_model.Attachment{ a, err := attachment_service.UploadAttachmentForIssue(ctx, uploaderFile, &repo_model.Attachment{
Name: attachment.Name, Name: attachment.Name,
UploaderID: doer.ID, UploaderID: doer.ID,
RepoID: issue.Repo.ID, RepoID: issue.Repo.ID,