Merge pull request '[gitea] week 2024-24 cherry pick (gitea/main -> forgejo)' (#4083) from earl-warren/wcp/2024-24 into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4083
Reviewed-by: twenty-panda <twenty-panda@noreply.codeberg.org>
This commit is contained in:
Earl Warren 2024-06-11 10:37:26 +00:00
commit df373c9f7e
53 changed files with 532 additions and 226 deletions

View file

@ -10,7 +10,8 @@
"ghcr.io/devcontainers-contrib/features/poetry:2": {}, "ghcr.io/devcontainers-contrib/features/poetry:2": {},
"ghcr.io/devcontainers/features/python:1": { "ghcr.io/devcontainers/features/python:1": {
"version": "3.12" "version": "3.12"
} },
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
}, },
"customizations": { "customizations": {
"vscode": { "vscode": {
@ -25,8 +26,9 @@
"Vue.volar", "Vue.volar",
"ms-azuretools.vscode-docker", "ms-azuretools.vscode-docker",
"vitest.explorer", "vitest.explorer",
"qwtel.sqlite-viewer", "cweijan.vscode-database-client2",
"GitHub.vscode-pull-request-github" "GitHub.vscode-pull-request-github",
"Azurite.azurite"
] ]
} }
}, },

View file

@ -43,7 +43,7 @@ vscode:
- Vue.volar - Vue.volar
- ms-azuretools.vscode-docker - ms-azuretools.vscode-docker
- vitest.explorer - vitest.explorer
- qwtel.sqlite-viewer - cweijan.vscode-database-client2
- GitHub.vscode-pull-request-github - GitHub.vscode-pull-request-github
ports: ports:

View file

