From 78118a3b029ee4eb140d47be22e86df17253a786 Mon Sep 17 00:00:00 2001 From: Jimmy Praet Date: Tue, 13 Jul 2021 01:26:25 +0200 Subject: [PATCH 1/3] Add checkbox to delete pull branch after successful merge (#16049) * Add checkbox to delete pull branch after successful merge * Omit DeleteBranchAfterMerge field in json * Log a warning instead of error when PR head branch deleted * Add DefaultDeleteBranchAfterMerge to PullRequestConfig * Add support for delete_branch_after_merge via API * Fix for API: the branch should be deleted from the HEAD repo If head and base repo are the same, reuse the already opened ctx.Repo.GitRepo * Don't delegate to CleanupBranch, only reuse branch deletion code CleanupBranch contains too much logic that has already been performed by the Merge * Reuse gitrepo in MergePullRequest Co-authored-by: Andrew Thornton --- models/repo_unit.go | 17 +++--- modules/structs/repo.go | 2 + options/locale/locale_en-US.ini | 1 + routers/api/v1/repo/pull.go | 34 ++++++++++++ routers/api/v1/repo/repo.go | 20 ++++--- routers/web/repo/pull.go | 59 +++++++++++++++++---- routers/web/repo/setting.go | 17 +++--- services/forms/repo_form.go | 12 +++-- services/pull/pull.go | 6 ++- services/pull/temp_repo.go | 7 ++- services/pull/update.go | 4 +- templates/repo/issue/view_content/pull.tmpl | 30 +++++++++++ templates/repo/settings/options.tmpl | 6 +++ templates/swagger/v1_json.tmpl | 9 ++++ 14 files changed, 181 insertions(+), 43 deletions(-) diff --git a/models/repo_unit.go b/models/repo_unit.go index d8060d16a03c9..a12e056a7d5ad 100644 --- a/models/repo_unit.go +++ b/models/repo_unit.go @@ -91,14 +91,15 @@ func (cfg *IssuesConfig) ToDB() ([]byte, error) { // PullRequestsConfig describes pull requests config type PullRequestsConfig struct { - IgnoreWhitespaceConflicts bool - AllowMerge bool - AllowRebase bool - AllowRebaseMerge bool - AllowSquash bool - AllowManualMerge bool - AutodetectManualMerge bool - DefaultMergeStyle MergeStyle + IgnoreWhitespaceConflicts bool + AllowMerge bool + AllowRebase bool + AllowRebaseMerge bool + AllowSquash bool + AllowManualMerge bool + AutodetectManualMerge bool + DefaultDeleteBranchAfterMerge bool + DefaultMergeStyle MergeStyle } // FromDB fills up a PullRequestsConfig from serialized format. diff --git a/modules/structs/repo.go b/modules/structs/repo.go index cef864c0205bf..2089f4d69cd0c 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -172,6 +172,8 @@ type EditRepoOption struct { AllowManualMerge *bool `json:"allow_manual_merge,omitempty"` // either `true` to enable AutodetectManualMerge, or `false` to prevent it. `has_pull_requests` must be `true`, Note: In some special cases, misjudgments can occur. AutodetectManualMerge *bool `json:"autodetect_manual_merge,omitempty"` + // set to `true` to delete pr branch after merge by default + DefaultDeleteBranchAfterMerge *bool `json:"default_delete_branch_after_merge,omitempty"` // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", or "squash". `has_pull_requests` must be `true`. DefaultMergeStyle *string `json:"default_merge_style,omitempty"` // set to `true` to archive this repository. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 191cb5de6764c..c0ea28172b38d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1664,6 +1664,7 @@ settings.pulls.allow_rebase_merge_commit = Enable Rebasing with explicit merge c settings.pulls.allow_squash_commits = Enable Squashing to Merge Commits settings.pulls.allow_manual_merge = Enable Mark PR as manually merged settings.pulls.enable_autodetect_manual_merge = Enable autodetect manual merge (Note: In some special cases, misjudgments can occur) +settings.pulls.default_delete_branch_after_merge = Delete pull request branch after merge by default settings.projects_desc = Enable Repository Projects settings.admin_settings = Administrator Settings settings.admin_enable_health_check = Enable Repository Health Checks (git fsck) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 0c09a9a86b0ee..66bcabfd38c41 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -5,6 +5,7 @@ package repo import ( + "errors" "fmt" "math" "net/http" @@ -25,6 +26,7 @@ import ( "code.gitea.io/gitea/services/forms" issue_service "code.gitea.io/gitea/services/issue" pull_service "code.gitea.io/gitea/services/pull" + repo_service "code.gitea.io/gitea/services/repository" ) // ListPullRequests returns a list of all PRs @@ -878,6 +880,38 @@ func MergePullRequest(ctx *context.APIContext) { } log.Trace("Pull request merged: %d", pr.ID) + + if form.DeleteBranchAfterMerge { + var headRepo *git.Repository + if ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.HeadRepoID && ctx.Repo.GitRepo != nil { + headRepo = ctx.Repo.GitRepo + } else { + headRepo, err = git.OpenRepository(pr.HeadRepo.RepoPath()) + if err != nil { + ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err) + return + } + defer headRepo.Close() + } + if err := repo_service.DeleteBranch(ctx.User, pr.HeadRepo, headRepo, pr.HeadBranch); err != nil { + switch { + case git.IsErrBranchNotExist(err): + ctx.NotFound(err) + case errors.Is(err, repo_service.ErrBranchIsDefault): + ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch")) + case errors.Is(err, repo_service.ErrBranchIsProtected): + ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected")) + default: + ctx.Error(http.StatusInternalServerError, "DeleteBranch", err) + } + return + } + if err := models.AddDeletePRBranchComment(ctx.User, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil { + // Do not fail here as branch has already been deleted + log.Error("DeleteBranch: %v", err) + } + } + ctx.Status(http.StatusOK) } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 5d397191a6138..77691b4d1561b 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -833,14 +833,15 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { if err != nil { // Unit type doesn't exist so we make a new config file with default values config = &models.PullRequestsConfig{ - IgnoreWhitespaceConflicts: false, - AllowMerge: true, - AllowRebase: true, - AllowRebaseMerge: true, - AllowSquash: true, - AllowManualMerge: true, - AutodetectManualMerge: false, - DefaultMergeStyle: models.MergeStyleMerge, + IgnoreWhitespaceConflicts: false, + AllowMerge: true, + AllowRebase: true, + AllowRebaseMerge: true, + AllowSquash: true, + AllowManualMerge: true, + AutodetectManualMerge: false, + DefaultDeleteBranchAfterMerge: false, + DefaultMergeStyle: models.MergeStyleMerge, } } else { config = unit.PullRequestsConfig() @@ -867,6 +868,9 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { if opts.AutodetectManualMerge != nil { config.AutodetectManualMerge = *opts.AutodetectManualMerge } + if opts.DefaultDeleteBranchAfterMerge != nil { + config.DefaultDeleteBranchAfterMerge = *opts.DefaultDeleteBranchAfterMerge + } if opts.DefaultMergeStyle != nil { config.DefaultMergeStyle = models.MergeStyle(*opts.DefaultMergeStyle) } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index e5554e9664444..a299799647777 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -965,6 +965,22 @@ func MergePullRequest(ctx *context.Context) { } log.Trace("Pull request merged: %d", pr.ID) + + if form.DeleteBranchAfterMerge { + var headRepo *git.Repository + if ctx.Repo != nil && ctx.Repo.Repository != nil && pr.HeadRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil { + headRepo = ctx.Repo.GitRepo + } else { + headRepo, err = git.OpenRepository(pr.HeadRepo.RepoPath()) + if err != nil { + ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err) + return + } + defer headRepo.Close() + } + deleteBranch(ctx, pr, headRepo) + } + ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) } @@ -1170,19 +1186,35 @@ func CleanUpPullRequest(ctx *context.Context) { fullBranchName := pr.HeadRepo.Owner.Name + "/" + pr.HeadBranch - gitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath()) - if err != nil { - ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err) - return + var gitBaseRepo *git.Repository + + // Assume that the base repo is the current context (almost certainly) + if ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.BaseRepoID && ctx.Repo.GitRepo != nil { + gitBaseRepo = ctx.Repo.GitRepo + } else { + // If not just open it + gitBaseRepo, err = git.OpenRepository(pr.BaseRepo.RepoPath()) + if err != nil { + ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.BaseRepo.RepoPath()), err) + return + } + defer gitBaseRepo.Close() } - defer gitRepo.Close() - gitBaseRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath()) - if err != nil { - ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.BaseRepo.RepoPath()), err) - return + // Now assume that the head repo is the same as the base repo (reasonable chance) + gitRepo := gitBaseRepo + // But if not: is it the same as the context? + if pr.BaseRepoID != pr.HeadRepoID && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.HeadRepoID && ctx.Repo.GitRepo != nil { + gitRepo = ctx.Repo.GitRepo + } else if pr.BaseRepoID != pr.HeadRepoID { + // Otherwise just load it up + gitRepo, err = git.OpenRepository(pr.HeadRepo.RepoPath()) + if err != nil { + ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err) + return + } + defer gitRepo.Close() } - defer gitBaseRepo.Close() defer func() { ctx.JSON(http.StatusOK, map[string]interface{}{ @@ -1208,6 +1240,11 @@ func CleanUpPullRequest(ctx *context.Context) { return } + deleteBranch(ctx, pr, gitRepo) +} + +func deleteBranch(ctx *context.Context, pr *models.PullRequest, gitRepo *git.Repository) { + fullBranchName := pr.HeadRepo.Owner.Name + "/" + pr.HeadBranch if err := repo_service.DeleteBranch(ctx.User, pr.HeadRepo, gitRepo, pr.HeadBranch); err != nil { switch { case git.IsErrBranchNotExist(err): @@ -1223,7 +1260,7 @@ func CleanUpPullRequest(ctx *context.Context) { return } - if err := models.AddDeletePRBranchComment(ctx.User, pr.BaseRepo, issue.ID, pr.HeadBranch); err != nil { + if err := models.AddDeletePRBranchComment(ctx.User, pr.BaseRepo, pr.IssueID, pr.HeadBranch); err != nil { // Do not fail here as branch has already been deleted log.Error("DeleteBranch: %v", err) } diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index 5e8c2c5276251..0a84f15bf0bff 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -416,14 +416,15 @@ func SettingsPost(ctx *context.Context) { RepoID: repo.ID, Type: models.UnitTypePullRequests, Config: &models.PullRequestsConfig{ - IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, - AllowMerge: form.PullsAllowMerge, - AllowRebase: form.PullsAllowRebase, - AllowRebaseMerge: form.PullsAllowRebaseMerge, - AllowSquash: form.PullsAllowSquash, - AllowManualMerge: form.PullsAllowManualMerge, - AutodetectManualMerge: form.EnableAutodetectManualMerge, - DefaultMergeStyle: models.MergeStyle(form.PullsDefaultMergeStyle), + IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, + AllowMerge: form.PullsAllowMerge, + AllowRebase: form.PullsAllowRebase, + AllowRebaseMerge: form.PullsAllowRebaseMerge, + AllowSquash: form.PullsAllowSquash, + AllowManualMerge: form.PullsAllowManualMerge, + AutodetectManualMerge: form.EnableAutodetectManualMerge, + DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, + DefaultMergeStyle: models.MergeStyle(form.PullsDefaultMergeStyle), }, }) } else if !models.UnitTypePullRequests.UnitGlobalDisabled() { diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 71a83a8be36e2..7c79c4dc21eb0 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -151,6 +151,7 @@ type RepoSettingForm struct { PullsAllowManualMerge bool PullsDefaultMergeStyle string EnableAutodetectManualMerge bool + DefaultDeleteBranchAfterMerge bool EnableTimetracker bool AllowOnlyContributorsToTrackTime bool EnableIssueDependencies bool @@ -551,11 +552,12 @@ func (f *InitializeLabelsForm) Validate(req *http.Request, errs binding.Errors) type MergePullRequestForm struct { // required: true // enum: merge,rebase,rebase-merge,squash,manually-merged - Do string `binding:"Required;In(merge,rebase,rebase-merge,squash,manually-merged)"` - MergeTitleField string - MergeMessageField string - MergeCommitID string // only used for manually-merged - ForceMerge *bool `json:"force_merge,omitempty"` + Do string `binding:"Required;In(merge,rebase,rebase-merge,squash,manually-merged)"` + MergeTitleField string + MergeMessageField string + MergeCommitID string // only used for manually-merged + ForceMerge *bool `json:"force_merge,omitempty"` + DeleteBranchAfterMerge bool `json:"delete_branch_after_merge,omitempty"` } // Validate validates the fields diff --git a/services/pull/pull.go b/services/pull/pull.go index db216ddbf4556..6b3acd2004254 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -303,7 +303,11 @@ func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSy for _, pr := range prs { divergence, err := GetDiverging(pr) if err != nil { - log.Error("GetDiverging: %v", err) + if models.IsErrBranchDoesNotExist(err) && !git.IsBranchExist(pr.HeadRepo.RepoPath(), pr.HeadBranch) { + log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch) + } else { + log.Error("GetDiverging: %v", err) + } } else { err = pr.UpdateCommitDivergence(divergence.Ahead, divergence.Behind) if err != nil { diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index 45cd10b65bbe4..19b488790a0ce 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -141,10 +141,15 @@ func createTemporaryRepo(pr *models.PullRequest) (string, error) { trackingBranch := "tracking" // Fetch head branch if err := git.NewCommand("fetch", "--no-tags", remoteRepoName, git.BranchPrefix+pr.HeadBranch+":"+trackingBranch).RunInDirPipeline(tmpBasePath, &outbuf, &errbuf); err != nil { - log.Error("Unable to fetch head_repo head branch [%s:%s -> tracking in %s]: %v:\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, tmpBasePath, err, outbuf.String(), errbuf.String()) if err := models.RemoveTemporaryPath(tmpBasePath); err != nil { log.Error("CreateTempRepo: RemoveTemporaryPath: %s", err) } + if !git.IsBranchExist(pr.HeadRepo.RepoPath(), pr.HeadBranch) { + return "", models.ErrBranchDoesNotExist{ + BranchName: pr.HeadBranch, + } + } + log.Error("Unable to fetch head_repo head branch [%s:%s -> tracking in %s]: %v:\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, tmpBasePath, err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("Unable to fetch head_repo head branch [%s:%s -> tracking in tmpBasePath]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, err, outbuf.String(), errbuf.String()) } outbuf.Reset() diff --git a/services/pull/update.go b/services/pull/update.go index f4f7859a49ec1..f35e47cbf820a 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -88,7 +88,9 @@ func GetDiverging(pr *models.PullRequest) (*git.DivergeObject, error) { tmpRepo, err := createTemporaryRepo(pr) if err != nil { - log.Error("CreateTemporaryPath: %v", err) + if !models.IsErrBranchDoesNotExist(err) { + log.Error("CreateTemporaryRepo: %v", err) + } return nil, err } defer func() { diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl index 3bdec4becb02e..fcb3597ae866a 100644 --- a/templates/repo/issue/view_content/pull.tmpl +++ b/templates/repo/issue/view_content/pull.tmpl @@ -315,6 +315,12 @@ + {{if .IsPullBranchDeletable}} +
+ + +
+ {{end}} {{end}} @@ -328,6 +334,12 @@ + {{if .IsPullBranchDeletable}} +
+ + +
+ {{end}} {{end}} @@ -347,6 +359,12 @@ + {{if .IsPullBranchDeletable}} +
+ + +
+ {{end}} {{end}} @@ -366,6 +384,12 @@ + {{if .IsPullBranchDeletable}} +
+ + +
+ {{end}} {{end}} @@ -382,6 +406,12 @@ + {{if .IsPullBranchDeletable}} +
+ + +
+ {{end}} {{end}} diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index eb76a3b720064..054df7c36899c 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -445,6 +445,12 @@ +
+
+ + +
+

{{.i18n.Tr "repo.settings.default_merge_style_desc"}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 669e3552cc5de..de61b9dd29377 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -14058,6 +14058,11 @@ "type": "string", "x-go-name": "DefaultBranch" }, + "default_delete_branch_after_merge": { + "description": "set to `true` to delete pr branch after merge by default", + "type": "boolean", + "x-go-name": "DefaultDeleteBranchAfterMerge" + }, "default_merge_style": { "description": "set to a merge style to be used by this repository: \"merge\", \"rebase\", \"rebase-merge\", or \"squash\". `has_pull_requests` must be `true`.", "type": "string", @@ -15137,6 +15142,10 @@ "MergeTitleField": { "type": "string" }, + "delete_branch_after_merge": { + "type": "boolean", + "x-go-name": "DeleteBranchAfterMerge" + }, "force_merge": { "type": "boolean", "x-go-name": "ForceMerge" From 2614309a58635bb0f49cfeea086a62b6fd7711e1 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Tue, 13 Jul 2021 00:07:51 +0000 Subject: [PATCH 2/3] [skip ci] Updated translations via Crowdin --- options/locale/locale_de-DE.ini | 5 +++++ options/locale/locale_es-ES.ini | 34 ++++++++++++++++++++++++++++++--- options/locale/locale_fr-FR.ini | 2 ++ options/locale/locale_ja-JP.ini | 4 ++++ options/locale/locale_pt-PT.ini | 7 +++++++ options/locale/locale_zh-TW.ini | 4 ++++ 6 files changed, 53 insertions(+), 3 deletions(-) diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index a2c07213f6a35..e83f65d9cd631 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -326,6 +326,8 @@ hi_user_x=Hallo %s, activate_account=Bitte aktiviere dein Konto activate_account.title=%s, bitte aktiviere dein Konto +activate_account.text_1=Hallo %[1]s, danke für deine Registrierung bei %[2]! +activate_account.text_2=Bitte klicke innerhalb von %s auf folgenden Link, um dein Konto zu aktivieren: activate_email=Bestätige deine E-Mail-Adresse activate_email.title=%s, bitte verifiziere deine E-Mail-Adresse @@ -1813,6 +1815,7 @@ settings.event_pull_request_review_desc=Pull-Request genehmigt, abgelehnt oder K settings.event_pull_request_sync=Pull-Request synchronisiert settings.event_pull_request_sync_desc=Pull-Request synchronisiert. settings.branch_filter=Branch-Filter +settings.branch_filter_desc=Whitelist für Branches für Push-, Erzeugungs- und Löschevents, als glob Pattern beschrieben. Es werden Events für alle Branches gemeldet, falls das Pattern * ist, oder falls es leer ist. Siehe die github.com/gobwas/glob Dokumentation für die Syntax (Englisch). Beispiele: master, {master,release*}. settings.active=Aktiv settings.active_helper=Informationen über ausgelöste Ereignisse werden an diese Webhook-URL gesendet. settings.add_hook_success=Webhook wurde hinzugefügt. @@ -1882,6 +1885,7 @@ settings.dismiss_stale_approvals_desc=Wenn neue Commits gepusht werden, die den settings.require_signed_commits=Signierte Commits erforderlich settings.require_signed_commits_desc=Pushes auf diesen Branch ablehnen, wenn Commits nicht signiert oder nicht überprüfbar sind. settings.protect_protected_file_patterns=Geschützte Dateimuster (durch Semikolon getrennt '\;'): +settings.protect_protected_file_patterns_desc=Geschützte Dateien, die nicht einmal geändert werden können, wenn der Benutzer die Rechte hat, Dateien in diesem Branch hinzuzufügen, zu bearbeiten, oder zu löschen. Verschiedene Pattern können per Semicolon (';') getrennt werden. Siehe die github.com/gobwas/glob Dokumentation für die Pattern Syntax (Englisch). Beispiele: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Schutz aktivieren settings.delete_protected_branch=Schutz deaktivieren settings.update_protect_branch_success=Branch-Schutz für den Branch „%s“ wurde geändert. @@ -1909,6 +1913,7 @@ settings.tags.protection.allowed.teams=Erlaubte Teams settings.tags.protection.allowed.noone=Niemand settings.tags.protection.create=Tag schützen settings.tags.protection.none=Es gibt keine geschützten Tags. +settings.tags.protection.pattern.description=Du kannst einen einzigen Namen oder ein globales Schema oder einen regulären Ausdruck verwenden, um mehrere Tags zu schützen. Mehr dazu im geschützte Tags Guide (Englisch). settings.bot_token=Bot-Token settings.chat_id=Chat-ID settings.matrix.homeserver_url=Homeserver-URL diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index bddad39b7113f..180c7b0c82428 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -83,6 +83,7 @@ add=Añadir add_all=Añadir todo remove=Eliminar remove_all=Eliminar todos +edit=Editar write=Escribir preview=Vista previa @@ -99,6 +100,8 @@ never=Nunca [error] occurred=Se ha producido un error report_message=Si estás seguro de que este es un error de Gitea, por favor busca un problema en GitHub y abre un nuevo problema si es necesario. +missing_csrf=Solicitud incorrecta: sin token CSRF +invalid_csrf=Solicitud incorrecta: el token CSRF no es válido [startpage] app_desc=Un servicio de Git autoalojado y sin complicaciones @@ -272,8 +275,8 @@ account_activated=La cuenta ha sido activada prohibit_login=Ingreso prohibido prohibit_login_desc=Su cuenta tiene prohibido ingresar al sistema. Por favor contacte con el administrador del sistema. resent_limit_prompt=Ya ha solicitado recientemente un correo de activación. Por favor, espere 3 minutos y vuelva a intentarlo. -has_unconfirmed_mail=Hola %s, tu correo electrónico (%s) no está confirmado. Si no has recibido un correo de confirmación o necesitas que lo enviemos de nuevo, por favor, haz click en el siguiente botón. -resend_mail=Haz click aquí para reenviar tu correo electrónico de activación +has_unconfirmed_mail=Hola %s, su correo electrónico (%s) no está confirmado. Si no ha recibido un correo de confirmación o necesita que lo enviemos de nuevo, por favor, haga click en el siguiente botón. +resend_mail=Haga click aquí para reenviar su correo electrónico de activación email_not_associate=Esta dirección de correo electrónico no esta asociada a ninguna cuenta. send_reset_mail=Enviar correo de recuperación de cuenta reset_password=Recuperación de cuenta @@ -302,6 +305,8 @@ openid_connect_desc=La URI OpenID elegida es desconocida. Asóciela a una nueva openid_register_title=Crear una nueva cuenta openid_register_desc=La URI OpenID elegida es desconocida. Asóciela a una nueva cuenta aquí. openid_signin_desc=Introduzca su URI OpenID. Por ejemplo: https://anne.me, bob.openid.org.cn o gnusocial.net/carry. +disable_forgot_password_mail=La recuperación de cuentas está desactivada porque no hay correo electrónico configurado. Por favor, contacte con el administrador del sitio. +disable_forgot_password_mail_admin=La recuperación de cuentas solo está disponible cuando se configura el correo electrónico configurado. Por favor, configure el correo electrónico para permitir la recuperación de cuentas. email_domain_blacklisted=No puede registrarse con su correo electrónico. authorize_application=Autorizar aplicación authorize_redirect_notice=Será redirigido a %s si autoriza esta aplicación. @@ -321,6 +326,8 @@ hi_user_x=Hola %s, activate_account=Por favor, active su cuenta activate_account.title=%s, por favor activa tu cuenta +activate_account.text_1=¡Hola %[1]s, gracias por registrarse en %[2]! +activate_account.text_2=Por favor, haga clic en el siguiente enlace para activar su cuenta dentro de %s: activate_email=Verifique su correo electrónico activate_email.title=%s, por favor verifica tu dirección de correo electrónico @@ -719,6 +726,13 @@ email_notifications.onmention=Enviar correo sólo al ser mencionado email_notifications.disable=Deshabilitar las notificaciones por correo electrónico email_notifications.submit=Establecer preferencias de correo electrónico +visibility=Visibilidad del usuario +visibility.public=Público +visibility.public_tooltip=Visible para todos los usuarios +visibility.limited=Limitado +visibility.limited_tooltip=Visible sólo para usuarios conectados +visibility.private=Privado +visibility.private_tooltip=Sólo visible para los miembros de la organización [repo] new_repo_helper=Un repositorio contiene todos los archivos del proyecto, incluyendo el historial de revisiones. ¿Ya lo tiene en otro lugar? Migrar repositorio. @@ -797,6 +811,7 @@ delete_preexisting_label=Eliminar delete_preexisting=Eliminar archivos preexistentes delete_preexisting_content=Eliminar archivos en %s delete_preexisting_success=Eliminó archivos no adoptados en %s +blame_prior=Ver la culpa antes de este cambio transfer.accept=Aceptar transferencia transfer.accept_desc=Transferir a "%s" @@ -990,7 +1005,7 @@ editor.file_is_a_symlink='%s' es un enlace simbólico. Los enlaces simbólicos n editor.filename_is_a_directory=Nombre de archivo '%s' ya se utiliza como un nombre de directorio en este repositorio. editor.file_editing_no_longer_exists=El archivo que está editando, '%s', ya no existe en este repositorio. editor.file_deleting_no_longer_exists=El archivo que se está eliminando, '%s', ya no existe en este repositorio. -editor.file_changed_while_editing=Desde que comenzó a editar, el contenido del archivo ha sido cambiado. Clic aquí para ver qué ha cambiado o presione confirmar de nuevo para sobrescribir los cambios. +editor.file_changed_while_editing=Desde que comenzó a editar, el contenido del archivo ha sido cambiado. Haga clic aquí para ver qué ha cambiado o presione confirmar de nuevo para sobrescribir los cambios. editor.file_already_exists=Ya existe un archivo con nombre '%s' en este repositorio. editor.commit_empty_file_header=Commit un archivo vacío editor.commit_empty_file_text=El archivo que estás tratando de commit está vacío. ¿Proceder? @@ -1870,6 +1885,7 @@ settings.dismiss_stale_approvals_desc=Cuando los nuevos commits que cambien el c settings.require_signed_commits=Requiere commits firmados settings.require_signed_commits_desc=Rechazar push en esta rama si los commits no están firmados o no son verificables. settings.protect_protected_file_patterns=Patrones de archivos protegidos (separados con punto y coma '\;'): +settings.protect_protected_file_patterns_desc=Archivos protegidos que no están permitidos a ser cambiados directamente incluso si el usuario tiene permiso para agregar, editar o borrar archivos en esta rama. Múltiples patrones pueden separarse usando punto y coma ('\;'). Vea la documentación de github.com/gobwas/glob para la sintaxis de patrones. Ejemplos: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Activar protección settings.delete_protected_branch=Desactivar protección settings.update_protect_branch_success=La protección de la rama '%s' ha sido actualizada. @@ -1888,6 +1904,16 @@ settings.choose_branch=Elija una rama… settings.no_protected_branch=No hay ramas protegidas. settings.edit_protected_branch=Editar settings.protected_branch_required_approvals_min=Las aprobaciones necesarias no pueden ser negativas. +settings.tags=Etiquetas +settings.tags.protection=Protección de etiquetas +settings.tags.protection.pattern=Patrón de etiquetas +settings.tags.protection.allowed=Permitido +settings.tags.protection.allowed.users=Usuarios permitidos +settings.tags.protection.allowed.teams=Equipos permitidos +settings.tags.protection.allowed.noone=Ningún +settings.tags.protection.create=Proteger Etiqueta +settings.tags.protection.none=No hay etiquetas protegidas. +settings.tags.protection.pattern.description=Puede usar un solo nombre o un patrón de glob o expresión regular para que coincida con varias etiquetas. Lea más en la guía de etiquetas protegida. settings.bot_token=Token del Bot settings.chat_id=ID Chat settings.matrix.homeserver_url=URL de Homeserver @@ -1901,6 +1927,7 @@ settings.archive.success=El repositorio ha sido archivado exitosamente. settings.archive.error=Ha ocurrido un error al intentar archivar el repositorio. Vea el registro para más detalles. settings.archive.error_ismirror=No puede archivar un repositorio replicado. settings.archive.branchsettings_unavailable=Los ajustes de rama no están disponibles si el repositorio está archivado. +settings.archive.tagsettings_unavailable=Los ajustes de las etiquetas no están disponibles si el repositorio está archivado. settings.unarchive.button=Desarchivar Repositorio settings.unarchive.header=Desarchivar este Repositorio settings.unarchive.text=Des-archivar el repositorio restaurará su capacidad de recibir commits y pushes, así como nuevas incidencias y pull-requests. @@ -2015,6 +2042,7 @@ release.deletion_tag_desc=Eliminará esta etiqueta del repositorio. El contenido release.deletion_tag_success=La etiqueta ha sido eliminada. release.tag_name_already_exist=Ya existe uno lanzamiento con esta etiqueta. release.tag_name_invalid=El nombre de la etiqueta no es válido. +release.tag_name_protected=El nombre de la etiqueta está protegido. release.tag_already_exist=Este nombre de etiqueta ya existe. release.downloads=Descargas release.download_count=Descargas: %s diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index b9ce6d87c60e1..c238fc0bfda07 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -1968,8 +1968,10 @@ release.delete_release=Supprimer cette version release.delete_tag=Supprimer le tag release.deletion=Supprimer cette version release.deletion_success=Cette livraison a été supprimée. +release.deletion_tag_success=L'étiquette a été supprimée. release.tag_name_already_exist=Une version avec ce nom de balise existe déjà. release.tag_name_invalid=Le nom de balise est invalide. +release.tag_already_exist=Ce nom d'étiquette existe déjà. release.downloads=Téléchargements release.download_count=Télécharger: %s diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 9ad2d48a5a54c..6a756f6a9c77a 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -100,6 +100,8 @@ never=無し [error] occurred=エラーが発生しました report_message=Giteaのバグが疑われる場合は、GitHubでIssueを検索して、見つからなければ新しいIssueを作成してください。 +missing_csrf=不正なリクエスト: CSRFトークンが不明です +invalid_csrf=不正なリクエスト: CSRFトークンが無効です [startpage] app_desc=自分で立てる、超簡単 Git サービス @@ -324,6 +326,8 @@ hi_user_x=こんにちは、%s さん。 activate_account=あなたのアカウントをアクティベートしてください。 activate_account.title=%s さん、アカウントをアクティベートしてください +activate_account.text_1=こんにちは、%[1]s さん。 %[2]s へのご登録ありがとうございます! +activate_account.text_2=あなたのアカウントを有効化するため、%s以内に次のリンクをクリックしてください: activate_email=メール アドレスを確認します activate_email.title=%s さん、メールアドレス確認をお願いします diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 7ed6db37e6a06..62207342e5aba 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -100,6 +100,8 @@ never=Nunca [error] occurred=Ocorreu um erro report_message=Se tiver certeza de que se trata de um erro do Gitea, por favor, procure a questão no GitHub e abra uma nova questão, se necessário. +missing_csrf=Pedido inválido: não há código CSRF +invalid_csrf=Pedido inválido: código CSRF inválido [startpage] app_desc=Um serviço Git auto-hospedado e fácil de usar @@ -318,6 +320,7 @@ password_pwned=A senha utilizada está numa Como é que eu faço um espelho de outro repositório? settings.mirror_settings.mirrored_repository=Repositório espelhado settings.mirror_settings.direction=Sentido +settings.mirror_settings.direction.pull=Puxada +settings.mirror_settings.direction.push=Envio settings.mirror_settings.last_update=Última modificação settings.mirror_settings.push_mirror.none=Não foram configurados quaisquer espelhos de envio settings.mirror_settings.push_mirror.remote_url=URL do repositório remoto Git diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 7075d5fa6aee5..ba36394e46956 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -100,6 +100,8 @@ never=從來沒有 [error] occurred=發生錯誤 report_message=如果你確定這是一個 Gitea 的 bug,請去 GitHub 搜尋相關的問題,如果有需要你也可以開一個新的問題 +missing_csrf=Bad Request: no CSRF token present +invalid_csrf=Bad Request: Invalid CSRF token [startpage] app_desc=一套極易架設的 Git 服務 @@ -324,6 +326,8 @@ hi_user_x=%s 您好, activate_account=請啟用您的帳戶 activate_account.title=%s,請啟用您的帳戶 +activate_account.text_1=%[1]s 您好,感謝您註冊 %[2]s! +activate_account.text_2=請在 %s內點擊下列連結以啟用您的帳戶: activate_email=請驗證您的電子信箱 activate_email.title=%s,請驗證您的電子信箱 From 4ce32c9e93591f2449a388201c323ca193f59c07 Mon Sep 17 00:00:00 2001 From: Jimmy Praet Date: Tue, 13 Jul 2021 03:13:52 +0200 Subject: [PATCH 3/3] Detect encoding changes while parsing diff (#16330) * Detect encoding changes while parsing diff --- services/gitdiff/gitdiff.go | 50 +++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index f8f0fd7e3b90b..d50e41eb40279 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -32,6 +32,7 @@ import ( "github.com/sergi/go-diff/diffmatchpatch" stdcharset "golang.org/x/net/html/charset" + "golang.org/x/text/encoding" "golang.org/x/text/transform" ) @@ -883,35 +884,46 @@ parsingLoop: } - // FIXME: There are numerous issues with this: + // TODO: There are numerous issues with this: // - we might want to consider detecting encoding while parsing but... // - we're likely to fail to get the correct encoding here anyway as we won't have enough information - // - and this doesn't really account for changes in encoding - var buf bytes.Buffer + var diffLineTypeBuffers = make(map[DiffLineType]*bytes.Buffer, 3) + var diffLineTypeDecoders = make(map[DiffLineType]*encoding.Decoder, 3) + diffLineTypeBuffers[DiffLinePlain] = new(bytes.Buffer) + diffLineTypeBuffers[DiffLineAdd] = new(bytes.Buffer) + diffLineTypeBuffers[DiffLineDel] = new(bytes.Buffer) for _, f := range diff.Files { - buf.Reset() + for _, buffer := range diffLineTypeBuffers { + buffer.Reset() + } for _, sec := range f.Sections { for _, l := range sec.Lines { if l.Type == DiffLineSection { continue } - buf.WriteString(l.Content[1:]) - buf.WriteString("\n") + diffLineTypeBuffers[l.Type].WriteString(l.Content[1:]) + diffLineTypeBuffers[l.Type].WriteString("\n") } } - charsetLabel, err := charset.DetectEncoding(buf.Bytes()) - if charsetLabel != "UTF-8" && err == nil { - encoding, _ := stdcharset.Lookup(charsetLabel) - if encoding != nil { - d := encoding.NewDecoder() - for _, sec := range f.Sections { - for _, l := range sec.Lines { - if l.Type == DiffLineSection { - continue - } - if c, _, err := transform.String(d, l.Content[1:]); err == nil { - l.Content = l.Content[0:1] + c - } + for lineType, buffer := range diffLineTypeBuffers { + diffLineTypeDecoders[lineType] = nil + if buffer.Len() == 0 { + continue + } + charsetLabel, err := charset.DetectEncoding(buffer.Bytes()) + if charsetLabel != "UTF-8" && err == nil { + encoding, _ := stdcharset.Lookup(charsetLabel) + if encoding != nil { + diffLineTypeDecoders[lineType] = encoding.NewDecoder() + } + } + } + for _, sec := range f.Sections { + for _, l := range sec.Lines { + decoder := diffLineTypeDecoders[l.Type] + if decoder != nil { + if c, _, err := transform.String(decoder, l.Content[1:]); err == nil { + l.Content = l.Content[0:1] + c } } }