0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-08 01:34:08 +02:00

Merge branch 'main' into hanism01-fix/project-board-api-review-feedback

This commit is contained in:
Lunny Xiao 2026-04-02 20:12:34 -07:00
commit f08a73838b
7 changed files with 166 additions and 160 deletions

View File

@ -1531,6 +1531,7 @@
"repo.issues.context.edit": "Edit",
"repo.issues.context.delete": "Delete",
"repo.issues.no_content": "No description provided.",
"repo.issues.comment_no_content": "No comment provided.",
"repo.issues.close": "Close Issue",
"repo.issues.comment_pull_merged_at": "merged commit %[1]s into %[2]s %[3]s",
"repo.issues.comment_manually_pull_merged_at": "manually merged commit %[1]s into %[2]s %[3]s",

View File

@ -213,6 +213,9 @@
"editor.buttons.switch_to_legacy.tooltip": "Utiliser lancien éditeur à la place",
"editor.buttons.enable_monospace_font": "Activer la police à chasse fixe",
"editor.buttons.disable_monospace_font": "Désactiver la police à chasse fixe",
"editor.code_editor.command_palette": "Palette de commandes",
"editor.code_editor.find": "Rechercher",
"editor.code_editor.placeholder": "Saisissez ici le contenu du fichier",
"filter.string.asc": "AZ",
"filter.string.desc": "ZA",
"error.occurred": "Une erreur sest produite",
@ -2250,13 +2253,14 @@
"repo.settings.webhook.delivery.success": "Un événement a été ajouté à la file d'attente. Cela peut prendre quelques secondes avant qu'il n'apparaisse dans l'historique de livraison.",
"repo.settings.githooks_desc": "Les déclencheurs Git sont lancés par Git lui-même. Ils sont modifiables dans la liste ci-dessous afin de configurer des opérations personnalisées.",
"repo.settings.githook_edit_desc": "Si un Hook est inactif, un exemple de contenu vous sera proposé. Un contenu laissé vide signifie un Hook inactif.",
"repo.settings.githook_name": "Nom du Hook",
"repo.settings.githook_content": "Contenu du Hook",
"repo.settings.update_githook": "Mettre le Hook à jour",
"repo.settings.add_webhook_desc": "Gitea enverra à l'URL cible des requêtes <code>POST</code> avec un type de contenu spécifié. Lire la suite dans le <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">guide des webhooks</a>.",
"repo.settings.payload_url": "URL cible",
"repo.settings.http_method": "Méthode HTTP",
"repo.settings.content_type": "Type de contenu POST",
"repo.settings.webhook.name": "Nom du déclencheur web",
"repo.settings.webhook.name_helper": "Optionnellement donner un nom convivial à ce déclencheur web",
"repo.settings.webhook.name_empty": "Déclencheur web sans nom",
"repo.settings.secret": "Secret",
"repo.settings.webhook_secret_desc": "Si le serveur webhook supporte lusage de secrets, vous pouvez indiquer un secret ici en vous basant sur leur documentation.",
"repo.settings.slack_username": "Nom d'utilisateur",
@ -2778,9 +2782,9 @@
"org.settings.labels_desc": "Ajoute des labels qui peuvent être utilisés sur les tickets pour <strong>tous les dépôts</strong> de cette organisation.",
"org.members.membership_visibility": "Visibilité des membres:",
"org.members.public": "Public",
"org.members.public_helper": "rendre caché",
"org.members.public_helper": "Cacher",
"org.members.private": "Caché",
"org.members.private_helper": "rendre visible",
"org.members.private_helper": "Révéler",
"org.members.member_role": "Rôle du membre :",
"org.members.owner": "Propriétaire",
"org.members.member": "Membre",
@ -2808,7 +2812,10 @@
"org.teams.no_desc": "Aucune description",
"org.teams.settings": "Paramètres",
"org.teams.owners_permission_desc": "Les propriétaires ont un accès complet à <strong>tous les dépôts</strong> et disposent <strong> d'un accès administrateur</strong> de l'organisation.",
"org.teams.owners_permission_suggestion": "Vous pouvez créer de nouvelles équipes pour les membres afin davoir un contrôle précis sur les droits daccès.",
"org.teams.members": "Membres de L'Équipe",
"org.teams.manage_team_member": "Gérer les équipes et les membres",
"org.teams.manage_team_member_prompt": "Les membres sont gérés par des équipes. Ajoutez des utilisateurs à une équipe pour les inviter dans cette organisation.",
"org.teams.update_settings": "Appliquer les paramètres",
"org.teams.delete_team": "Supprimer l'équipe",
"org.teams.add_team_member": "Ajouter un Membre",
@ -3715,6 +3722,8 @@
"actions.runs.workflow_run_count_1": "%d exécution du workflow",
"actions.runs.workflow_run_count_n": "%d exécutions du workflow",
"actions.runs.commit": "Révision",
"actions.runs.run_details": "Détails de lexécution",
"actions.runs.workflow_file": "Fichier de flux de travail",
"actions.runs.scheduled": "Planifié",
"actions.runs.pushed_by": "soumis par",
"actions.runs.invalid_workflow_helper": "La configuration du flux de travail est invalide. Veuillez vérifier votre fichier %s.",