@ -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 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 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 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_IMAGE ?= gitea/gitea
DOCKER_TAG ?= latest DOCKER_TAG ?= latest
@ -228,6 +229,7 @@ help:
@echo " - lint-go lint go files" @echo " - lint-go lint go files"
@echo " - lint-go-fix lint go files and fix issues" @echo " - lint-go-fix lint go files and fix issues"
@echo " - lint-go-vet lint go files with vet" @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 lint js files"
@echo " - lint-js-fix lint js files and fix issues" @echo " - lint-js-fix lint js files and fix issues"
@echo " - lint-css lint css files" @echo " - lint-css lint css files"
@ -468,6 +470,11 @@ lint-go-vet:
@echo "Running go vet..." @echo "Running go vet..."
@$(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 .PHONY: lint-editorconfig
lint-editorconfig: lint-editorconfig:
$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .forgejo/workflows $(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .forgejo/workflows
@ -879,13 +886,14 @@ deps-tools:
$(GO) install $(GO_LICENSES_PACKAGE) $(GO) install $(GO_LICENSES_PACKAGE)
$(GO) install $(GOVULNCHECK_PACKAGE) $(GO) install $(GOVULNCHECK_PACKAGE)
$(GO) install $(GOMOCK_PACKAGE) $(GO) install $(GOMOCK_PACKAGE)
$(GO) install $(GOPLS_PACKAGE)
node_modules: package-lock.json node_modules: package-lock.json
npm install --no-save npm install --no-save
@touch node_modules @touch node_modules
.venv: poetry.lock .venv: poetry.lock
poetry install --no-root poetry install
@touch .venv @touch .venv
.PHONY: fomantic .PHONY: fomantic

View file

@ -1387,6 +1387,9 @@ LEVEL = Info
;; ;;
;; Maximum allowed file size in bytes to render CSV files as table. (Set to 0 for no limit). ;; Maximum allowed file size in bytes to render CSV files as table. (Set to 0 for no limit).
;MAX_FILE_SIZE = 524288 ;MAX_FILE_SIZE = 524288
;;
;; Maximum allowed rows to render CSV files. (Set to 0 for no limit)
;MAX_ROWS = 2500
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1746,6 +1749,16 @@ LEVEL = Info
;; convert \r\n to \n for Sendmail ;; convert \r\n to \n for Sendmail
;SENDMAIL_CONVERT_CRLF = true ;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] ;[email.incoming]

View file

@ -5,8 +5,6 @@ package markup
import ( import (
"bufio" "bufio"
"bytes"
"fmt"
"html" "html"
"io" "io"
"regexp" "regexp"
@ -15,6 +13,8 @@ import (
"code.gitea.io/gitea/modules/csv" "code.gitea.io/gitea/modules/csv"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
) )
func init() { 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 { func (r Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
tmpBlock := bufio.NewWriter(output) tmpBlock := bufio.NewWriter(output)
maxSize := setting.UI.CSV.MaxFileSize maxSize := setting.UI.CSV.MaxFileSize
maxRows := setting.UI.CSV.MaxRows
if maxSize == 0 { if maxSize != 0 {
return r.tableRender(ctx, input, tmpBlock) 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("<pre>")
if err != nil {
return err
}
scan := bufio.NewScanner(input)
scan.Split(bufio.ScanRunes)
for scan.Scan() {
switch scan.Text() {
case `&`:
_, err = tmpBlock.WriteString("&amp;")
case `'`:
_, err = tmpBlock.WriteString("&#39;") // "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
case `<`:
_, err = tmpBlock.WriteString("&lt;")
case `>`:
_, err = tmpBlock.WriteString("&gt;")
case `"`:
_, err = tmpBlock.WriteString("&#34;") // "&#34;" is shorter than "&quot;".
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("</pre>")
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) rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, input)
if err != nil { if err != nil {
return err return err
} }
if _, err := tmpBlock.WriteString(`<table class="data-table">`); err != nil { if _, err := tmpBlock.WriteString(`<table class="data-table">`); err != nil {
return err return err
} }
row := 1
row := 0
for { for {
fields, err := rd.Read() fields, err := rd.Read()
if err == io.EOF { if err == io.EOF || (row >= maxRows && maxRows != 0) {
break break
} }
if err != nil { if err != nil {
continue continue
} }
if _, err := tmpBlock.WriteString("<tr>"); err != nil { if _, err := tmpBlock.WriteString("<tr>"); err != nil {
return err return err
} }
element := "td" element := "td"
if row == 1 { if row == 0 {
element = "th" 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 return err
} }
for _, field := range fields { for _, field := range fields {
@ -174,8 +126,32 @@ func (Renderer) tableRender(ctx *markup.RenderContext, input io.Reader, tmpBlock
row++ row++
} }
if _, err = tmpBlock.WriteString("</table>"); err != nil { if _, err = tmpBlock.WriteString("</table>"); err != nil {
return err return err
} }
// Check if maxRows or maxSize is reached, and if true, warn.
if (row >= maxRows && maxRows != 0) || (rd.InputOffset() >= maxSize && maxSize != 0) {
warn := `<table class="data-table"><tr><td>`
rawLink := ` <a href="` + ctx.Links.RawLink() + `/` + util.PathEscapeSegments(ctx.RelativePath) + `">`
// 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 + `</a></td></tr></table>`
// Write the HTML string to the output
if _, err := tmpBlock.WriteString(warn); err != nil {
return err
}
}
return tmpBlock.Flush() return tmpBlock.Flush()
} }

View file

@ -4,8 +4,6 @@
package markup package markup
import ( import (
"bufio"
"bytes"
"strings" "strings"
"testing" "testing"
@ -31,12 +29,4 @@ func TestRenderCSV(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, v, buf.String()) assert.EqualValues(t, v, buf.String())
} }
t.Run("fallbackRender", func(t *testing.T) {
var buf bytes.Buffer
err := render.fallbackRender(strings.NewReader("1,<a>\n2,<b>"), bufio.NewWriter(&buf))
assert.NoError(t, err)
want := "<pre>1,&lt;a&gt;\n2,&lt;b&gt;</pre>"
assert.Equal(t, want, buf.String())
})
} }

View file

@ -26,6 +26,7 @@ type Mailer struct {
FromEmail string `ini:"-"` FromEmail string `ini:"-"`
SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"` SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"`
SubjectPrefix string `ini:"SUBJECT_PREFIX"` SubjectPrefix string `ini:"SUBJECT_PREFIX"`
OverrideHeader map[string][]string `ini:"-"`
// SMTP sender // SMTP sender
Protocol string `ini:"PROTOCOL"` 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) 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 // Infer SMTPPort if not set
if MailService.SMTPPort == "" { if MailService.SMTPPort == "" {
switch MailService.Protocol { switch MailService.Protocol {

View file

@ -53,6 +53,7 @@ var UI = struct {
CSV struct { CSV struct {
MaxFileSize int64 MaxFileSize int64
MaxRows int
} `ini:"ui.csv"` } `ini:"ui.csv"`
Admin struct { Admin struct {
@ -110,8 +111,10 @@ var UI = struct {
}, },
CSV: struct { CSV: struct {
MaxFileSize int64 MaxFileSize int64
MaxRows int
}{ }{
MaxFileSize: 524288, MaxFileSize: 524288,
MaxRows: 2500,
}, },
Admin: struct { Admin: struct {
UserPagingNum int UserPagingNum int

View file

@ -8,6 +8,9 @@ import "time"
type Activity struct { type Activity struct {
ID int64 `json:"id"` ID int64 `json:"id"`
UserID int64 `json:"user_id"` // Receiver user 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"` OpType string `json:"op_type"`
ActUserID int64 `json:"act_user_id"` ActUserID int64 `json:"act_user_id"`
ActUser *User `json:"act_user"` ActUser *User `json:"act_user"`

44
options/gitignore/Alteryx Normal file
View file

@ -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

View file

@ -14,6 +14,8 @@
*.lzma *.lzma
*.cab *.cab
*.xar *.xar
*.zst
*.tzst
# Packing-only formats # Packing-only formats
*.iso *.iso

View file

@ -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

View file

@ -9,3 +9,4 @@ install_manifest.txt
compile_commands.json compile_commands.json
CTestTestfile.cmake CTestTestfile.cmake
_deps _deps
CMakeUserPresets.json

View file

@ -26,6 +26,18 @@
#*.obj #*.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) # Delphi compiler-generated binaries (safe to delete)
*.exe *.exe
*.dll *.dll

View file

@ -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

View file

@ -20,3 +20,6 @@
# Go workspace file # Go workspace file
go.work go.work
go.work.sum go.work.sum
# env file
.env

View file

@ -5,23 +5,6 @@
## User settings ## User settings
xcuserdata/ 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 ## Obj-C/Swift specific
*.hmap *.hmap

View file

@ -12,3 +12,10 @@ Cargo.lock
# MSVC Windows builds of rustc generate these, which store debugging information # MSVC Windows builds of rustc generate these, which store debugging information
*.pdb *.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/

View file

@ -5,23 +5,6 @@
## User settings ## User settings
xcuserdata/ 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 ## Obj-C/Swift specific
*.hmap *.hmap
@ -66,10 +49,6 @@ playground.xcworkspace
Carthage/Build/ Carthage/Build/
# Accio dependency management
Dependencies/
.accio/
# fastlane # fastlane
# #
# It is recommended to not store the screenshots in the git repo. # It is recommended to not store the screenshots in the git repo.
@ -81,10 +60,3 @@ fastlane/report.xml
fastlane/Preview.html fastlane/Preview.html
fastlane/screenshots/**/*.png fastlane/screenshots/**/*.png
fastlane/test_output fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/

View file

@ -39,6 +39,8 @@
*.synctex.gz *.synctex.gz
*.synctex.gz(busy) *.synctex.gz(busy)
*.pdfsync *.pdfsync
*.rubbercache
rubber.cache
## Build tool directories for auxiliary files ## Build tool directories for auxiliary files
# latexrun # latexrun
@ -138,6 +140,9 @@ acs-*.bib
*.trc *.trc
*.xref *.xref
# hypdoc
*.hd
# hyperref # hyperref
*.brf *.brf

View file

@ -23,6 +23,9 @@ override.tf.json
*_override.tf *_override.tf
*_override.tf.json *_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 # Include override files you do wish to add to version control using negated pattern
# !example_override.tf # !example_override.tf
@ -32,3 +35,6 @@ override.tf.json
# Ignore CLI configuration files # Ignore CLI configuration files
.terraformrc .terraformrc
terraform.rc terraform.rc
# Ignore hcl file
.terraform.lock.hcl

11
options/gitignore/UiPath Normal file
View file

@ -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

View file

@ -47,7 +47,7 @@ SourceArt/**/*.tga
# Binary Files # Binary Files
Binaries/* Binaries/*
Plugins/*/Binaries/* Plugins/**/Binaries/*
# Builds # Builds
Build/* Build/*
@ -68,7 +68,7 @@ Saved/*
# Compiled source files for the engine to use # Compiled source files for the engine to use
Intermediate/* Intermediate/*
Plugins/*/Intermediate/* Plugins/**/Intermediate/*
# Cache files for the editor to use # Cache files for the editor to use
DerivedDataCache/* DerivedDataCache/*

View file

@ -1,6 +1,2 @@
## User settings ## User settings
xcuserdata/ xcuserdata/
## Xcode 8 and earlier
*.xcscmblueprint
*.xccheckout

View file

@ -1,8 +1,5 @@
[tool.poetry] [tool.poetry]
name = "forgejo" package-mode = false
version = "0.0.0"
description = ""
authors = []
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.10" python = "^3.10"

View file

@ -0,0 +1,3 @@
- add [Reviewed-on and Reviewed-by variables](https://codeberg.org/forgejo/forgejo/commit/4ddd9af50fbfcfb2ebf629697a803b3bce56c4af) to the merge template
- [add the `[ui.csv].MAX_ROWS` setting](https://codeberg.org/forgejo/forgejo/commit/433b6c6910f8699dc41787ef8f5148b122b4677e) to avoid displaying a large number of lines (defaults to 2500)
- [add a setting to override or add headers of all outgoing emails](https://codeberg.org/forgejo/forgejo/commit/1d4bff4f65d5e4a3969871ef91d3612daf272b45), for instance `Reply-To` or `In-Reply-To`

View file

@ -0,0 +1 @@
- [NuGet Package fails `choco info pkgname` when `pkgname` is also a substring of another package Id](https://codeberg.org/forgejo/forgejo/commit/c6e04c3c9eddfa6c4bec541f681c8d300b157cdb)

View file

@ -96,20 +96,34 @@ func FeedCapabilityResource(ctx *context.Context) {
xmlResponse(ctx, http.StatusOK, Metadata) 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"), "'") searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'")
if searchTerm == "" { if searchTerm != "" {
return packages_model.SearchValue{
Value: searchTerm,
ExactMatch: false,
}
}
// $filter contains a query like: // $filter contains a query like:
// (((Id ne null) and substringof('microsoft',tolower(Id))) // (((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. // We don't support these queries, just extract the search term.
match := searchTermExtract.FindStringSubmatch(ctx.FormTrim("$filter")) filter := ctx.FormTrim("$filter")
match := searchTermExtract.FindStringSubmatch(filter)
if len(match) == 2 { if len(match) == 2 {
searchTerm = strings.TrimSpace(match[1]) return packages_model.SearchValue{
Value: strings.TrimSpace(match[1]),
ExactMatch: searchTermExact.MatchString(filter),
} }
} }
return searchTerm
return packages_model.SearchValue{}
} }
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
@ -120,9 +134,7 @@ func SearchServiceV2(ctx *context.Context) {
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID, OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet, Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{ Name: getSearchTerm(ctx),
Value: getSearchTerm(ctx),
},
IsInternal: optional.Some(false), IsInternal: optional.Some(false),
Paginator: paginator, Paginator: paginator,
}) })
@ -170,9 +182,7 @@ func SearchServiceV2(ctx *context.Context) {
func SearchServiceV2Count(ctx *context.Context) { func SearchServiceV2Count(ctx *context.Context) {
count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{ count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID, OwnerID: ctx.Package.Owner.ID,
Name: packages_model.SearchValue{ Name: getSearchTerm(ctx),
Value: getSearchTerm(ctx),
},
IsInternal: optional.Some(false), IsInternal: optional.Some(false),
}) })
if err != nil { if err != nil {

View file

@ -158,7 +158,7 @@ func GetContentHistoryDetail(ctx *context.Context) {
// use chroma to render the diff html // use chroma to render the diff html
diffHTMLBuf := bytes.Buffer{} diffHTMLBuf := bytes.Buffer{}
diffHTMLBuf.WriteString("<pre class='chroma' style='tab-size: 4'>") diffHTMLBuf.WriteString("<pre class='chroma'>")
for _, it := range diff { for _, it := range diff {
if it.Type == diffmatchpatch.DiffInsert { if it.Type == diffmatchpatch.DiffInsert {
diffHTMLBuf.WriteString("<span class='gi'>") diffHTMLBuf.WriteString("<span class='gi'>")

View file

@ -57,7 +57,7 @@ func (m *Message) ToMessage() *gomail.Message {
msg.SetHeader(header, m.Headers[header]...) msg.SetHeader(header, m.Headers[header]...)
} }
if len(setting.MailService.SubjectPrefix) > 0 { if setting.MailService.SubjectPrefix != "" {
msg.SetHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject) msg.SetHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject)
} else { } else {
msg.SetHeader("Subject", m.Subject) msg.SetHeader("Subject", m.Subject)
@ -79,6 +79,14 @@ func (m *Message) ToMessage() *gomail.Message {
if len(msg.GetHeader("Message-ID")) == 0 { if len(msg.GetHeader("Message-ID")) == 0 {
msg.SetHeader("Message-ID", m.generateAutoMessageID()) 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 return msg
} }

View file

@ -4,22 +4,22 @@
package mailer package mailer
import ( import (
"strings"
"testing" "testing"
"time" "time"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestGenerateMessageID(t *testing.T) { func TestGenerateMessageID(t *testing.T) {
mailService := setting.Mailer{ defer test.MockVariableValue(&setting.MailService, &setting.Mailer{
From: "test@gitea.com", From: "test@gitea.com",
} })()
defer test.MockVariableValue(&setting.Domain, "localhost")()
setting.MailService = &mailService
setting.Domain = "localhost"
date := time.Date(2000, 1, 2, 3, 4, 5, 6, time.UTC) date := time.Date(2000, 1, 2, 3, 4, 5, 6, time.UTC)
m := NewMessageFrom("", "display-name", "from-address", "subject", "body") m := NewMessageFrom("", "display-name", "from-address", "subject", "body")
@ -39,7 +39,7 @@ func TestGenerateMessageID(t *testing.T) {
} }
func TestGenerateMessageIDForRelease(t *testing.T) { func TestGenerateMessageIDForRelease(t *testing.T) {
setting.Domain = "localhost" defer test.MockVariableValue(&setting.Domain, "localhost")()
rel := repo_model.Release{ rel := repo_model.Release{
ID: 42, ID: 42,
@ -51,3 +51,77 @@ func TestGenerateMessageIDForRelease(t *testing.T) {
m := createMessageIDForRelease(&rel) m := createMessageIDForRelease(&rel)
assert.Equal(t, "<test/tag-test/releases/42@localhost>", m) assert.Equal(t, "<test/tag-test/releases/42@localhost>", m)
} }
func TestToMessage(t *testing.T) {
defer test.MockVariableValue(&setting.MailService, &setting.Mailer{
From: "test@gitea.com",
})()
defer test.MockVariableValue(&setting.Domain, "localhost")()
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\" <test@gitea.com>",
"Message-ID": "<autogen--6795364578871-69c000786adc60dc@localhost>",
"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\" <test@gitea.com>",
"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
}

View file

@ -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 // 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 // process for small repository. For a big repository, save all the data to disk
// before upload is better // 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 { if messenger == nil {
messenger = base.NilMessenger messenger = base.NilMessenger
} }

View file

@ -46,6 +46,9 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue
if err := pr.Issue.LoadPoster(ctx); err != nil { if err := pr.Issue.LoadPoster(ctx); err != nil {
return "", "", err return "", "", err
} }
if err := pr.Issue.LoadRepo(ctx); err != nil {
return "", "", err
}
isExternalTracker := pr.BaseRepo.UnitEnabled(ctx, unit.TypeExternalTracker) isExternalTracker := pr.BaseRepo.UnitEnabled(ctx, unit.TypeExternalTracker)
issueReference := "#" issueReference := "#"
@ -53,6 +56,9 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue
issueReference = "!" issueReference = "!"
} }
reviewedOn := fmt.Sprintf("Reviewed-on: %s/%s", setting.AppURL, pr.Issue.Link())
reviewedBy := pr.GetApprovers(ctx)
if mergeStyle != "" { if mergeStyle != "" {
commit, err := baseGitRepo.GetBranchCommit(pr.BaseRepo.DefaultBranch) commit, err := baseGitRepo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
if err != nil { if err != nil {
@ -83,6 +89,8 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue
"PullRequestPosterName": pr.Issue.Poster.Name, "PullRequestPosterName": pr.Issue.Poster.Name,
"PullRequestIndex": strconv.FormatInt(pr.Index, 10), "PullRequestIndex": strconv.FormatInt(pr.Index, 10),
"PullRequestReference": fmt.Sprintf("%s%d", issueReference, pr.Index), "PullRequestReference": fmt.Sprintf("%s%d", issueReference, pr.Index),
"ReviewedOn": reviewedOn,
"ReviewedBy": reviewedBy,
} }
if pr.HeadRepo != nil { if pr.HeadRepo != nil {
vars["HeadRepoOwnerName"] = pr.HeadRepo.OwnerName vars["HeadRepoOwnerName"] = pr.HeadRepo.OwnerName
@ -122,20 +130,22 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue
return "", "", nil return "", "", nil
} }
body = fmt.Sprintf("%s\n%s", reviewedOn, reviewedBy)
// Squash merge has a different from other styles. // Squash merge has a different from other styles.
if mergeStyle == repo_model.MergeStyleSquash { 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 { 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 { if pr.HeadRepo == nil {
return fmt.Sprintf("Merge pull request '%s' (%s%d) from <deleted>:%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 <deleted>:%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) { func expandDefaultMergeMessage(template string, vars map[string]string) (message, body string) {

View file

@ -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. // 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 oldRepoName := repo.Name
newRepoName = strings.ToLower(newRepoName) newRepoName = strings.ToLower(newRepoName)
if err = repo_model.IsUsableRepoName(newRepoName); err != nil { 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. // local copy's origin accordingly.
repoWorkingPool.CheckIn(fmt.Sprint(repo.ID)) 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)) repoWorkingPool.CheckOut(fmt.Sprint(repo.ID))
return err return err
} }

View file

@ -1,6 +1,7 @@
import {readFileSync} from 'node:fs'; import {readFileSync} from 'node:fs';
import {env} from 'node:process'; import {env} from 'node:process';
import {parse} from 'postcss'; import {parse} from 'postcss';
import plugin from 'tailwindcss/plugin.js';
const isProduction = env.NODE_ENV !== 'development'; 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',
},
});
}),
],
}; };

View file

@ -5,7 +5,7 @@
{{ctx.Locale.Tr "admin.packages.total_size" (ctx.Locale.TrSize .TotalBlobSize)}}, {{ctx.Locale.Tr "admin.packages.total_size" (ctx.Locale.TrSize .TotalBlobSize)}},
{{ctx.Locale.Tr "admin.packages.unreferenced_size" (ctx.Locale.TrSize .TotalUnreferencedBlobSize)}}) {{ctx.Locale.Tr "admin.packages.unreferenced_size" (ctx.Locale.TrSize .TotalUnreferencedBlobSize)}})
<div class="ui right"> <div class="ui right">
<form method="post" action="/admin/packages/cleanup"> <form method="post" action="{{AppSubUrl}}/admin/packages/cleanup">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<button class="ui primary tiny button">{{ctx.Locale.Tr "admin.packages.cleanup"}}</button> <button class="ui primary tiny button">{{ctx.Locale.Tr "admin.packages.cleanup"}}</button>
</form> </form>

View file

@ -25,7 +25,7 @@
<div><label><input name="check" type="checkbox"> check</label></div> <div><label><input name="check" type="checkbox"> check</label></div>
<div><button name="btn">submit post</button></div> <div><button name="btn">submit post</button></div>
</form> </form>
<form method="post" action="/no-such-uri" class="form-fetch-action"> <form method="post" action="no-such-uri" class="form-fetch-action">
<div class="tw-py-8">bad action url</div> <div class="tw-py-8">bad action url</div>
<div><button name="btn">submit test</button></div> <div><button name="btn">submit test</button></div>
</form> </form>

View file

@ -66,7 +66,7 @@
<div id="project-board"> <div id="project-board">
<div class="board {{if .CanWriteProjects}}sortable{{end}}"{{if .CanWriteProjects}} data-url="{{$.Link}}/move"{{end}}> <div class="board {{if .CanWriteProjects}}sortable{{end}}"{{if .CanWriteProjects}} data-url="{{$.Link}}/move"{{end}}>
{{range .Columns}} {{range .Columns}}
<div class="ui segment project-column"{{if .Color}} style="background: {{.Color}} !important; color: {{ContrastColor .Color}} !important"{{end}} data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.Link}}/{{.ID}}"> <div class="project-column"{{if .Color}} style="background: {{.Color}} !important; color: {{ContrastColor .Color}} !important"{{end}} data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.Link}}/{{.ID}}">
<div class="project-column-header{{if $canWriteProject}} tw-cursor-grab{{end}}"> <div class="project-column-header{{if $canWriteProject}} tw-cursor-grab{{end}}">
<div class="ui large label project-column-title tw-py-1"> <div class="ui large label project-column-title tw-py-1">
<div class="ui small circular grey label project-column-issue-count"> <div class="ui small circular grey label project-column-issue-count">

View file

@ -1,6 +1,6 @@
{{range .RecentlyPushedNewBranches}} {{range .RecentlyPushedNewBranches}}
<div class="ui positive message tw-flex tw-items-center"> <div class="ui positive message tw-flex tw-items-center tw-gap-2">
<div class="tw-flex-1"> <div class="tw-flex-1 tw-break-anywhere">
{{$timeSince := TimeSince .CommitTime.AsTime ctx.Locale}} {{$timeSince := TimeSince .CommitTime.AsTime ctx.Locale}}
{{$repo := .GetRepo $.Context}} {{$repo := .GetRepo $.Context}}
{{$name := .Name}} {{$name := .Name}}

View file

@ -14,7 +14,7 @@
<div class="issue-card-icon"> <div class="issue-card-icon">
{{template "shared/issueicon" .}} {{template "shared/issueicon" .}}
</div> </div>
<a class="issue-card-title muted issue-title" href="{{.Link}}">{{.Title | RenderEmoji ctx | RenderCodeBlock}}</a> <a class="issue-card-title muted issue-title tw-break-anywhere" href="{{.Link}}">{{.Title | RenderEmoji ctx | RenderCodeBlock}}</a>
{{if and $.isPinnedIssueCard $.Page.IsRepoAdmin}} {{if and $.isPinnedIssueCard $.Page.IsRepoAdmin}}
<a role="button" class="issue-card-unpin muted tw-flex tw-items-center" data-tooltip-content={{ctx.Locale.Tr "repo.issues.unpin_issue"}} data-issue-id="{{.ID}}" data-unpin-url="{{$.Page.Link}}/unpin/{{.Index}}"> <a role="button" class="issue-card-unpin muted tw-flex tw-items-center" data-tooltip-content={{ctx.Locale.Tr "repo.issues.unpin_issue"}} data-issue-id="{{.ID}}" data-unpin-url="{{$.Page.Link}}/unpin/{{.Index}}">
{{svg "octicon-x" 16}} {{svg "octicon-x" 16}}

View file

@ -199,7 +199,6 @@
{{if .AllowMerge}} {{/* user is allowed to merge */}} {{if .AllowMerge}} {{/* user is allowed to merge */}}
{{$prUnit := .Repository.MustGetUnit $.Context $.UnitTypePullRequests}} {{$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}} {{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash $prUnit.PullRequestsConfig.AllowFastForwardOnly}}
{{$hasPendingPullRequestMergeTip := ""}} {{$hasPendingPullRequestMergeTip := ""}}
{{if .HasPendingPullRequestMerge}} {{if .HasPendingPullRequestMerge}}
@ -208,11 +207,10 @@
{{end}} {{end}}
<div class="divider"></div> <div class="divider"></div>
<script type="module"> <script type="module">
const issueUrl = window.location.origin + {{$.Issue.Link}};
const defaultMergeTitle = {{.DefaultMergeMessage}}; const defaultMergeTitle = {{.DefaultMergeMessage}};
const defaultSquashMergeTitle = {{.DefaultSquashMergeMessage}}; const defaultSquashMergeTitle = {{.DefaultSquashMergeMessage}};
const defaultMergeMessage = {{if .DefaultMergeBody}}{{.DefaultMergeBody}}{{else}}`Reviewed-on: ${issueUrl}\n` + {{$approvers}}{{end}}; const defaultMergeMessage = {{.DefaultMergeBody}};
const defaultSquashMergeMessage = {{if .DefaultSquashMergeBody}}{{.DefaultSquashMergeBody}}{{else}}`Reviewed-on: ${issueUrl}\n` + {{$approvers}}{{end}}; const defaultSquashMergeMessage = {{.DefaultSquashMergeBody}};
const mergeForm = { const mergeForm = {
'baseLink': {{.Link}}, 'baseLink': {{.Link}},
'textCancel': {{ctx.Locale.Tr "cancel"}}, 'textCancel': {{ctx.Locale.Tr "cancel"}},

View file

@ -70,7 +70,7 @@
<td><p data-tooltip-content="{{.Description}}">{{.Name}}</p></td> <td><p data-tooltip-content="{{.Description}}">{{.Name}}</p></td>
<td>{{if .Version}}{{.Version}}{{else}}{{ctx.Locale.Tr "unknown"}}{{end}}</td> <td>{{if .Version}}{{.Version}}{{else}}{{ctx.Locale.Tr "unknown"}}{{end}}</td>
<td><span data-tooltip-content="{{.BelongsToOwnerName}}">{{.BelongsToOwnerType.LocaleString ctx.Locale}}</span></td> <td><span data-tooltip-content="{{.BelongsToOwnerName}}">{{.BelongsToOwnerType.LocaleString ctx.Locale}}</span></td>
<td class="runner-tags"> <td class="tw-flex tw-flex-wrap tw-gap-2 runner-tags">
{{range .AgentLabels}}<span class="ui label">{{.}}</span>{{end}} {{range .AgentLabels}}<span class="ui label">{{.}}</span>{{end}}
</td> </td>
<td>{{if .LastOnline}}{{TimeSinceUnix .LastOnline ctx.Locale}}{{else}}{{ctx.Locale.Tr "never"}}{{end}}</td> <td>{{if .LastOnline}}{{TimeSinceUnix .LastOnline ctx.Locale}}{{else}}{{ctx.Locale.Tr "never"}}{{end}}</td>

View file

@ -18537,7 +18537,37 @@
"x-go-name": "IsPrivate" "x-go-name": "IsPrivate"
}, },
"op_type": { "op_type": {
"description": "the type of action",
"type": "string", "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" "x-go-name": "OpType"
}, },
"ref_name": { "ref_name": {

View file

@ -49,14 +49,14 @@
{{end}} {{end}}
</div> </div>
<a class="notifications-link tw-flex tw-flex-1 tw-flex-col silenced" href="{{.Link ctx}}"> <a class="notifications-link tw-flex tw-flex-1 tw-flex-col silenced" href="{{.Link ctx}}">
<div class="notifications-top-row tw-text-13"> <div class="notifications-top-row tw-text-13 tw-break-anywhere">
{{.Repository.FullName}} {{if .Issue}}<span class="text light-3">#{{.Issue.Index}}</span>{{end}} {{.Repository.FullName}} {{if .Issue}}<span class="text light-3">#{{.Issue.Index}}</span>{{end}}
{{if eq .Status 3}} {{if eq .Status 3}}
{{svg "octicon-pin" 13 "text blue tw-mt-0.5 tw-ml-1"}} {{svg "octicon-pin" 13 "text blue tw-mt-0.5 tw-ml-1"}}
{{end}} {{end}}
</div> </div>
<div class="notifications-bottom-row tw-text-16 tw-py-0.5"> <div class="notifications-bottom-row tw-text-16 tw-py-0.5">
<span class="issue-title"> <span class="issue-title tw-break-anywhere">
{{if .Issue}} {{if .Issue}}
{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}} {{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}
{{else}} {{else}}

View file

@ -75,7 +75,7 @@
{{ctx.Locale.Tr "settings.select_permissions"}} {{ctx.Locale.Tr "settings.select_permissions"}}
</summary> </summary>
<p class="activity meta"> <p class="activity meta">
<p>{{ctx.Locale.Tr "settings.access_token_desc" (`href="/api/swagger" target="_blank"`|SafeHTML) (`href="https://forgejo.org/docs/latest/user/token-scope/" target="_blank"`|SafeHTML)}}</p> <p>{{ctx.Locale.Tr "settings.access_token_desc" (HTMLFormat `href="%s/api/swagger" target="_blank"` AppSubUrl) (`href="https://forgejo.org/docs/latest/user/token-scope/" target="_blank"`|SafeHTML)}}</p>
</p> </p>
<div class="scoped-access-token-mount"> <div class="scoped-access-token-mount">
<scoped-access-token-selector <scoped-access-token-selector

View file

@ -436,17 +436,28 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
Take int Take int
ExpectedTotal int64 ExpectedTotal int64
ExpectedResults int ExpectedResults int
ExpectedExactMatch bool
}{ }{
{"", 0, 0, 1, 1}, {"", 0, 0, 4, 4, false},
{"", 0, 10, 1, 1}, {"", 0, 10, 4, 4, false},
{"gitea", 0, 10, 0, 0}, {"gitea", 0, 10, 0, 0, false},
{"test", 0, 10, 1, 1}, {"test", 0, 10, 1, 1, false},
{"test", 1, 10, 1, 0}, {"test", 1, 10, 1, 0, false},
{"almost.similar", 0, 0, 3, 3, true},
} }
req := NewRequestWithBody(t, "PUT", url, createPackage(packageName, "1.0.99")). 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) AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated) MakeRequest(t, req, http.StatusCreated)
}
t.Run("v2", func(t *testing.T) { t.Run("v2", func(t *testing.T) {
t.Run("Search()", 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) { t.Run("Next", func(t *testing.T) {
req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='test'&$skip=0&$top=1", url)). req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='test'&$skip=0&$top=1", url)).
AddBasicAuth(user.Name) AddBasicAuth(user.Name)
@ -550,9 +618,11 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
}) })
}) })
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, "1.0.99")). for _, fakePackageName := range fakePackages {
req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, fakePackageName, "1.0.99")).
AddBasicAuth(user.Name) AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusNoContent)
}
}) })
t.Run("RegistrationService", func(t *testing.T) { t.Run("RegistrationService", func(t *testing.T) {

View file

@ -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.zip", tags[0].ZipballURL)
assert.Equal(t, setting.AppURL+"user2/repo1/archive/v1.1.tar.gz", tags[0].TarballURL) 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) resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &tags) DecodeJSON(t, resp, &tags)
assert.Len(t, tags, 2) assert.Len(t, tags, 2)
@ -72,7 +72,7 @@ func TestAPIRepoTags(t *testing.T) {
MakeRequest(t, req, http.StatusNotFound) 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) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags", ownerName, repoName)
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateTagOption{ req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateTagOption{
TagName: name, TagName: name,
@ -96,7 +96,7 @@ func TestAPIGetTagArchiveDownloadCount(t *testing.T) {
repoName := "repo1" repoName := "repo1"
tagName := "TagDownloadCount" 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) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags/%s?token=%s", user.Name, repoName, tagName, token)

View file

@ -237,7 +237,7 @@ func (c *compareDump) assertLoadFiles(beforeFilename, afterFilename string, t re
// //
// Given []Something{} create afterPtr, beforePtr []*Something{} // 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) beforeSlice := reflect.MakeSlice(sliceType, 0, 10)
beforePtr = reflect.New(beforeSlice.Type()) beforePtr = reflect.New(beforeSlice.Type())
beforePtr.Elem().Set(beforeSlice) beforePtr.Elem().Set(beforeSlice)

View file

@ -36,7 +36,7 @@ func TestGPGGit(t *testing.T) {
defer os.Setenv("GNUPGHOME", oldGNUPGHome) defer os.Setenv("GNUPGHOME", oldGNUPGHome)
// Need to create a root key // Need to create a root key
rootKeyPair, err := importTestingKey(tmpDir, "gitea", "gitea@fake.local") rootKeyPair, err := importTestingKey()
if !assert.NoError(t, err, "importTestingKey") { if !assert.NoError(t, err, "importTestingKey") {
return 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{ return doAPICreateFile(ctx, path, &api.CreateFileOptions{
FileOptions: api.FileOptions{ FileOptions: api.FileOptions{
BranchName: from, BranchName: from,
@ -282,7 +282,7 @@ func crudActionCreateFile(t *testing.T, ctx APITestContext, user *user_model.Use
}, callback...) }, 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 { if _, _, err := process.GetManager().Exec("gpg --import tests/integration/private-testing.key", "gpg", "--import", "tests/integration/private-testing.key"); err != nil {
return nil, err return nil, err
} }

23
tools/lint-go-gopls.sh Executable file
View file

@ -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

View file

@ -9,6 +9,7 @@
.project-column { .project-column {
background-color: var(--color-project-column-bg) !important; background-color: var(--color-project-column-bg) !important;
border: 1px solid var(--color-secondary) !important; border: 1px solid var(--color-secondary) !important;
border-radius: var(--border-radius);
margin: 0 0.5rem !important; margin: 0 0.5rem !important;
padding: 0.5rem !important; padding: 0.5rem !important;
width: 320px; width: 320px;

View file

@ -35,22 +35,6 @@ Gitea's private styles use `g-` prefix.
.interact-bg:hover { background: var(--color-hover) !important; } .interact-bg:hover { background: var(--color-hover) !important; }
.interact-bg:active { background: var(--color-active) !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) { @media (max-width: 767.98px) {
/* double selector so it wins over .tw-flex (old .gt-df) etc */ /* double selector so it wins over .tw-flex (old .gt-df) etc */
.not-mobile.not-mobile { .not-mobile.not-mobile {

View file

@ -2489,6 +2489,7 @@ tbody.commit-list {
min-height: 12em; min-height: 12em;
max-height: calc(100vh - 10.5rem); max-height: calc(100vh - 10.5rem);
overflow-y: auto; overflow-y: auto;
tab-size: 4;
} }
.comment-diff-data pre { .comment-diff-data pre {