From e754f24712da31565d49e2e3ebe7e319967eb96d Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 3 Jun 2024 02:09:51 +0200 Subject: [PATCH 01/19] Fix overflow in issue card (#31203) Before: Screenshot 2024-06-01 at 01 31 26 After: Screenshot 2024-06-01 at 01 31 32 (cherry picked from commit 9b05bfb173795ba2a2267402d2669715cd4a64e4) --- templates/repo/issue/card.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/issue/card.tmpl b/templates/repo/issue/card.tmpl index 526f6dd5db..4c22c28329 100644 --- a/templates/repo/issue/card.tmpl +++ b/templates/repo/issue/card.tmpl @@ -14,7 +14,7 @@
{{template "shared/issueicon" .}}
- {{.Title | RenderEmoji ctx | RenderCodeBlock}} + {{.Title | RenderEmoji ctx | RenderCodeBlock}} {{if and $.isPinnedIssueCard $.Page.IsRepoAdmin}} {{svg "octicon-x" 16}} From 12528ae72c1ec959818004673fdfaad4c44b66d1 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Mon, 3 Jun 2024 00:27:17 +0000 Subject: [PATCH 02/19] [skip ci] Updated licenses and gitignores (cherry picked from commit c6854202be9eb8358f046871d4eb0e4fd74639ff) --- options/gitignore/Alteryx | 44 ++++++++++++++++++++++++++++++++++ options/gitignore/Archives | 2 ++ options/gitignore/Ballerina | 11 +++++++++ options/gitignore/CMake | 1 + options/gitignore/Delphi | 12 ++++++++++ options/gitignore/GitHubPages | 18 ++++++++++++++ options/gitignore/Go | 3 +++ options/gitignore/Objective-C | 17 ------------- options/gitignore/Rust | 7 ++++++ options/gitignore/Swift | 28 ---------------------- options/gitignore/TeX | 5 ++++ options/gitignore/Terraform | 6 +++++ options/gitignore/UiPath | 11 +++++++++ options/gitignore/UnrealEngine | 4 ++-- options/gitignore/Xcode | 4 ---- 15 files changed, 122 insertions(+), 51 deletions(-) create mode 100644 options/gitignore/Alteryx create mode 100644 options/gitignore/Ballerina create mode 100644 options/gitignore/GitHubPages create mode 100644 options/gitignore/UiPath diff --git a/options/gitignore/Alteryx b/options/gitignore/Alteryx new file mode 100644 index 0000000000..a8e1341ffe --- /dev/null +++ b/options/gitignore/Alteryx @@ -0,0 +1,44 @@ +# gitignore template for Alteryx Designer +# website: https://www.alteryx.com/ +# website: https://help.alteryx.com/current/designer/alteryx-file-types + +# Alteryx Data Files +*.yxdb +*.cydb +*.cyidx +*.rptx +*.vvf +*.aws + +# Alteryx Special Files +*.yxwv +*.yxft +*.yxbe +*.bak +*.pcxml +*.log +*.bin +*.yxlang +CASS.ini + +# Alteryx License Files +*.yxlc +*.slc +*.cylc +*.alc +*.gzlc + +## gitignore reference sites +# https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository#Ignoring-Files +# https://git-scm.com/docs/gitignore +# https://help.github.com/articles/ignoring-files/ + +## Useful knowledge from stackoverflow +# Even if you haven't tracked the files so far, git seems to be able to "know" about them even after you add them to .gitignore. +# WARNING: First commit your current changes, or you will lose them. +# Then run the following commands from the top folder of your git repo: +# git rm -r --cached . +# git add . +# git commit -m "fixed untracked files" + +# author: Kacper Ksieski \ No newline at end of file diff --git a/options/gitignore/Archives b/options/gitignore/Archives index 4ed9ab8350..8c92521b4c 100644 --- a/options/gitignore/Archives +++ b/options/gitignore/Archives @@ -14,6 +14,8 @@ *.lzma *.cab *.xar +*.zst +*.tzst # Packing-only formats *.iso diff --git a/options/gitignore/Ballerina b/options/gitignore/Ballerina new file mode 100644 index 0000000000..030a350fbf --- /dev/null +++ b/options/gitignore/Ballerina @@ -0,0 +1,11 @@ +# generated files +target/ +generated/ + +# dependencies +Dependencies.toml + +# config files +Config.toml +# the config files used for testing, Uncomment the following line if you want to commit the test config files +#!**/tests/Config.toml diff --git a/options/gitignore/CMake b/options/gitignore/CMake index 46f42f8f3c..11c76431e1 100644 --- a/options/gitignore/CMake +++ b/options/gitignore/CMake @@ -9,3 +9,4 @@ install_manifest.txt compile_commands.json CTestTestfile.cmake _deps +CMakeUserPresets.json diff --git a/options/gitignore/Delphi b/options/gitignore/Delphi index 9532800ba2..8df99b676b 100644 --- a/options/gitignore/Delphi +++ b/options/gitignore/Delphi @@ -26,6 +26,18 @@ #*.obj # +# Default Delphi compiler directories +# Content of this directories are generated with each Compile/Construct of a project. +# Most of the time, files here have not there place in a code repository. +#Win32/ +#Win64/ +#OSX64/ +#OSXARM64/ +#Android/ +#Android64/ +#iOSDevice64/ +#Linux64/ + # Delphi compiler-generated binaries (safe to delete) *.exe *.dll diff --git a/options/gitignore/GitHubPages b/options/gitignore/GitHubPages new file mode 100644 index 0000000000..493e69ba39 --- /dev/null +++ b/options/gitignore/GitHubPages @@ -0,0 +1,18 @@ +# This .gitignore is appropriate for repositories deployed to GitHub Pages and using +# a Gemfile as specified at https://github.com/github/pages-gem#conventional + +# Basic Jekyll gitignores (synchronize to Jekyll.gitignore) +_site/ +.sass-cache/ +.jekyll-cache/ +.jekyll-metadata + +# Additional Ruby/bundler ignore for when you run: bundle install +/vendor + +# Specific ignore for GitHub Pages +# GitHub Pages will always use its own deployed version of pages-gem +# This means GitHub Pages will NOT use your Gemfile.lock and therefore it is +# counterproductive to check this file into the repository. +# Details at https://github.com/github/pages-gem/issues/768 +Gemfile.lock diff --git a/options/gitignore/Go b/options/gitignore/Go index 6f6f5e6adc..6f72f89261 100644 --- a/options/gitignore/Go +++ b/options/gitignore/Go @@ -20,3 +20,6 @@ # Go workspace file go.work go.work.sum + +# env file +.env diff --git a/options/gitignore/Objective-C b/options/gitignore/Objective-C index 7801c93000..9b8cd0706f 100644 --- a/options/gitignore/Objective-C +++ b/options/gitignore/Objective-C @@ -5,23 +5,6 @@ ## User settings xcuserdata/ -## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) -*.xcscmblueprint -*.xccheckout - -## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) -build/ -DerivedData/ -*.moved-aside -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 - ## Obj-C/Swift specific *.hmap diff --git a/options/gitignore/Rust b/options/gitignore/Rust index 6985cf1bd0..d01bd1a990 100644 --- a/options/gitignore/Rust +++ b/options/gitignore/Rust @@ -12,3 +12,10 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/options/gitignore/Swift b/options/gitignore/Swift index 330d1674f3..52fe2f7102 100644 --- a/options/gitignore/Swift +++ b/options/gitignore/Swift @@ -5,23 +5,6 @@ ## User settings xcuserdata/ -## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) -*.xcscmblueprint -*.xccheckout - -## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) -build/ -DerivedData/ -*.moved-aside -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 - ## Obj-C/Swift specific *.hmap @@ -66,10 +49,6 @@ playground.xcworkspace Carthage/Build/ -# Accio dependency management -Dependencies/ -.accio/ - # fastlane # # It is recommended to not store the screenshots in the git repo. @@ -81,10 +60,3 @@ fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output - -# Code Injection -# -# After new code Injection tools there's a generated folder /iOSInjectionProject -# https://github.com/johnno1962/injectionforxcode - -iOSInjectionProject/ diff --git a/options/gitignore/TeX b/options/gitignore/TeX index e964244133..a1f5212090 100644 --- a/options/gitignore/TeX +++ b/options/gitignore/TeX @@ -39,6 +39,8 @@ *.synctex.gz *.synctex.gz(busy) *.pdfsync +*.rubbercache +rubber.cache ## Build tool directories for auxiliary files # latexrun @@ -138,6 +140,9 @@ acs-*.bib *.trc *.xref +# hypdoc +*.hd + # hyperref *.brf diff --git a/options/gitignore/Terraform b/options/gitignore/Terraform index 9b8a46e692..15073ca88b 100644 --- a/options/gitignore/Terraform +++ b/options/gitignore/Terraform @@ -23,6 +23,9 @@ override.tf.json *_override.tf *_override.tf.json +# Ignore transient lock info files created by terraform apply +.terraform.tfstate.lock.info + # Include override files you do wish to add to version control using negated pattern # !example_override.tf @@ -32,3 +35,6 @@ override.tf.json # Ignore CLI configuration files .terraformrc terraform.rc + +# Ignore hcl file +.terraform.lock.hcl diff --git a/options/gitignore/UiPath b/options/gitignore/UiPath new file mode 100644 index 0000000000..f0c2267b89 --- /dev/null +++ b/options/gitignore/UiPath @@ -0,0 +1,11 @@ +# gitignore template for RPA development using UiPath Studio +# website: https://www.uipath.com/product/studio +# +# Recommended: n/a + +# Ignore folders that could cause issues if accidentally tracked +**/.local/** +**/.settings/** +**/.objects/** +**/.tmh/** +**/*.log diff --git a/options/gitignore/UnrealEngine b/options/gitignore/UnrealEngine index 6582eaf9a1..6e0d95fb31 100644 --- a/options/gitignore/UnrealEngine +++ b/options/gitignore/UnrealEngine @@ -47,7 +47,7 @@ SourceArt/**/*.tga # Binary Files Binaries/* -Plugins/*/Binaries/* +Plugins/**/Binaries/* # Builds Build/* @@ -68,7 +68,7 @@ Saved/* # Compiled source files for the engine to use Intermediate/* -Plugins/*/Intermediate/* +Plugins/**/Intermediate/* # Cache files for the editor to use DerivedDataCache/* diff --git a/options/gitignore/Xcode b/options/gitignore/Xcode index f87d2f2e74..5073505e08 100644 --- a/options/gitignore/Xcode +++ b/options/gitignore/Xcode @@ -1,6 +1,2 @@ ## User settings xcuserdata/ - -## Xcode 8 and earlier -*.xcscmblueprint -*.xccheckout From 3befabead064aefe1352265edd14d9ec4cd819f4 Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Mon, 3 Jun 2024 19:41:29 +0900 Subject: [PATCH 03/19] Remove sqlite-viewer and using database client (#31223) sqlite-viewer can not edit sqlite. database client can connect to almost all common databases, which is very useful I think. Of cause, it can edit sqlite. https://marketplace.visualstudio.com/items?itemName=cweijan.vscode-database-client2 And for using sqlite, sqlite3 is required. So also added a new feature: https://github.com/warrenbuckley/codespace-features found from: https://containers.dev/features (cherry picked from commit fc641b3a28300e13c822140556eca8d00f2b5196) Conflicts: .devcontainer/devcontainer.json trivial context conflict because AZure blob support was not added --- .devcontainer/devcontainer.json | 8 +++++--- .gitpod.yml | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b232dfb8cc..236bc4722a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,7 +10,8 @@ "ghcr.io/devcontainers-contrib/features/poetry:2": {}, "ghcr.io/devcontainers/features/python:1": { "version": "3.12" - } + }, + "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} }, "customizations": { "vscode": { @@ -25,8 +26,9 @@ "Vue.volar", "ms-azuretools.vscode-docker", "vitest.explorer", - "qwtel.sqlite-viewer", - "GitHub.vscode-pull-request-github" + "cweijan.vscode-database-client2", + "GitHub.vscode-pull-request-github", + "Azurite.azurite" ] } }, diff --git a/.gitpod.yml b/.gitpod.yml index f573d55a76..8671edc47c 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -43,7 +43,7 @@ vscode: - Vue.volar - ms-azuretools.vscode-docker - vitest.explorer - - qwtel.sqlite-viewer + - cweijan.vscode-database-client2 - GitHub.vscode-pull-request-github ports: From 90753a2c11205887d2dc3ef50562936e7abc1022 Mon Sep 17 00:00:00 2001 From: Kemal Zebari <60799661+kemzeb@users.noreply.github.com> Date: Mon, 3 Jun 2024 06:40:48 -0700 Subject: [PATCH 04/19] Document possible action types for the user activity feed API (#31196) Resolves #31131. It uses the the go-swagger `enum` property to document the activity action types. (cherry picked from commit cb27c438a82fec9f2476f6058bc5dcda2617aab5) --- modules/structs/activity.go | 7 +++++-- templates/swagger/v1_json.tmpl | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/modules/structs/activity.go b/modules/structs/activity.go index 6d2ee56b08..ea27fbfd77 100644 --- a/modules/structs/activity.go +++ b/modules/structs/activity.go @@ -6,8 +6,11 @@ package structs import "time" type Activity struct { - ID int64 `json:"id"` - UserID int64 `json:"user_id"` // Receiver user + ID int64 `json:"id"` + UserID int64 `json:"user_id"` // Receiver user + // the type of action + // + // enum: create_repo,rename_repo,star_repo,watch_repo,commit_repo,create_issue,create_pull_request,transfer_repo,push_tag,comment_issue,merge_pull_request,close_issue,reopen_issue,close_pull_request,reopen_pull_request,delete_tag,delete_branch,mirror_sync_push,mirror_sync_create,mirror_sync_delete,approve_pull_request,reject_pull_request,comment_pull,publish_release,pull_review_dismissed,pull_request_ready_for_review,auto_merge_pull_request OpType string `json:"op_type"` ActUserID int64 `json:"act_user_id"` ActUser *User `json:"act_user"` diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 8e9ee8b5cd..f0a48495a9 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -18537,7 +18537,37 @@ "x-go-name": "IsPrivate" }, "op_type": { + "description": "the type of action", "type": "string", + "enum": [ + "create_repo", + "rename_repo", + "star_repo", + "watch_repo", + "commit_repo", + "create_issue", + "create_pull_request", + "transfer_repo", + "push_tag", + "comment_issue", + "merge_pull_request", + "close_issue", + "reopen_issue", + "close_pull_request", + "reopen_pull_request", + "delete_tag", + "delete_branch", + "mirror_sync_push", + "mirror_sync_create", + "mirror_sync_delete", + "approve_pull_request", + "reject_pull_request", + "comment_pull", + "publish_release", + "pull_review_dismissed", + "pull_request_ready_for_review", + "auto_merge_pull_request" + ], "x-go-name": "OpType" }, "ref_name": { From b62fa72cebf84b2ece9075549c5a748a251ac515 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 3 Jun 2024 19:21:45 +0200 Subject: [PATCH 05/19] Remove unnecessary inline style for tab-size (#31224) Move the rule to the parent node. `tab-size` is inherited so will work just as before. (cherry picked from commit 0f0db6a14fd10a493ba73f211e2e627c3884d114) --- routers/web/repo/issue_content_history.go | 2 +- web_src/css/repo.css | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go index c817d6aa96..31d2de6d53 100644 --- a/routers/web/repo/issue_content_history.go +++ b/routers/web/repo/issue_content_history.go @@ -158,7 +158,7 @@ func GetContentHistoryDetail(ctx *context.Context) { // use chroma to render the diff html diffHTMLBuf := bytes.Buffer{} - diffHTMLBuf.WriteString("
")
+	diffHTMLBuf.WriteString("
")
 	for _, it := range diff {
 		if it.Type == diffmatchpatch.DiffInsert {
 			diffHTMLBuf.WriteString("")
diff --git a/web_src/css/repo.css b/web_src/css/repo.css
index 066e81f63c..af27a66005 100644
--- a/web_src/css/repo.css
+++ b/web_src/css/repo.css
@@ -2489,6 +2489,7 @@ tbody.commit-list {
   min-height: 12em;
   max-height: calc(100vh - 10.5rem);
   overflow-y: auto;
+  tab-size: 4;
 }
 
 .comment-diff-data pre {

From 237d18974eec8e0152efc3cd9618229bd6255d6d Mon Sep 17 00:00:00 2001
From: silverwind 
Date: Mon, 3 Jun 2024 20:21:28 +0200
Subject: [PATCH 06/19] Move custom `tw-` helpers to tailwind plugin (#31184)

Move the previous custom `tw-` classes to be defined in a tailwind
plugin. I think it's cleaner that way and I also verified double-class
works as expected:

Screenshot 2024-05-30 at 19 06 24

(cherry picked from commit 8c68c5e436805848197d98313e9ee77e8d540a83)
---
 tailwind.config.js      | 23 +++++++++++++++++++++++
 web_src/css/helpers.css | 16 ----------------
 2 files changed, 23 insertions(+), 16 deletions(-)

diff --git a/tailwind.config.js b/tailwind.config.js
index 94dfdbced4..8f3e8c8251 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,6 +1,7 @@
 import {readFileSync} from 'node:fs';
 import {env} from 'node:process';
 import {parse} from 'postcss';
+import plugin from 'tailwindcss/plugin.js';
 
 const isProduction = env.NODE_ENV !== 'development';
 
@@ -98,4 +99,26 @@ export default {
       })),
     },
   },
+  plugins: [
+    plugin(({addUtilities}) => {
+      addUtilities({
+        // tw-hidden must win all other "display: xxx !important" classes to get the chance to "hide" an element.
+        // do not use:
+        // * "[hidden]" attribute: it's too weak, can not be applied to an element with "display: flex"
+        // * ".hidden" class: it has been polluted by Fomantic UI in many cases
+        // * inline style="display: none": it's difficult to tweak
+        // * jQuery's show/hide/toggle: it can not show/hide elements with "display: xxx !important"
+        // only use:
+        // * this ".tw-hidden" class
+        // * showElem/hideElem/toggleElem functions in "utils/dom.js"
+        '.hidden.hidden': {
+          'display': 'none',
+        },
+        // proposed class from https://github.com/tailwindlabs/tailwindcss/pull/12128
+        '.break-anywhere': {
+          'overflow-wrap': 'anywhere',
+        },
+      });
+    }),
+  ],
 };
diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css
index 60ecd7db72..15df9f3a45 100644
--- a/web_src/css/helpers.css
+++ b/web_src/css/helpers.css
@@ -35,22 +35,6 @@ Gitea's private styles use `g-` prefix.
 .interact-bg:hover { background: var(--color-hover) !important; }
 .interact-bg:active { background: var(--color-active) !important; }
 
-/*
-tw-hidden must win all other "display: xxx !important" classes to get the chance to "hide" an element.
-do not use:
-* "[hidden]" attribute: it's too weak, can not be applied to an element with "display: flex"
-* ".hidden" class: it has been polluted by Fomantic UI in many cases
-* inline style="display: none": it's difficult to tweak
-* jQuery's show/hide/toggle: it can not show/hide elements with "display: xxx !important"
-only use:
-* this ".tw-hidden" class
-* showElem/hideElem/toggleElem functions in "utils/dom.js"
-*/
-.tw-hidden.tw-hidden { display: none !important; }
-
-/* proposed class from https://github.com/tailwindlabs/tailwindcss/pull/12128 */
-.tw-break-anywhere { overflow-wrap: anywhere !important; }
-
 @media (max-width: 767.98px) {
   /* double selector so it wins over .tw-flex (old .gt-df) etc */
   .not-mobile.not-mobile {

From 1d4bff4f65d5e4a3969871ef91d3612daf272b45 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Mon, 3 Jun 2024 20:42:52 +0200
Subject: [PATCH 07/19] Add option for mailer to override mail headers (#27860)

Add option to override headers of mails, gitea send out

---
*Sponsored by Kithara Software GmbH*

(cherry picked from commit aace3bccc3290446637cac30b121b94b5d03075f)

Conflicts:
	docs/content/administration/config-cheat-sheet.en-us.md
	does not exist in Forgejo
	services/mailer/mailer_test.go
	trivial context conflict
---
 custom/conf/app.example.ini    | 10 +++++
 modules/setting/mailer.go      | 23 ++++++----
 services/mailer/mailer.go      | 10 ++++-
 services/mailer/mailer_test.go | 76 ++++++++++++++++++++++++++++++++++
 4 files changed, 110 insertions(+), 9 deletions(-)

diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 451742ac06..013a0e5bec 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -1746,6 +1746,16 @@ LEVEL = Info
 ;; convert \r\n to \n for Sendmail
 ;SENDMAIL_CONVERT_CRLF = true
 
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;[mailer.override_header]
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; This is empty by default, use it only if you know what you need it for.
+;Reply-To = test@example.com, test2@example.com
+;Content-Type = text/html; charset=utf-8
+;In-Reply-To =
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;[email.incoming]
diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go
index e9ce640c7f..cfedb4613f 100644
--- a/modules/setting/mailer.go
+++ b/modules/setting/mailer.go
@@ -18,14 +18,15 @@ import (
 // Mailer represents mail service.
 type Mailer struct {
 	// Mailer
-	Name                 string `ini:"NAME"`
-	From                 string `ini:"FROM"`
-	EnvelopeFrom         string `ini:"ENVELOPE_FROM"`
-	OverrideEnvelopeFrom bool   `ini:"-"`
-	FromName             string `ini:"-"`
-	FromEmail            string `ini:"-"`
-	SendAsPlainText      bool   `ini:"SEND_AS_PLAIN_TEXT"`
-	SubjectPrefix        string `ini:"SUBJECT_PREFIX"`
+	Name                 string              `ini:"NAME"`
+	From                 string              `ini:"FROM"`
+	EnvelopeFrom         string              `ini:"ENVELOPE_FROM"`
+	OverrideEnvelopeFrom bool                `ini:"-"`
+	FromName             string              `ini:"-"`
+	FromEmail            string              `ini:"-"`
+	SendAsPlainText      bool                `ini:"SEND_AS_PLAIN_TEXT"`
+	SubjectPrefix        string              `ini:"SUBJECT_PREFIX"`
+	OverrideHeader       map[string][]string `ini:"-"`
 
 	// SMTP sender
 	Protocol             string `ini:"PROTOCOL"`
@@ -159,6 +160,12 @@ func loadMailerFrom(rootCfg ConfigProvider) {
 		log.Fatal("Unable to map [mailer] section on to MailService. Error: %v", err)
 	}
 
+	overrideHeader := rootCfg.Section("mailer.override_header").Keys()
+	MailService.OverrideHeader = make(map[string][]string)
+	for _, key := range overrideHeader {
+		MailService.OverrideHeader[key.Name()] = key.Strings(",")
+	}
+
 	// Infer SMTPPort if not set
 	if MailService.SMTPPort == "" {
 		switch MailService.Protocol {
diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go
index a9316cca76..0a723f974a 100644
--- a/services/mailer/mailer.go
+++ b/services/mailer/mailer.go
@@ -57,7 +57,7 @@ func (m *Message) ToMessage() *gomail.Message {
 		msg.SetHeader(header, m.Headers[header]...)
 	}
 
-	if len(setting.MailService.SubjectPrefix) > 0 {
+	if setting.MailService.SubjectPrefix != "" {
 		msg.SetHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject)
 	} else {
 		msg.SetHeader("Subject", m.Subject)
@@ -79,6 +79,14 @@ func (m *Message) ToMessage() *gomail.Message {
 	if len(msg.GetHeader("Message-ID")) == 0 {
 		msg.SetHeader("Message-ID", m.generateAutoMessageID())
 	}
+
+	for k, v := range setting.MailService.OverrideHeader {
+		if len(msg.GetHeader(k)) != 0 {
+			log.Debug("Mailer override header '%s' as per config", k)
+		}
+		msg.SetHeader(k, v...)
+	}
+
 	return msg
 }
 
diff --git a/services/mailer/mailer_test.go b/services/mailer/mailer_test.go
index 253454e89c..2f7da08697 100644
--- a/services/mailer/mailer_test.go
+++ b/services/mailer/mailer_test.go
@@ -4,6 +4,7 @@
 package mailer
 
 import (
+	"strings"
 	"testing"
 	"time"
 
@@ -51,3 +52,78 @@ func TestGenerateMessageIDForRelease(t *testing.T) {
 	m := createMessageIDForRelease(&rel)
 	assert.Equal(t, "", m)
 }
+
+func TestToMessage(t *testing.T) {
+	oldConf := *setting.MailService
+	defer func() {
+		setting.MailService = &oldConf
+	}()
+	setting.MailService.From = "test@gitea.com"
+
+	m1 := Message{
+		Info:            "info",
+		FromAddress:     "test@gitea.com",
+		FromDisplayName: "Test Gitea",
+		To:              "a@b.com",
+		Subject:         "Issue X Closed",
+		Body:            "Some Issue got closed by Y-Man",
+	}
+
+	buf := &strings.Builder{}
+	_, err := m1.ToMessage().WriteTo(buf)
+	assert.NoError(t, err)
+	header, _ := extractMailHeaderAndContent(t, buf.String())
+	assert.EqualValues(t, map[string]string{
+		"Content-Type":             "multipart/alternative;",
+		"Date":                     "Mon, 01 Jan 0001 00:00:00 +0000",
+		"From":                     "\"Test Gitea\" ",
+		"Message-ID":               "",
+		"Mime-Version":             "1.0",
+		"Subject":                  "Issue X Closed",
+		"To":                       "a@b.com",
+		"X-Auto-Response-Suppress": "All",
+	}, header)
+
+	setting.MailService.OverrideHeader = map[string][]string{
+		"Message-ID":     {""},               // delete message id
+		"Auto-Submitted": {"auto-generated"}, // suppress auto replay
+	}
+
+	buf = &strings.Builder{}
+	_, err = m1.ToMessage().WriteTo(buf)
+	assert.NoError(t, err)
+	header, _ = extractMailHeaderAndContent(t, buf.String())
+	assert.EqualValues(t, map[string]string{
+		"Content-Type":             "multipart/alternative;",
+		"Date":                     "Mon, 01 Jan 0001 00:00:00 +0000",
+		"From":                     "\"Test Gitea\" ",
+		"Message-ID":               "",
+		"Mime-Version":             "1.0",
+		"Subject":                  "Issue X Closed",
+		"To":                       "a@b.com",
+		"X-Auto-Response-Suppress": "All",
+		"Auto-Submitted":           "auto-generated",
+	}, header)
+}
+
+func extractMailHeaderAndContent(t *testing.T, mail string) (map[string]string, string) {
+	header := make(map[string]string)
+
+	parts := strings.SplitN(mail, "boundary=", 2)
+	if !assert.Len(t, parts, 2) {
+		return nil, ""
+	}
+	content := strings.TrimSpace("boundary=" + parts[1])
+
+	hParts := strings.Split(parts[0], "\n")
+
+	for _, hPart := range hParts {
+		parts := strings.SplitN(hPart, ":", 2)
+		hk := strings.TrimSpace(parts[0])
+		if hk != "" {
+			header[hk] = strings.TrimSpace(parts[1])
+		}
+	}
+
+	return header, content
+}

From 21b94765ce1b25cea4d8be44831ef5df96996342 Mon Sep 17 00:00:00 2001
From: Earl Warren 
Date: Sun, 9 Jun 2024 08:04:11 +0200
Subject: [PATCH 08/19] test(services/mailer): Add option for mailer to
 override mail headers

Use MockVariableValue to avoid undesirable side effects between tests
modifying global variables. TestToMessage relies
on *setting.MailService being set, which will not be the case if run
individually with test-sqlite#TestToMessage and fail.
---
 services/mailer/mailer_test.go | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)

diff --git a/services/mailer/mailer_test.go b/services/mailer/mailer_test.go
index 2f7da08697..b7b4c28a3c 100644
--- a/services/mailer/mailer_test.go
+++ b/services/mailer/mailer_test.go
@@ -10,17 +10,16 @@ import (
 
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/test"
 
 	"github.com/stretchr/testify/assert"
 )
 
 func TestGenerateMessageID(t *testing.T) {
-	mailService := setting.Mailer{
+	defer test.MockVariableValue(&setting.MailService, &setting.Mailer{
 		From: "test@gitea.com",
-	}
-
-	setting.MailService = &mailService
-	setting.Domain = "localhost"
+	})()
+	defer test.MockVariableValue(&setting.Domain, "localhost")()
 
 	date := time.Date(2000, 1, 2, 3, 4, 5, 6, time.UTC)
 	m := NewMessageFrom("", "display-name", "from-address", "subject", "body")
@@ -40,7 +39,7 @@ func TestGenerateMessageID(t *testing.T) {
 }
 
 func TestGenerateMessageIDForRelease(t *testing.T) {
-	setting.Domain = "localhost"
+	defer test.MockVariableValue(&setting.Domain, "localhost")()
 
 	rel := repo_model.Release{
 		ID: 42,
@@ -54,11 +53,10 @@ func TestGenerateMessageIDForRelease(t *testing.T) {
 }
 
 func TestToMessage(t *testing.T) {
-	oldConf := *setting.MailService
-	defer func() {
-		setting.MailService = &oldConf
-	}()
-	setting.MailService.From = "test@gitea.com"
+	defer test.MockVariableValue(&setting.MailService, &setting.Mailer{
+		From: "test@gitea.com",
+	})()
+	defer test.MockVariableValue(&setting.Domain, "localhost")()
 
 	m1 := Message{
 		Info:            "info",

From 5416e4054a5984ddf53440442e46fb77d3ee07bc Mon Sep 17 00:00:00 2001
From: silverwind 
Date: Tue, 4 Jun 2024 08:10:04 +0200
Subject: [PATCH 09/19] Fix overflow on notifications (#31178)

Fixes https://github.com/go-gitea/gitea/issues/31170.

image

(cherry picked from commit 4f9b8b397c1acb6f6d26c55e224aafcb5474a85b)
---
 templates/user/notification/notification_div.tmpl | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/templates/user/notification/notification_div.tmpl b/templates/user/notification/notification_div.tmpl
index 99da119901..f24ee7bf94 100644
--- a/templates/user/notification/notification_div.tmpl
+++ b/templates/user/notification/notification_div.tmpl
@@ -49,14 +49,14 @@
 								{{end}}
 							
 							
-								
+
{{.Repository.FullName}} {{if .Issue}}#{{.Issue.Index}}{{end}} {{if eq .Status 3}} {{svg "octicon-pin" 13 "text blue tw-mt-0.5 tw-ml-1"}} {{end}}
- + {{if .Issue}} {{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}} {{else}} From c6e04c3c9eddfa6c4bec541f681c8d300b157cdb Mon Sep 17 00:00:00 2001 From: Thomas Desveaux Date: Tue, 4 Jun 2024 08:45:56 +0200 Subject: [PATCH 10/19] Fix NuGet Package API for $filter with Id equality (#31188) Fixes issue when running `choco info pkgname` where `pkgname` is also a substring of another package Id. Relates to #31168 --- This might fix the issue linked, but I'd like to test it with more choco commands before closing the issue in case I find other problems if that's ok. --------- Co-authored-by: KN4CK3R (cherry picked from commit c888c933a930ee2ba4e7bb0bf6678aaf45a9778a) --- routers/api/packages/nuget/nuget.go | 48 +++++---- tests/integration/api_packages_nuget_test.go | 102 ++++++++++++++++--- 2 files changed, 115 insertions(+), 35 deletions(-) diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go index 26b0ae226e..3633d0d007 100644 --- a/routers/api/packages/nuget/nuget.go +++ b/routers/api/packages/nuget/nuget.go @@ -96,20 +96,34 @@ func FeedCapabilityResource(ctx *context.Context) { xmlResponse(ctx, http.StatusOK, Metadata) } -var searchTermExtract = regexp.MustCompile(`'([^']+)'`) +var ( + searchTermExtract = regexp.MustCompile(`'([^']+)'`) + searchTermExact = regexp.MustCompile(`\s+eq\s+'`) +) -func getSearchTerm(ctx *context.Context) string { +func getSearchTerm(ctx *context.Context) packages_model.SearchValue { searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'") - if searchTerm == "" { - // $filter contains a query like: - // (((Id ne null) and substringof('microsoft',tolower(Id))) - // We don't support these queries, just extract the search term. - match := searchTermExtract.FindStringSubmatch(ctx.FormTrim("$filter")) - if len(match) == 2 { - searchTerm = strings.TrimSpace(match[1]) + if searchTerm != "" { + return packages_model.SearchValue{ + Value: searchTerm, + ExactMatch: false, } } - return searchTerm + + // $filter contains a query like: + // (((Id ne null) and substringof('microsoft',tolower(Id))) + // https://www.odata.org/documentation/odata-version-2-0/uri-conventions/ section 4.5 + // We don't support these queries, just extract the search term. + filter := ctx.FormTrim("$filter") + match := searchTermExtract.FindStringSubmatch(filter) + if len(match) == 2 { + return packages_model.SearchValue{ + Value: strings.TrimSpace(match[1]), + ExactMatch: searchTermExact.MatchString(filter), + } + } + + return packages_model.SearchValue{} } // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs @@ -118,11 +132,9 @@ func SearchServiceV2(ctx *context.Context) { paginator := db.NewAbsoluteListOptions(skip, take) pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ - OwnerID: ctx.Package.Owner.ID, - Type: packages_model.TypeNuGet, - Name: packages_model.SearchValue{ - Value: getSearchTerm(ctx), - }, + OwnerID: ctx.Package.Owner.ID, + Type: packages_model.TypeNuGet, + Name: getSearchTerm(ctx), IsInternal: optional.Some(false), Paginator: paginator, }) @@ -169,10 +181,8 @@ func SearchServiceV2(ctx *context.Context) { // http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351 func SearchServiceV2Count(ctx *context.Context) { count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{ - OwnerID: ctx.Package.Owner.ID, - Name: packages_model.SearchValue{ - Value: getSearchTerm(ctx), - }, + OwnerID: ctx.Package.Owner.ID, + Name: getSearchTerm(ctx), IsInternal: optional.Some(false), }) if err != nil { diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index 991f37fe74..e0ec279d7a 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -431,22 +431,33 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) t.Run("SearchService", func(t *testing.T) { cases := []struct { - Query string - Skip int - Take int - ExpectedTotal int64 - ExpectedResults int + Query string + Skip int + Take int + ExpectedTotal int64 + ExpectedResults int + ExpectedExactMatch bool }{ - {"", 0, 0, 1, 1}, - {"", 0, 10, 1, 1}, - {"gitea", 0, 10, 0, 0}, - {"test", 0, 10, 1, 1}, - {"test", 1, 10, 1, 0}, + {"", 0, 0, 4, 4, false}, + {"", 0, 10, 4, 4, false}, + {"gitea", 0, 10, 0, 0, false}, + {"test", 0, 10, 1, 1, false}, + {"test", 1, 10, 1, 0, false}, + {"almost.similar", 0, 0, 3, 3, true}, } - req := NewRequestWithBody(t, "PUT", url, createPackage(packageName, "1.0.99")). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + fakePackages := []string{ + packageName, + "almost.similar.dependency", + "almost.similar", + "almost.similar.dependant", + } + + for _, fakePackageName := range fakePackages { + req := NewRequestWithBody(t, "PUT", url, createPackage(fakePackageName, "1.0.99")). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) + } t.Run("v2", func(t *testing.T) { t.Run("Search()", func(t *testing.T) { @@ -493,6 +504,63 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) } }) + t.Run("Packages()", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("substringof", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + for i, c := range cases { + req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result FeedResponse + decodeXML(t, resp, &result) + + assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i) + assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i) + } + }) + + t.Run("IdEq", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + for i, c := range cases { + if c.Query == "" { + // Ignore the `tolower(Id) eq ''` as it's unlikely to happen + continue + } + req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=(tolower(Id) eq '%s')&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result FeedResponse + decodeXML(t, resp, &result) + + expectedCount := 0 + if c.ExpectedExactMatch { + expectedCount = 1 + } + + assert.Equal(t, int64(expectedCount), result.Count, "case %d: unexpected total hits", i) + assert.Len(t, result.Entries, expectedCount, "case %d: unexpected result count", i) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=(tolower(Id) eq '%s')&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, strconv.FormatInt(int64(expectedCount), 10), resp.Body.String(), "case %d: unexpected total hits", i) + } + }) + }) + t.Run("Next", func(t *testing.T) { req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='test'&$skip=0&$top=1", url)). AddBasicAuth(user.Name) @@ -550,9 +618,11 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) }) }) - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, "1.0.99")). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusNoContent) + for _, fakePackageName := range fakePackages { + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, fakePackageName, "1.0.99")). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + } }) t.Run("RegistrationService", func(t *testing.T) { From 57647abc24d036de6925eef1f8b1192c7014ccbe Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 4 Jun 2024 09:14:24 +0200 Subject: [PATCH 11/19] Fix overflow on push notification (#31179) Fixes: https://github.com/go-gitea/gitea/issues/30063 Screenshot 2024-05-30 at 14 43 24 (cherry picked from commit 1f8ac27b31b52791396f198b665a1d6bbdcfd8b3) --- templates/repo/code/recently_pushed_new_branches.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/repo/code/recently_pushed_new_branches.tmpl b/templates/repo/code/recently_pushed_new_branches.tmpl index f6f595a00f..d996acc5b2 100644 --- a/templates/repo/code/recently_pushed_new_branches.tmpl +++ b/templates/repo/code/recently_pushed_new_branches.tmpl @@ -1,6 +1,6 @@ {{range .RecentlyPushedNewBranches}} -
-
+
+
{{$timeSince := TimeSince .CommitTime.AsTime ctx.Locale}} {{$repo := .GetRepo $.Context}} {{$name := .Name}} From 23a82bcd7a9d2665c53d2c7f38ad7e32721be21b Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 4 Jun 2024 09:46:05 +0200 Subject: [PATCH 12/19] Remove .segment from .project-column (#31204) Using `.segment` on the project columns is a major abuse of that class, so remove it and instead set the border-radius directly on it. Fixes: https://github.com/go-gitea/gitea/issues/31129 (cherry picked from commit 4ca65fabdad75e39f9948b9a2a18e32edc98ec02) --- templates/projects/view.tmpl | 2 +- web_src/css/features/projects.css | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index 47f214a44e..df52dbbf9c 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -66,7 +66,7 @@
{{range .Columns}} -
+
diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css index d363b97e22..9d39306c8d 100644 --- a/web_src/css/features/projects.css +++ b/web_src/css/features/projects.css @@ -9,6 +9,7 @@ .project-column { background-color: var(--color-project-column-bg) !important; border: 1px solid var(--color-secondary) !important; + border-radius: var(--border-radius); margin: 0 0.5rem !important; padding: 0.5rem !important; width: 320px; From 6582f0029bb71adeee76a7c25d5547be77a624ff Mon Sep 17 00:00:00 2001 From: silverwind Date: Wed, 5 Jun 2024 03:22:38 +0200 Subject: [PATCH 13/19] Add `lint-go-gopls` (#30729) Uses `gopls check ` as a linter. Tested locally and brings up 149 errors currently for me. I don't think I want to fix them in this PR, but I would like at least to get this analysis running on CI. List of errors: ``` modules/indexer/code/indexer.go:181:11: impossible condition: nil != nil routers/private/hook_post_receive.go:120:15: tautological condition: nil == nil services/auth/source/oauth2/providers.go:185:9: tautological condition: nil == nil services/convert/issue.go:216:11: tautological condition: non-nil != nil tests/integration/git_test.go:332:9: impossible condition: nil != nil services/migrations/migrate.go:179:24-43: unused parameter: ctx services/repository/transfer.go:288:48-69: unused parameter: doer tests/integration/api_repo_tags_test.go:75:41-61: unused parameter: session tests/integration/git_test.go:696:64-74: unused parameter: baseBranch tests/integration/gpg_git_test.go:265:27-39: unused parameter: t tests/integration/gpg_git_test.go:284:23-29: unused parameter: tmpDir tests/integration/gpg_git_test.go:284:31-35: unused parameter: name tests/integration/gpg_git_test.go:284:37-42: unused parameter: email ``` (cherry picked from commit 816222243af523316041692622be6f48ef068693) Conflicts: Makefile trivial context conflict and also ask renovate to watch over it do not include it in lint-backend because the errors are not fixed --- Makefile | 8 ++++++++ services/migrations/migrate.go | 2 +- services/repository/transfer.go | 4 ++-- tests/integration/api_repo_tags_test.go | 6 +++--- tests/integration/dump_restore_test.go | 2 +- tests/integration/gpg_git_test.go | 6 +++--- tools/lint-go-gopls.sh | 23 +++++++++++++++++++++++ 7 files changed, 41 insertions(+), 10 deletions(-) create mode 100755 tools/lint-go-gopls.sh diff --git a/Makefile b/Makefile index 4bdd0d8819..6e91bc3a96 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasour GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.22.0 # renovate: datasource=go GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go +GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.15.3 # renovate: datasource=go DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest @@ -228,6 +229,7 @@ help: @echo " - lint-go lint go files" @echo " - lint-go-fix lint go files and fix issues" @echo " - lint-go-vet lint go files with vet" + @echo " - lint-go-gopls lint go files with gopls" @echo " - lint-js lint js files" @echo " - lint-js-fix lint js files and fix issues" @echo " - lint-css lint css files" @@ -468,6 +470,11 @@ lint-go-vet: @echo "Running go vet..." @$(GO) vet ./... +.PHONY: lint-go-gopls +lint-go-gopls: + @echo "Running gopls check..." + @GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES_NO_BINDATA) + .PHONY: lint-editorconfig lint-editorconfig: $(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .forgejo/workflows @@ -879,6 +886,7 @@ deps-tools: $(GO) install $(GO_LICENSES_PACKAGE) $(GO) install $(GOVULNCHECK_PACKAGE) $(GO) install $(GOMOCK_PACKAGE) + $(GO) install $(GOPLS_PACKAGE) node_modules: package-lock.json npm install --no-save diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go index 367818c0e7..de90c5e98f 100644 --- a/services/migrations/migrate.go +++ b/services/migrations/migrate.go @@ -183,7 +183,7 @@ func newDownloader(ctx context.Context, ownerName string, opts base.MigrateOptio // migrateRepository will download information and then upload it to Uploader, this is a simple // process for small repository. For a big repository, save all the data to disk // before upload is better -func migrateRepository(ctx context.Context, doer *user_model.User, downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions, messenger base.Messenger) error { +func migrateRepository(_ context.Context, doer *user_model.User, downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions, messenger base.Messenger) error { if messenger == nil { messenger = base.NilMessenger } diff --git a/services/repository/transfer.go b/services/repository/transfer.go index ca6ea6b632..467c85ef6f 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -285,7 +285,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName } // changeRepositoryName changes all corresponding setting from old repository name to new one. -func changeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, newRepoName string) (err error) { +func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newRepoName string) (err error) { oldRepoName := repo.Name newRepoName = strings.ToLower(newRepoName) if err = repo_model.IsUsableRepoName(newRepoName); err != nil { @@ -347,7 +347,7 @@ func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo // local copy's origin accordingly. repoWorkingPool.CheckIn(fmt.Sprint(repo.ID)) - if err := changeRepositoryName(ctx, doer, repo, newRepoName); err != nil { + if err := changeRepositoryName(ctx, repo, newRepoName); err != nil { repoWorkingPool.CheckOut(fmt.Sprint(repo.ID)) return err } diff --git a/tests/integration/api_repo_tags_test.go b/tests/integration/api_repo_tags_test.go index 10a82e11a8..09f17ef475 100644 --- a/tests/integration/api_repo_tags_test.go +++ b/tests/integration/api_repo_tags_test.go @@ -42,7 +42,7 @@ func TestAPIRepoTags(t *testing.T) { assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.zip", tags[0].ZipballURL) assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.tar.gz", tags[0].TarballURL) - newTag := createNewTagUsingAPI(t, session, token, user.Name, repoName, "gitea/22", "", "nice!\nand some text") + newTag := createNewTagUsingAPI(t, token, user.Name, repoName, "gitea/22", "", "nice!\nand some text") resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &tags) assert.Len(t, tags, 2) @@ -72,7 +72,7 @@ func TestAPIRepoTags(t *testing.T) { MakeRequest(t, req, http.StatusNotFound) } -func createNewTagUsingAPI(t *testing.T, session *TestSession, token, ownerName, repoName, name, target, msg string) *api.Tag { +func createNewTagUsingAPI(t *testing.T, token, ownerName, repoName, name, target, msg string) *api.Tag { urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags", ownerName, repoName) req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateTagOption{ TagName: name, @@ -96,7 +96,7 @@ func TestAPIGetTagArchiveDownloadCount(t *testing.T) { repoName := "repo1" tagName := "TagDownloadCount" - createNewTagUsingAPI(t, session, token, user.Name, repoName, tagName, "", "") + createNewTagUsingAPI(t, token, user.Name, repoName, tagName, "", "") urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags/%s?token=%s", user.Name, repoName, tagName, token) diff --git a/tests/integration/dump_restore_test.go b/tests/integration/dump_restore_test.go index bed2453054..47bb6f76e9 100644 --- a/tests/integration/dump_restore_test.go +++ b/tests/integration/dump_restore_test.go @@ -237,7 +237,7 @@ func (c *compareDump) assertLoadFiles(beforeFilename, afterFilename string, t re // // Given []Something{} create afterPtr, beforePtr []*Something{} // - sliceType := reflect.SliceOf(reflect.PtrTo(t.Elem())) + sliceType := reflect.SliceOf(reflect.PointerTo(t.Elem())) beforeSlice := reflect.MakeSlice(sliceType, 0, 10) beforePtr = reflect.New(beforeSlice.Type()) beforePtr.Elem().Set(beforeSlice) diff --git a/tests/integration/gpg_git_test.go b/tests/integration/gpg_git_test.go index b1f8fad268..d82b0e9414 100644 --- a/tests/integration/gpg_git_test.go +++ b/tests/integration/gpg_git_test.go @@ -36,7 +36,7 @@ func TestGPGGit(t *testing.T) { defer os.Setenv("GNUPGHOME", oldGNUPGHome) // Need to create a root key - rootKeyPair, err := importTestingKey(tmpDir, "gitea", "gitea@fake.local") + rootKeyPair, err := importTestingKey() if !assert.NoError(t, err, "importTestingKey") { return } @@ -263,7 +263,7 @@ func TestGPGGit(t *testing.T) { }) } -func crudActionCreateFile(t *testing.T, ctx APITestContext, user *user_model.User, from, to, path string, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) { +func crudActionCreateFile(_ *testing.T, ctx APITestContext, user *user_model.User, from, to, path string, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) { return doAPICreateFile(ctx, path, &api.CreateFileOptions{ FileOptions: api.FileOptions{ BranchName: from, @@ -282,7 +282,7 @@ func crudActionCreateFile(t *testing.T, ctx APITestContext, user *user_model.Use }, callback...) } -func importTestingKey(tmpDir, name, email string) (*openpgp.Entity, error) { +func importTestingKey() (*openpgp.Entity, error) { if _, _, err := process.GetManager().Exec("gpg --import tests/integration/private-testing.key", "gpg", "--import", "tests/integration/private-testing.key"); err != nil { return nil, err } diff --git a/tools/lint-go-gopls.sh b/tools/lint-go-gopls.sh new file mode 100755 index 0000000000..4bb69f4c16 --- /dev/null +++ b/tools/lint-go-gopls.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -uo pipefail + +cd "$(dirname -- "${BASH_SOURCE[0]}")" && cd .. + +IGNORE_PATTERNS=( + "is deprecated" # TODO: fix these +) + +# lint all go files with 'gopls check' and look for lines starting with the +# current absolute path, indicating a error was found. This is neccessary +# because the tool does not set non-zero exit code when errors are found. +# ref: https://github.com/golang/go/issues/67078 +ERROR_LINES=$("$GO" run "$GOPLS_PACKAGE" check "$@" 2>/dev/null | grep -E "^$PWD" | grep -vFf <(printf '%s\n' "${IGNORE_PATTERNS[@]}")); +NUM_ERRORS=$(echo -n "$ERROR_LINES" | wc -l) + +if [ "$NUM_ERRORS" -eq "0" ]; then + exit 0; +else + echo "$ERROR_LINES" + echo "Found $NUM_ERRORS 'gopls check' errors" + exit 1; +fi From 93d1fea67dc7fb4b3b79d370d2bc03b0ec098684 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Wed, 5 Jun 2024 22:39:45 +0800 Subject: [PATCH 14/19] Optimize runner-tags layout to enhance visual experience (#31258) ![image](https://github.com/go-gitea/gitea/assets/3371163/b8199005-94f2-45be-8ca9-4fa1b3f221b2) (cherry picked from commit 06ebae7472aef4380602d2ecd64fdc9dddcb6037) --- templates/shared/actions/runner_list.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/shared/actions/runner_list.tmpl b/templates/shared/actions/runner_list.tmpl index caf4c8351f..abbf2204a8 100644 --- a/templates/shared/actions/runner_list.tmpl +++ b/templates/shared/actions/runner_list.tmpl @@ -70,7 +70,7 @@

{{.Name}}

{{if .Version}}{{.Version}}{{else}}{{ctx.Locale.Tr "unknown"}}{{end}} {{.BelongsToOwnerType.LocaleString ctx.Locale}} - + {{range .AgentLabels}}{{.}}{{end}} {{if .LastOnline}}{{TimeSinceUnix .LastOnline ctx.Locale}}{{else}}{{ctx.Locale.Tr "never"}}{{end}} From 433b6c6910f8699dc41787ef8f5148b122b4677e Mon Sep 17 00:00:00 2001 From: Henrique Pimentel <66185935+HenriquerPimentel@users.noreply.github.com> Date: Thu, 6 Jun 2024 09:06:59 +0100 Subject: [PATCH 15/19] Add `MAX_ROWS` option for CSV rendering (#30268) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This solution implements a new config variable MAX_ROWS, which corresponds to the “Maximum allowed rows to render CSV files. (0 for no limit)” and rewrites the Render function for CSV files in markup module. Now the render function only reads the file once, having MAX_FILE_SIZE+1 as a reader limit and MAX_ROWS as a row limit. When the file is larger than MAX_FILE_SIZE or has more rows than MAX_ROWS, it only renders until the limit, and displays a user-friendly warning informing that the rendered data is not complete, in the user's language. --- Previously, when a CSV file was larger than the limit, the render function lost its function to render the code. There were also multiple reads to the file, in order to determine its size and render or pre-render. The warning: ![image](https://s3.amazonaws.com/i.snag.gy/vcKh90.jpg) (cherry picked from commit f7125ab61aaa02fd4c7ab0062a2dc9a57726e2ec) --- custom/conf/app.example.ini | 3 ++ modules/markup/csv/csv.go | 94 +++++++++++++--------------------- modules/markup/csv/csv_test.go | 10 ---- modules/setting/ui.go | 3 ++ 4 files changed, 41 insertions(+), 69 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 013a0e5bec..b1fe38dee1 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1387,6 +1387,9 @@ LEVEL = Info ;; ;; Maximum allowed file size in bytes to render CSV files as table. (Set to 0 for no limit). ;MAX_FILE_SIZE = 524288 +;; +;; Maximum allowed rows to render CSV files. (Set to 0 for no limit) +;MAX_ROWS = 2500 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/modules/markup/csv/csv.go b/modules/markup/csv/csv.go index 1dd26eb8ac..3d952b0de4 100644 --- a/modules/markup/csv/csv.go +++ b/modules/markup/csv/csv.go @@ -5,8 +5,6 @@ package markup import ( "bufio" - "bytes" - "fmt" "html" "io" "regexp" @@ -15,6 +13,8 @@ import ( "code.gitea.io/gitea/modules/csv" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/modules/util" ) func init() { @@ -81,86 +81,38 @@ func writeField(w io.Writer, element, class, field string) error { func (r Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { tmpBlock := bufio.NewWriter(output) maxSize := setting.UI.CSV.MaxFileSize + maxRows := setting.UI.CSV.MaxRows - if maxSize == 0 { - return r.tableRender(ctx, input, tmpBlock) + if maxSize != 0 { + input = io.LimitReader(input, maxSize+1) } - rawBytes, err := io.ReadAll(io.LimitReader(input, maxSize+1)) - if err != nil { - return err - } - - if int64(len(rawBytes)) <= maxSize { - return r.tableRender(ctx, bytes.NewReader(rawBytes), tmpBlock) - } - return r.fallbackRender(io.MultiReader(bytes.NewReader(rawBytes), input), tmpBlock) -} - -func (Renderer) fallbackRender(input io.Reader, tmpBlock *bufio.Writer) error { - _, err := tmpBlock.WriteString("
")
-	if err != nil {
-		return err
-	}
-
-	scan := bufio.NewScanner(input)
-	scan.Split(bufio.ScanRunes)
-	for scan.Scan() {
-		switch scan.Text() {
-		case `&`:
-			_, err = tmpBlock.WriteString("&")
-		case `'`:
-			_, err = tmpBlock.WriteString("'") // "'" is shorter than "'" and apos was not in HTML until HTML5.
-		case `<`:
-			_, err = tmpBlock.WriteString("<")
-		case `>`:
-			_, err = tmpBlock.WriteString(">")
-		case `"`:
-			_, err = tmpBlock.WriteString(""") // """ is shorter than """.
-		default:
-			_, err = tmpBlock.Write(scan.Bytes())
-		}
-		if err != nil {
-			return err
-		}
-	}
-	if err = scan.Err(); err != nil {
-		return fmt.Errorf("fallbackRender scan: %w", err)
-	}
-
-	_, err = tmpBlock.WriteString("
") - if err != nil { - return err - } - return tmpBlock.Flush() -} - -func (Renderer) tableRender(ctx *markup.RenderContext, input io.Reader, tmpBlock *bufio.Writer) error { rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, input) if err != nil { return err } - if _, err := tmpBlock.WriteString(``); err != nil { return err } - row := 1 + + row := 0 for { fields, err := rd.Read() - if err == io.EOF { + if err == io.EOF || (row >= maxRows && maxRows != 0) { break } if err != nil { continue } + if _, err := tmpBlock.WriteString(""); err != nil { return err } element := "td" - if row == 1 { + if row == 0 { element = "th" } - if err := writeField(tmpBlock, element, "line-num", strconv.Itoa(row)); err != nil { + if err := writeField(tmpBlock, element, "line-num", strconv.Itoa(row+1)); err != nil { return err } for _, field := range fields { @@ -174,8 +126,32 @@ func (Renderer) tableRender(ctx *markup.RenderContext, input io.Reader, tmpBlock row++ } + if _, err = tmpBlock.WriteString("
"); err != nil { return err } + + // Check if maxRows or maxSize is reached, and if true, warn. + if (row >= maxRows && maxRows != 0) || (rd.InputOffset() >= maxSize && maxSize != 0) { + warn := `
` + rawLink := ` ` + + // Try to get the user translation + if locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale); ok { + warn += locale.TrString("repo.file_too_large") + rawLink += locale.TrString("repo.file_view_raw") + } else { + warn += "The file is too large to be shown." + rawLink += "View Raw" + } + + warn += rawLink + `
` + + // Write the HTML string to the output + if _, err := tmpBlock.WriteString(warn); err != nil { + return err + } + } + return tmpBlock.Flush() } diff --git a/modules/markup/csv/csv_test.go b/modules/markup/csv/csv_test.go index 3d12be477c..8c07184b21 100644 --- a/modules/markup/csv/csv_test.go +++ b/modules/markup/csv/csv_test.go @@ -4,8 +4,6 @@ package markup import ( - "bufio" - "bytes" "strings" "testing" @@ -31,12 +29,4 @@ func TestRenderCSV(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, v, buf.String()) } - - t.Run("fallbackRender", func(t *testing.T) { - var buf bytes.Buffer - err := render.fallbackRender(strings.NewReader("1,\n2,"), bufio.NewWriter(&buf)) - assert.NoError(t, err) - want := "
1,<a>\n2,<b>
" - assert.Equal(t, want, buf.String()) - }) } diff --git a/modules/setting/ui.go b/modules/setting/ui.go index 47e1393ef3..056d670ba6 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -53,6 +53,7 @@ var UI = struct { CSV struct { MaxFileSize int64 + MaxRows int } `ini:"ui.csv"` Admin struct { @@ -110,8 +111,10 @@ var UI = struct { }, CSV: struct { MaxFileSize int64 + MaxRows int }{ MaxFileSize: 524288, + MaxRows: 2500, }, Admin: struct { UserPagingNum int From 4ddd9af50fbfcfb2ebf629697a803b3bce56c4af Mon Sep 17 00:00:00 2001 From: Max Wipfli Date: Thu, 6 Jun 2024 10:35:04 +0200 Subject: [PATCH 16/19] Allow including `Reviewed-on`/`Reviewed-by` lines for custom merge messages (#31211) This PR introduces the `ReviewedOn` and `ReviewedBy` variables for the default merge message templates (e.g., `.gitea/default_merge_message/MERGE_TEMPLATE.md`). This allows customizing the default merge messages while retaining these trailers. This also moves the associated logic out of `pull.tmpl` into the relevant Go function. This is a first contribution towards #11077. --- For illustration, this allows to recreate the "default default" merge message with the following template: ``` .gitea/default_merge_message/MERGE_TEMPLATE.md Merge pull request '${PullRequestTitle}' (${PullRequestReference}) from ${HeadBranch} into ${BaseBranch} ${ReviewedOn} ${ReviewedBy} ``` (cherry picked from commit da4bbc42477ba04d175cc0775a0c5ec90c4c24fe) Conflicts: docs/content/usage/merge-message-templates.en-us.md not in Forgejo templates/repo/issue/view_content/pull.tmpl trivial context conflict --- services/pull/merge.go | 18 ++++++++++++++---- templates/repo/issue/view_content/pull.tmpl | 6 ++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/services/pull/merge.go b/services/pull/merge.go index 525146833e..0d37daa6ed 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -46,6 +46,9 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue if err := pr.Issue.LoadPoster(ctx); err != nil { return "", "", err } + if err := pr.Issue.LoadRepo(ctx); err != nil { + return "", "", err + } isExternalTracker := pr.BaseRepo.UnitEnabled(ctx, unit.TypeExternalTracker) issueReference := "#" @@ -53,6 +56,9 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue issueReference = "!" } + reviewedOn := fmt.Sprintf("Reviewed-on: %s/%s", setting.AppURL, pr.Issue.Link()) + reviewedBy := pr.GetApprovers(ctx) + if mergeStyle != "" { commit, err := baseGitRepo.GetBranchCommit(pr.BaseRepo.DefaultBranch) if err != nil { @@ -83,6 +89,8 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue "PullRequestPosterName": pr.Issue.Poster.Name, "PullRequestIndex": strconv.FormatInt(pr.Index, 10), "PullRequestReference": fmt.Sprintf("%s%d", issueReference, pr.Index), + "ReviewedOn": reviewedOn, + "ReviewedBy": reviewedBy, } if pr.HeadRepo != nil { vars["HeadRepoOwnerName"] = pr.HeadRepo.OwnerName @@ -122,20 +130,22 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue return "", "", nil } + body = fmt.Sprintf("%s\n%s", reviewedOn, reviewedBy) + // Squash merge has a different from other styles. if mergeStyle == repo_model.MergeStyleSquash { - return fmt.Sprintf("%s (%s%d)", pr.Issue.Title, issueReference, pr.Issue.Index), "", nil + return fmt.Sprintf("%s (%s%d)", pr.Issue.Title, issueReference, pr.Issue.Index), body, nil } if pr.BaseRepoID == pr.HeadRepoID { - return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), "", nil + return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), body, nil } if pr.HeadRepo == nil { - return fmt.Sprintf("Merge pull request '%s' (%s%d) from :%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), "", nil + return fmt.Sprintf("Merge pull request '%s' (%s%d) from :%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), body, nil } - return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch), "", nil + return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch), body, nil } func expandDefaultMergeMessage(template string, vars map[string]string) (message, body string) { diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl index 3c71ba833a..2672bd3389 100644 --- a/templates/repo/issue/view_content/pull.tmpl +++ b/templates/repo/issue/view_content/pull.tmpl @@ -199,7 +199,6 @@ {{if .AllowMerge}} {{/* user is allowed to merge */}} {{$prUnit := .Repository.MustGetUnit $.Context $.UnitTypePullRequests}} - {{$approvers := (.Issue.PullRequest.GetApprovers ctx)}} {{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash $prUnit.PullRequestsConfig.AllowFastForwardOnly}} {{$hasPendingPullRequestMergeTip := ""}} {{if .HasPendingPullRequestMerge}} @@ -208,11 +207,10 @@ {{end}}