View File

@ -213,6 +213,9 @@
"editor.buttons.switch_to_legacy.tooltip": "Úsáid an eagarthóir oidhreachta ina ionad",
"editor.buttons.enable_monospace_font": "Cumasaigh cló monospace",
"editor.buttons.disable_monospace_font": "Díchumasaigh cló monospace",
"editor.code_editor.command_palette": "Pailéad Ordú",
"editor.code_editor.find": "Aimsigh",
"editor.code_editor.placeholder": "Cuir isteach ábhar an chomhaid anseo",
"filter.string.asc": "A - Z",
"filter.string.desc": "Z - A",
"error.occurred": "Tharla earráid",
@ -2250,13 +2253,14 @@
"repo.settings.webhook.delivery.success": "Cuireadh imeacht leis an scuaine seachadta. D'fhéadfadh sé cúpla soicind a thógáil sula dtaispeántar sé sa stair seachadta.",
"repo.settings.githooks_desc": "Tá Git Crúcaí faoi thiomáint ag Git féin. Is féidir leat comhaid crúca a chur in eagar thíos chun oibríochtaí saincheaptha a shocrú.",
"repo.settings.githook_edit_desc": "Mura bhfuil an hook neamhghníomhach, cuirfear ábhar samplach i láthair. Má fhágann tú ábhar go luach folamh díchumasófar an crúca seo.",
"repo.settings.githook_name": "Ainm Crúca",
"repo.settings.githook_content": "Ábhar Crúca",
"repo.settings.update_githook": "Nuashonraigh Crúca",
"repo.settings.add_webhook_desc": "Seolfaidh Gitea iarratais <code>POST</code> le cineál ábhar sonraithe chuig an spriocURL. Léigh tuilleadh sa <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">treoir Crúcaí Gréasán</a>.",
"repo.settings.payload_url": "URL spriocdhírithe",
"repo.settings.http_method": "Modh HTTP",
"repo.settings.content_type": "Cineál Ábhar POST",
"repo.settings.webhook.name": "Ainm an Crúca Gréasáin",
"repo.settings.webhook.name_helper": "Tabhair ainm cairdiúil don crúca gréasáin seo más mian leat",
"repo.settings.webhook.name_empty": "Crúca Gréasáin Gan Ainm",
"repo.settings.secret": "Rúnda",
"repo.settings.webhook_secret_desc": "Más féidir le freastalaí an webhook rún a úsáid, is féidir leat lámhleabhar an webhook a leanúint agus rún a líonadh isteach anseo.",
"repo.settings.slack_username": "Ainm úsáideora",
@ -2778,9 +2782,9 @@
"org.settings.labels_desc": "Cuir lipéid leis ar féidir iad a úsáid ar shaincheisteanna do <strong>gach stóras</strong> faoin eagraíocht seo.",
"org.members.membership_visibility": "Infheictheacht Ballraíochta:",
"org.members.public": "Infheicthe",
"org.members.public_helper": "dhéanamh i bhfolach",
"org.members.public_helper": "Déan i bhfolach",
"org.members.private": "I bhfolach",
"org.members.private_helper": "a dhéanamh le feiceáil",
"org.members.private_helper": "Déan infheicthe",
"org.members.member_role": "Ról Comhalta:",
"org.members.owner": "Úinéir",
"org.members.member": "Comhalta",
@ -2808,7 +2812,10 @@
"org.teams.no_desc": "Níl aon tuairisc ag an bhfoireann seo",
"org.teams.settings": "Socruithe",
"org.teams.owners_permission_desc": "Tá rochtain iomlán ag úinéirí ar <strong>gach stórais</strong> agus tá <strong>rochtain ag an riarthóir</strong> ar an eagraíocht.",
"org.teams.owners_permission_suggestion": "Is féidir leat foirne nua a chruthú do bhaill chun rialú rochtana mionsonraithe a fháil.",
"org.teams.members": "Baill Foirne",
"org.teams.manage_team_member": "Bainistigh foirne agus baill",
"org.teams.manage_team_member_prompt": "Déantar baill a bhainistiú trí fhoirne. Cuir úsáideoirí le foireann chun cuireadh a thabhairt dóibh chuig an eagraíocht seo.",
"org.teams.update_settings": "Nuashonrú Socruithe",
"org.teams.delete_team": "Scrios Foireann",
"org.teams.add_team_member": "Cuir Comhalta Foirne leis",
@ -3715,6 +3722,8 @@
"actions.runs.workflow_run_count_1": "%d rith sreabha oibre",
"actions.runs.workflow_run_count_n": "%d rith sreabha oibre",
"actions.runs.commit": "Tiomantas",
"actions.runs.run_details": "Sonraí Rith",
"actions.runs.workflow_file": "Comhad sreabhadh oibre",
"actions.runs.scheduled": "Sceidealaithe",
"actions.runs.pushed_by": "bhrú ag",
"actions.runs.invalid_workflow_helper": "Tá comhad cumraíochta sreabhadh oibre nebhailí. Seiceáil do chomhad cumraithe le do thoil: %s",

View File

@ -21,6 +21,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
@ -31,31 +32,22 @@ import (
// NewComment create a comment for issue
func NewComment(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.CreateCommentForm)
issue := GetActionIssue(ctx)
if ctx.Written() {
if issue == nil {
return
}
if !ctx.IsSigned || (ctx.Doer.ID != issue.PosterID && !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull)) {
if log.IsTrace() {
if ctx.IsSigned {
issueType := "issues"
if issue.IsPull {
issueType = "pulls"
}
log.Trace("Permission Denied: User %-v not the Poster (ID: %d) and cannot read %s in Repo %-v.\n"+
"User in Repo has Permissions: %-+v",
ctx.Doer,
issue.PosterID,
issueType,
ctx.Repo.Repository,
ctx.Repo.Permission)
} else {
log.Trace("Permission Denied: Not logged in")
}
}
if ctx.HasError() {
ctx.JSONError(ctx.GetErrMsg())
return
}
form := web.GetForm(ctx).(*forms.CreateCommentForm)
issueType := util.Iif(issue.IsPull, "pulls", "issues")
if !ctx.IsSigned || (ctx.Doer.ID != issue.PosterID && !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull)) {
log.Trace("Permission Denied: User %-v not the Poster (ID: %d) and cannot read %s in Repo %-v.\n"+
"User in Repo has Permissions: %-+v", ctx.Doer, issue.PosterID, issueType, ctx.Repo.Repository, ctx.Repo.Permission)
ctx.HTTPError(http.StatusForbidden)
return
}
@ -65,137 +57,12 @@ func NewComment(ctx *context.Context) {
return
}
var attachments []string
if setting.Attachment.Enabled {
attachments = form.Files
}
attachments := util.Iif(setting.Attachment.Enabled, form.Files, nil)
if ctx.HasError() {
ctx.JSONError(ctx.GetErrMsg())
return
}
var comment *issues_model.Comment
defer func() {
// Check if issue admin/poster changes the status of issue.
if (ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) || (ctx.IsSigned && issue.IsPoster(ctx.Doer.ID))) &&
(form.Status == "reopen" || form.Status == "close") &&
!(issue.IsPull && issue.PullRequest.HasMerged) {
// Duplication and conflict check should apply to reopen pull request.
var pr *issues_model.PullRequest
if form.Status == "reopen" && issue.IsPull {
pull := issue.PullRequest
var err error
pr, err = issues_model.GetUnmergedPullRequest(ctx, pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch, pull.Flow)
if err != nil {
if !issues_model.IsErrPullRequestNotExist(err) {
ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
return
}
}
// Regenerate patch and test conflict.
if pr == nil {
issue.PullRequest.HeadCommitID = ""
pull_service.StartPullRequestCheckImmediately(ctx, issue.PullRequest)
}
// check whether the ref of PR <refs/pulls/pr_index/head> in base repo is consistent with the head commit of head branch in the head repo
// get head commit of PR
if pull.Flow == issues_model.PullRequestFlowGithub {
prHeadRef := pull.GetGitHeadRefName()
if err := pull.LoadBaseRepo(ctx); err != nil {
ctx.ServerError("Unable to load base repo", err)
return
}
prHeadCommitID, err := gitrepo.GetFullCommitID(ctx, pull.BaseRepo, prHeadRef)
if err != nil {
ctx.ServerError("Get head commit Id of pr fail", err)
return
}
// get head commit of branch in the head repo
if err := pull.LoadHeadRepo(ctx); err != nil {
ctx.ServerError("Unable to load head repo", err)
return
}
if exist, _ := git_model.IsBranchExist(ctx, pull.HeadRepo.ID, pull.BaseBranch); !exist {
// todo localize
ctx.JSONError("The origin branch is delete, cannot reopen.")
return
}
headBranchRef := git.RefNameFromBranch(pull.HeadBranch)
headBranchCommitID, err := gitrepo.GetFullCommitID(ctx, pull.HeadRepo, headBranchRef.String())
if err != nil {
ctx.ServerError("Get head commit Id of head branch fail", err)
return
}
err = pull.LoadIssue(ctx)
if err != nil {
ctx.ServerError("load the issue of pull request error", err)
return
}
if prHeadCommitID != headBranchCommitID {
// force push to base repo
err := gitrepo.Push(ctx, pull.HeadRepo, pull.BaseRepo, git.PushOptions{
Branch: pull.HeadBranch + ":" + prHeadRef,
Force: true,
Env: repo_module.InternalPushingEnvironment(pull.Issue.Poster, pull.BaseRepo),
})
if err != nil {
ctx.ServerError("force push error", err)
return
}
}
}
}
if pr != nil {
ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index))
} else {
if form.Status == "close" && !issue.IsClosed {
if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
log.Error("CloseIssue: %v", err)
if issues_model.IsErrDependenciesLeft(err) {
if issue.IsPull {
ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
} else {
ctx.JSONError(ctx.Tr("repo.issues.dependency.issue_close_blocked"))
}
return
}
} else {
if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
ctx.ServerError("stopTimerIfAvailable", err)
return
}
log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)
}
} else if form.Status == "reopen" && issue.IsClosed {
if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
log.Error("ReopenIssue: %v", err)
}
}
}
}
// Redirect to comment hashtag if there is any actual content.
typeName := "issues"
if issue.IsPull {
typeName = "pulls"
}
if comment != nil {
ctx.JSONRedirect(fmt.Sprintf("%s/%s/%d#%s", ctx.Repo.RepoLink, typeName, issue.Index, comment.HashTag()))
} else {
ctx.JSONRedirect(fmt.Sprintf("%s/%s/%d", ctx.Repo.RepoLink, typeName, issue.Index))
}
}()
// Fix #321: Allow empty comments, as long as we have attachments.
if len(form.Content) == 0 && len(attachments) == 0 {
// Can allow empty comments if there are attachments or a status change (close, reopen, approve, reject)
// So, only stop if there is no content, no attachments, and no status change.
if form.Content == "" && len(attachments) == 0 && form.Status == "" {
ctx.JSONError(ctx.Tr("repo.issues.comment_no_content"))
return
}
@ -209,7 +76,115 @@ func NewComment(ctx *context.Context) {
return
}
log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID)
// ATTENTION: From now on, do not use ctx.JSONError, don't return on user error, because the comment has been created.
// Always use ctx.Flash.Xxx and then redirect, then the message will be displayed
// TODO: need further refactoring to the code below
// Check if doer can change the status of issue (close, reopen).
if (ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) || (ctx.IsSigned && issue.IsPoster(ctx.Doer.ID))) &&
(form.Status == "reopen" || form.Status == "close") &&
!(issue.IsPull && issue.PullRequest.HasMerged) {
// Duplication and conflict check should apply to reopen pull request.
var branchOtherUnmergedPR *issues_model.PullRequest
if form.Status == "reopen" && issue.IsPull {
pull := issue.PullRequest
branchOtherUnmergedPR, err = issues_model.GetUnmergedPullRequest(ctx, pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch, pull.Flow)
if err != nil {
if !issues_model.IsErrPullRequestNotExist(err) {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
}
}
if branchOtherUnmergedPR != nil {
ctx.Flash.Error(ctx.Tr("repo.pulls.open_unmerged_pull_exists", branchOtherUnmergedPR.Index))
} else {
// Regenerate patch and test conflict.
issue.PullRequest.HeadCommitID = ""
pull_service.StartPullRequestCheckImmediately(ctx, issue.PullRequest)
}
// check whether the ref of PR <refs/pulls/pr_index/head> in base repo is consistent with the head commit of head branch in the head repo
// get head commit of PR
if branchOtherUnmergedPR != nil && pull.Flow == issues_model.PullRequestFlowGithub {
prHeadRef := pull.GetGitHeadRefName()
if err := pull.LoadBaseRepo(ctx); err != nil {
ctx.ServerError("Unable to load base repo", err)
return
}
prHeadCommitID, err := gitrepo.GetFullCommitID(ctx, pull.BaseRepo, prHeadRef)
if err != nil {
ctx.ServerError("Get head commit Id of pr fail", err)
return
}
// get head commit of branch in the head repo
if err := pull.LoadHeadRepo(ctx); err != nil {
ctx.ServerError("Unable to load head repo", err)
return
}
if exist, _ := git_model.IsBranchExist(ctx, pull.HeadRepo.ID, pull.BaseBranch); !exist {
ctx.Flash.Error("The origin branch is delete, cannot reopen.")
return
}
headBranchRef := git.RefNameFromBranch(pull.HeadBranch)
headBranchCommitID, err := gitrepo.GetFullCommitID(ctx, pull.HeadRepo, headBranchRef.String())
if err != nil {
ctx.ServerError("Get head commit Id of head branch fail", err)
return
}
err = pull.LoadIssue(ctx)
if err != nil {
ctx.ServerError("load the issue of pull request error", err)
return
}
if prHeadCommitID != headBranchCommitID {
// force push to base repo
err := gitrepo.Push(ctx, pull.HeadRepo, pull.BaseRepo, git.PushOptions{
Branch: pull.HeadBranch + ":" + prHeadRef,
Force: true,
Env: repo_module.InternalPushingEnvironment(pull.Issue.Poster, pull.BaseRepo),
})
if err != nil {
ctx.ServerError("force push error", err)
return
}
}
}
}
if form.Status == "close" && !issue.IsClosed {
if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
log.Error("CloseIssue: %v", err)
if issues_model.IsErrDependenciesLeft(err) {
if issue.IsPull {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
} else {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.issue_close_blocked"))
}
}
} else {
if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
ctx.ServerError("stopTimerIfAvailable", err)
return
}
log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)
}
} else if form.Status == "reopen" && issue.IsClosed && branchOtherUnmergedPR == nil {
if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
log.Error("ReopenIssue: %v", err)
ctx.Flash.Error("Unable to 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)
}
// UpdateCommentContent change comment of issue's content

View File

@ -221,6 +221,7 @@
{{end}}
<div class="divider"></div>
<script type="module">
(() => {
const defaultMergeTitle = {{.DefaultMergeMessage}};
const defaultSquashMergeTitle = {{.DefaultSquashMergeMessage}};
const defaultMergeMessage = {{.DefaultMergeBody}};
@ -299,6 +300,7 @@
}
];
window.config.pageData.pullRequestMergeForm = mergeForm;
})();
</script>
{{$showGeneralMergeForm = true}}

View File

@ -110,7 +110,7 @@
<a href="{{.GetCommentLink ctx}}" class="tw-inline-block tw-truncate issue title">{{(.GetIssueTitle ctx) | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
{{$comment := index .GetIssueInfos 1}}
{{if $comment}}
<div class="render-content markup tw-text-14">{{ctx.RenderUtils.MarkdownToHtml $comment}}</div>
<div class="render-content markup truncated-markup">{{ctx.RenderUtils.MarkdownToHtml $comment}}</div>
{{end}}
{{else if .GetOpType.InActions "merge_pull_request"}}
<div class="flex-item-body tw-text-text">{{index .GetIssueInfos 1 | ctx.RenderUtils.RenderIssueSimpleTitle}}</div>

View File

@ -6,10 +6,20 @@ import {initMarkupTasklist} from './tasklist.ts';
import {registerGlobalSelectorFunc} from '../modules/observer.ts';
import {initMarkupRenderIframe} from './render-iframe.ts';
import {initMarkupRefIssue} from './refissue.ts';
import {toggleElemClass} from '../utils/dom.ts';
// code that runs for all markup content
export function initMarkupContent(): void {
registerGlobalSelectorFunc('.markup', (el: HTMLElement) => {
if (el.matches('.truncated-markup')) {
// when the rendered markup is truncated (e.g.: user's home activity feed)
// we should not initialize any of the features (e.g.: code copy button), due to:
// * truncated markup already means that the container doesn't want to show complex contents
// * truncated markup may contain incomplete HTML/mermaid elements
// so the only thing we need to do is to remove the "is-loading" class added by the backend render.
toggleElemClass(el.querySelectorAll('.is-loading'), 'is-loading', false);
return;
}
initMarkupCodeCopy(el);
initMarkupTasklist(el);
initMarkupCodeMermaid(el);