diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 78042a7598101..ed0309dbea3c2 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -558,7 +558,6 @@ rules: prefer-rest-params: [2] prefer-spread: [2] prefer-template: [2] - quotes: [2, single, {avoidEscape: true, allowTemplateLiterals: true}] radix: [2, as-needed] regexp/confusing-quantifier: [2] regexp/control-character-escape: [2] @@ -811,7 +810,7 @@ rules: wc/no-constructor-params: [2] wc/no-constructor: [2] wc/no-customized-built-in-elements: [2] - wc/no-exports-with-element: [2] + wc/no-exports-with-element: [0] wc/no-invalid-element-name: [2] wc/no-invalid-extends: [2] wc/no-method-prefixed-with-on: [2] diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index 0472d9a9f07e8..391137f015d7d 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -64,6 +64,18 @@ jobs: - run: make deps-frontend - run: make lint-swagger + lint-spell: + if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.templates == 'true' + needs: files-changed + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + check-latest: true + - run: make lint-spell + lint-go-windows: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed diff --git a/.gitpod.yml b/.gitpod.yml index 35b22c45ae7d9..ed2f57f4bf6cd 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -10,10 +10,19 @@ tasks: - name: Run backend command: | gp sync-await setup - if [ ! -f custom/conf/app.ini ] - then + + # Get the URL and extract the domain + url=$(gp url 3000) + domain=$(echo $url | awk -F[/:] '{print $4}') + + if [ -f custom/conf/app.ini ]; then + sed -i "s|^ROOT_URL =.*|ROOT_URL = ${url}/|" custom/conf/app.ini + sed -i "s|^DOMAIN =.*|DOMAIN = ${domain}|" custom/conf/app.ini + sed -i "s|^SSH_DOMAIN =.*|SSH_DOMAIN = ${domain}|" custom/conf/app.ini + sed -i "s|^NO_REPLY_ADDRESS =.*|SSH_DOMAIN = noreply.${domain}|" custom/conf/app.ini + else mkdir -p custom/conf/ - echo -e "[server]\nROOT_URL=$(gp url 3000)/" > custom/conf/app.ini + echo -e "[server]\nROOT_URL = ${url}/" > custom/conf/app.ini echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini fi export TAGS="sqlite sqlite_unlock_notify" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 96b02edd5bf1c..f9b9a421a372f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,6 +47,7 @@ - [Release Cycle](#release-cycle) - [Maintainers](#maintainers) - [Technical Oversight Committee (TOC)](#technical-oversight-committee-toc) + - [TOC election process](#toc-election-process) - [Current TOC members](#current-toc-members) - [Previous TOC/owners members](#previous-tocowners-members) - [Governance Compensation](#governance-compensation) @@ -486,36 +487,53 @@ if possible provide GPG signed commits. https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/ https://help.github.com/articles/signing-commits-with-gpg/ +Furthermore, any account with write access (like bots and TOC members) **must** use 2FA. +https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/ + ## Technical Oversight Committee (TOC) -At the start of 2023, the `Owners` team was dissolved. Instead, the governance charter proposed a technical oversight committee (TOC) which expands the ownership team of the Gitea project from three elected positions to six positions. Three positions would be elected as it has been over the past years, and the other three would consist of appointed members from the Gitea company. +At the start of 2023, the `Owners` team was dissolved. Instead, the governance charter proposed a technical oversight committee (TOC) which expands the ownership team of the Gitea project from three elected positions to six positions. Three positions are elected as it has been over the past years, and the other three consist of appointed members from the Gitea company. https://blog.gitea.com/quarterly-23q1/ -When the new community members have been elected, the old members will give up ownership to the newly elected members. For security reasons, TOC members or any account with write access (like a bot) must use 2FA. -https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/ +### TOC election process + +Any maintainer is eligible to be part of the community TOC if they are not associated with the Gitea company. +A maintainer can either nominate themselves, or can be nominated by other maintainers to be a candidate for the TOC election. +If you are nominated by someone else, you must first accept your nomination before the vote starts to be a candidate. + +The TOC is elected for one year, the TOC election happens yearly. +After the announcement of the results of the TOC election, elected members have two weeks time to confirm or refuse the seat. +If an elected member does not answer within this timeframe, they are automatically assumed to refuse the seat. +Refusals result in the person with the next highest vote getting the same choice. +As long as seats are empty in the TOC, members of the previous TOC can fill them until an elected member accepts the seat. + +If an elected member that accepts the seat does not have 2FA configured yet, they will be temporarily counted as `answer pending` until they manage to configure 2FA, thus leaving their seat empty for this duration. ### Current TOC members -- 2023-01-01 ~ 2023-12-31 - https://blog.gitea.com/quarterly-23q1/ +- 2024-01-01 ~ 2024-12-31 - Company - [Jason Song](https://gitea.com/wolfogre) - [Lunny Xiao](https://gitea.com/lunny) - - [Matti Ranta](https://gitea.com/techknowlogick) + - [Matti Ranta](https://gitea.com/techknowlogick) - Community - [6543](https://gitea.com/6543) <6543@obermui.de> - - [Andrew Thornton](https://gitea.com/zeripath) + - [delvh](https://gitea.com/delvh) - [John Olheiser](https://gitea.com/jolheiser) ### Previous TOC/owners members Here's the history of the owners and the time they served: -- [Lunny Xiao](https://gitea.com/lunny) - 2016, 2017, [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872) +- [Lunny Xiao](https://gitea.com/lunny) - 2016, 2017, [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023 - [Kim Carlbäcker](https://github.com/bkcsoft) - 2016, 2017 - [Thomas Boerger](https://gitea.com/tboerger) - 2016, 2017 - [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) - [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801) -- [Matti Ranta](https://gitea.com/techknowlogick) - [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872) -- [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872) +- [Matti Ranta](https://gitea.com/techknowlogick) - [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023 +- [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023 +- [6543](https://gitea.com/6543) - 2023 +- [John Olheiser](https://gitea.com/jolheiser) - 2023 +- [Jason Song](https://gitea.com/wolfogre) - 2023 ## Governance Compensation diff --git a/Makefile b/Makefile index 273ae1fa68045..366ca6c624023 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-che GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 -MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4 +MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.5 XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 @@ -146,6 +146,8 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN GO_DIRS := build cmd models modules routers services tests WEB_DIRS := web_src/js web_src/css +SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github + GO_SOURCES := $(wildcard *.go) GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go) GO_SOURCES += $(GENERATED_GO_DEST) @@ -219,6 +221,8 @@ help: @echo " - lint-swagger lint swagger files" @echo " - lint-templates lint template files" @echo " - lint-yaml lint yaml files" + @echo " - lint-spell lint spelling" + @echo " - lint-spell-fix lint spelling and fix issues" @echo " - checks run various consistency checks" @echo " - checks-frontend check frontend files" @echo " - checks-backend check backend files" @@ -308,10 +312,6 @@ fmt-check: fmt exit 1; \ fi -.PHONY: misspell-check -misspell-check: - go run $(MISSPELL_PACKAGE) -error $(GO_DIRS) $(WEB_DIRS) - .PHONY: $(TAGS_EVIDENCE) $(TAGS_EVIDENCE): @mkdir -p $(MAKE_EVIDENCE_DIR) @@ -351,13 +351,13 @@ checks: checks-frontend checks-backend checks-frontend: lockfile-check svg-check .PHONY: checks-backend -checks-backend: tidy-check swagger-check fmt-check misspell-check swagger-validate security-check +checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check .PHONY: lint -lint: lint-frontend lint-backend +lint: lint-frontend lint-backend lint-spell .PHONY: lint-fix -lint-fix: lint-frontend-fix lint-backend-fix +lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix .PHONY: lint-frontend lint-frontend: lint-js lint-css @@ -395,6 +395,14 @@ lint-swagger: node_modules lint-md: node_modules npx markdownlint docs *.md +.PHONY: lint-spell +lint-spell: + @go run $(MISSPELL_PACKAGE) -error $(SPELLCHECK_FILES) + +.PHONY: lint-spell-fix +lint-spell-fix: + @go run $(MISSPELL_PACKAGE) -w $(SPELLCHECK_FILES) + .PHONY: lint-go lint-go: $(GO) run $(GOLANGCI_LINT_PACKAGE) run diff --git a/README.md b/README.md index 5356fcfacd52e..174e37769c1bb 100644 --- a/README.md +++ b/README.md @@ -97,17 +97,17 @@ More info: https://docs.gitea.com/installation/install-from-source ./gitea web -NOTE: If you're interested in using our APIs, we have experimental -support with [documentation](https://try.gitea.io/api/swagger). +> [!NOTE] +> If you're interested in using our APIs, we have experimental support with [documentation](https://try.gitea.io/api/swagger). ## Contributing Expected workflow is: Fork -> Patch -> Push -> Pull Request -NOTES: - -1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.** -2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks! +> [!NOTE] +> +> 1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.** +> 2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks! ## Translating diff --git a/cmd/serv.go b/cmd/serv.go index 726663660b08e..3cc504beb476e 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -216,16 +216,18 @@ func runServ(c *cli.Context) error { } } - // LowerCase and trim the repoPath as that's how they are stored. - repoPath = strings.ToLower(strings.TrimSpace(repoPath)) - rr := strings.SplitN(repoPath, "/", 2) if len(rr) != 2 { return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath) } - username := strings.ToLower(rr[0]) - reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git")) + username := rr[0] + reponame := strings.TrimSuffix(rr[1], ".git") + + // LowerCase and trim the repoPath as that's how they are stored. + // This should be done after splitting the repoPath into username and reponame + // so that username and reponame are not affected. + repoPath = strings.ToLower(strings.TrimSpace(repoPath)) if alphaDashDotPattern.MatchString(reponame) { return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 363bbcb15133a..4aae1c497f3fa 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1044,7 +1044,7 @@ LEVEL = Info ;; List of keywords used in Pull Request comments to automatically reopen a related issue ;REOPEN_KEYWORDS = reopen,reopens,reopened ;; -;; Set default merge style for repository creating, valid options: merge, rebase, rebase-merge, squash +;; Set default merge style for repository creating, valid options: merge, rebase, rebase-merge, squash, fast-forward-only ;DEFAULT_MERGE_STYLE = merge ;; ;; In the default merge message for squash commits include at most this many commits diff --git a/docs/content/administration/backup-and-restore.zh-cn.md b/docs/content/administration/backup-and-restore.zh-cn.md index 98d378d5dcb71..db7eba84f7e03 100644 --- a/docs/content/administration/backup-and-restore.zh-cn.md +++ b/docs/content/administration/backup-and-restore.zh-cn.md @@ -19,6 +19,12 @@ menu: Gitea 已经实现了 `dump` 命令可以用来备份所有需要的文件到一个zip压缩文件。该压缩文件可以被用来进行数据恢复。 +## 备份一致性 + +为了确保 Gitea 实例的一致性,在备份期间必须关闭它。 + +Gitea 包括数据库、文件和 Git 仓库,当它被使用时所有这些都会发生变化。例如,当迁移正在进行时,在数据库中创建一个事务,而 Git 仓库正在被复制。如果备份发生在迁移的中间,Git 仓库可能是不完整的,尽管数据库声称它是完整的,因为它是在之后被转储的。避免这种竞争条件的唯一方法是在备份期间停止 Gitea 实例。 + ## 备份命令 (`dump`) 先转到git用户的权限: `su git`. 再Gitea目录运行 `./gitea dump`。一般会显示类似如下的输出: @@ -34,15 +40,43 @@ Gitea 已经实现了 `dump` 命令可以用来备份所有需要的文件到一 最后生成的 `gitea-dump-1482906742.zip` 文件将会包含如下内容: -* `custom` - 所有保存在 `custom/` 目录下的配置和自定义的文件。 -* `data` - 数据目录下的所有内容不包含使用文件session的文件。该目录包含 `attachments`, `avatars`, `lfs`, `indexers`, 如果使用sqlite 还会包含 sqlite 数据库文件。 +* `app.ini` - 如果原先存储在默认的 custom/ 目录之外,则是配置文件的可选副本 +* `custom/` - 所有保存在 `custom/` 目录下的配置和自定义的文件。 +* `data/` - 数据目录(APP_DATA_PATH),如果使用文件会话,则不包括会话。该目录包括 `attachments`、`avatars`、`lfs`、`indexers`、如果使用 SQLite 则包括 SQLite 文件。 +* `repos/` - 仓库目录的完整副本。 * `gitea-db.sql` - 数据库dump出来的 SQL。 -* `gitea-repo.zip` - Git仓库压缩文件。 * `log/` - Logs文件,如果用作迁移不是必须的。 中间备份文件将会在临时目录进行创建,如果您要重新指定临时目录,可以用 `--tempdir` 参数,或者用 `TMPDIR` 环境变量。 -## Restore Command (`restore`) +## 备份数据库 + +`gitea dump` 创建的 SQL 转储使用 XORM,Gitea 管理员可能更喜欢使用本地的 MySQL 和 PostgreSQL 转储工具。使用 XORM 转储数据库时仍然存在一些问题,可能会导致在尝试恢复时出现问题。 + +```sh +# mysql +mysqldump -u$USER -p$PASS --database $DATABASE > gitea-db.sql +# postgres +pg_dump -U $USER $DATABASE > gitea-db.sql +``` + +### 使用Docker (`dump`) + +在使用 Docker 时,使用 `dump` 命令有一些注意事项。 + +必须以 `gitea/conf/app.ini` 中指定的 `RUN_USER = ` 执行该命令;并且,为了让备份文件夹的压缩过程能够顺利执行,`docker exec` 命令必须在 `--tempdir` 内部执行。 + +示例: + +```none +docker exec -u -it -w <--tempdir> $(docker ps -qf 'name=^$') bash -c '/usr/local/bin/gitea dump -c ' +``` + +\*注意:`--tempdir` 指的是 Gitea 使用的 Docker 环境的临时目录;如果您没有指定自定义的 `--tempdir`,那么 Gitea 将使用 `/tmp` 或 Docker 容器的 `TMPDIR` 环境变量。对于 `--tempdir`,请相应调整您的 `docker exec` 命令选项。 + +结果应该是一个文件,存储在指定的 `--tempdir` 中,类似于:`gitea-dump-1482906742.zip` + +## 恢复命令 (`restore`) 当前还没有恢复命令,恢复需要人工进行。主要是把文件和数据库进行恢复。 @@ -51,10 +85,10 @@ Gitea 已经实现了 `dump` 命令可以用来备份所有需要的文件到一 ```sh unzip gitea-dump-1610949662.zip cd gitea-dump-1610949662 -mv data/conf/app.ini /etc/gitea/conf/app.ini +mv app.ini /etc/gitea/conf/app.ini mv data/* /var/lib/gitea/data/ mv log/* /var/lib/gitea/log/ -mv repos/* /var/lib/gitea/repositories/ +mv repos/* /var/lib/gitea/gitea-repositories/ chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea # mysql @@ -66,3 +100,55 @@ psql -U $USER -d $DATABASE < gitea-db.sql service gitea restart ``` + +如果安装方式发生了变化(例如 二进制 -> Docker),或者 Gitea 安装到了与之前安装不同的目录,则需要重新生成仓库 Git 钩子。 + +在 Gitea 运行时,并从 Gitea 二进制文件所在的目录执行:`./gitea admin regenerate hooks` + +这样可以确保仓库 Git 钩子中的应用程序和配置文件路径与当前安装一致。如果这些路径没有更新,仓库的 `push` 操作将失败。 + +### 使用 Docker (`restore`) + +在基于 Docker 的 Gitea 实例中,也没有恢复命令的支持。恢复过程与前面描述的步骤相同,但路径不同。 + +示例: + +```sh +# 在容器中打开 bash 会话 +docker exec --user git -it 2a83b293548e bash +# 在容器内解压您的备份文件 +unzip gitea-dump-1610949662.zip +cd gitea-dump-1610949662 +# 恢复 Gitea 数据 +mv data/* /data/gitea +# 恢复仓库本身 +mv repos/* /data/git/gitea-repositories/ +# 调整文件权限 +chown -R git:git /data +# 重新生成 Git 钩子 +/usr/local/bin/gitea -c '/data/gitea/conf/app.ini' admin regenerate hooks +``` + +Gitea 容器中的默认用户是 `git`(1000:1000)。请用您的 Gitea 容器 ID 或名称替换 `2a83b293548e`。 + +### 使用 Docker-rootless (`restore`) + +在 Docker-rootless 容器中的恢复工作流程只是要使用的目录不同: + +```sh +# 在容器中打开 bash 会话 +docker exec --user git -it 2a83b293548e bash +# 在容器内解压您的备份文件 +unzip gitea-dump-1610949662.zip +cd gitea-dump-1610949662 +# 恢复 app.ini +mv data/conf/app.ini /etc/gitea/app.ini +# 恢复 Gitea 数据 +mv data/* /var/lib/gitea +# 恢复仓库本身 +mv repos/* /var/lib/gitea/git/gitea-repositories +# 调整文件权限 +chown -R git:git /etc/gitea/app.ini /var/lib/gitea +# 重新生成 Git 钩子 +/usr/local/bin/gitea -c '/etc/gitea/app.ini' admin regenerate hooks +``` diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 33732d080bff5..415176d4fff28 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -126,7 +126,7 @@ In addition, there is _`StaticRootPath`_ which can be set as a built-in at build keywords used in Pull Request comments to automatically close a related issue - `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: List of keywords used in Pull Request comments to automatically reopen a related issue -- `DEFAULT_MERGE_STYLE`: **merge**: Set default merge style for repository creating, valid options: `merge`, `rebase`, `rebase-merge`, `squash` +- `DEFAULT_MERGE_STYLE`: **merge**: Set default merge style for repository creating, valid options: `merge`, `rebase`, `rebase-merge`, `squash`, `fast-forward-only` - `DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT`: **50**: In the default merge message for squash commits include at most this many commits. Set to `-1` to include all commits - `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: In the default merge message for squash commits limit the size of the commit messages. Set to `-1` to have no limit. Only used if `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES` is `true`. - `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: In the default merge message for squash commits walk all commits to include all authors in the Co-authored-by otherwise just use those in the limited list diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md index 2cee70daabe98..01906930cb9e0 100644 --- a/docs/content/administration/config-cheat-sheet.zh-cn.md +++ b/docs/content/administration/config-cheat-sheet.zh-cn.md @@ -29,7 +29,7 @@ menu: [ini](https://github.com/go-ini/ini/#recursive-values) 这里的说明。 标注了 :exclamation: 的配置项表明除非你真的理解这个配置项的意义,否则最好使用默认值。 -在下面的默认值中,`$XYZ`代表环境变量`XYZ`的值(详见:`enviroment-to-ini`)。 _`XxYyZz`_是指默认配置的一部分列出的值。这些在 app.ini 文件中不起作用,仅在此处列出作为文档说明。 +在下面的默认值中,`$XYZ`代表环境变量`XYZ`的值(详见:`environment-to-ini`)。 _`XxYyZz`_是指默认配置的一部分列出的值。这些在 app.ini 文件中不起作用,仅在此处列出作为文档说明。 包含`#`或者`;`的变量必须使用引号(`` ` ``或者`""""`)包裹,否则会被解析为注释。 @@ -125,7 +125,7 @@ menu: - `CLOSE_KEYWORDS`: **close**, **closes**, **closed**, **fix**, **fixes**, **fixed**, **resolve**, **resolves**, **resolved**: 在拉取请求评论中用于自动关闭相关问题的关键词列表。 - `REOPEN_KEYWORDS`: **reopen**, **reopens**, **reopened**: 在拉取请求评论中用于自动重新打开相关问题的 关键词列表。 -- `DEFAULT_MERGE_STYLE`: **merge**: 设置创建仓库的默认合并方式,可选: `merge`, `rebase`, `rebase-merge`, `squash` +- `DEFAULT_MERGE_STYLE`: **merge**: 设置创建仓库的默认合并方式,可选: `merge`, `rebase`, `rebase-merge`, `squash`, `fast-forward-only` - `DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT`: **50**: 在默认合并消息中,对于`squash`提交,最多包括此数量的提交。设置为 -1 以包括所有提交。 - `DEFAULT_MERGE_MESSAGE_SIZE`: **5120**: 在默认的合并消息中,对于`squash`提交,限制提交消息的大小。设置为 `-1`以取消限制。仅在`POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`为`true`时使用。 - `DEFAULT_MERGE_MESSAGE_ALL_AUTHORS`: **false**: 在默认合并消息中,对于`squash`提交,遍历所有提交以包括所有作者的`Co-authored-by`,否则仅使用限定列表中的作者。 diff --git a/docs/content/contributing/guidelines-frontend.zh-cn.md b/docs/content/contributing/guidelines-frontend.zh-cn.md index 66a4d4b4d6b6f..365144ee7c407 100644 --- a/docs/content/contributing/guidelines-frontend.zh-cn.md +++ b/docs/content/contributing/guidelines-frontend.zh-cn.md @@ -48,6 +48,7 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。 10. 避免在一个事件监听器中混合不同的事件,优先为每个事件使用独立的事件监听器。 11. 推荐使用自定义事件名称前缀`ce-`。 12. Gitea 的 tailwind-style CSS 类使用`gt-`前缀(`gt-relative`),而 Gitea 自身的私有框架级 CSS 类使用`g-`前缀(`g-modal-confirm`)。 +13. 尽量避免内联脚本和样式,建议将JS代码放入JS文件中并使用CSS类。如果内联脚本和样式不可避免,请解释无法避免的原因。 ### 可访问性 / ARIA @@ -64,18 +65,21 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari * Vue + Vanilla JS * Fomantic-UI(jQuery) +* htmx (部分页面重新加载其他静态组件) * Vanilla JS 不推荐的实现方式: * Vue + Fomantic-UI(jQuery) * jQuery + Vanilla JS +* htmx + 任何其他需要大量 JavaScript 代码或不必要的功能,如 htmx 脚本 (`hx-on`) 为了保持界面一致,Vue 组件可以使用 Fomantic-UI 的 CSS 类。 尽管不建议混合使用不同的框架, +我们使用 htmx 进行简单的交互。您可以在此 [PR](https://github.com/go-gitea/gitea/pull/28908) 中查看一个简单交互的示例,其中应使用 htmx。如果您需要更高级的反应性,请不要使用 htmx,请使用其他框架(Vue/Vanilla JS)。 但如果混合使用是必要的,并且代码设计良好且易于维护,也可以工作。 -### async 函数 +### `async` 函数 只有当函数内部存在`await`调用或返回`Promise`时,才将函数标记为`async`。 @@ -91,6 +95,12 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari 这是有意为之的,我们想调用异步函数并忽略Promise。 一些 lint 规则和 IDE 也会在未处理返回的 Promise 时发出警告。 +### 获取数据 + +要获取数据,请使用`modules/fetch.js`中的包装函数`GET`、`POST`等。他们 +接受内容的`data`选项,将自动设置 CSRF 令牌并返回 +[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response)。 + ### HTML 属性和 dataset 禁止使用`dataset`,它的驼峰命名行为使得搜索属性变得困难。 @@ -132,3 +142,7 @@ Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`ari ### Vue3 和 JSX Gitea 现在正在使用 Vue3。我们决定不引入 JSX,以保持 HTML 代码和 JavaScript 代码分离。 + +### UI示例 + +Gitea 使用一些自制的 UI 元素并自定义其他元素,以将它们更好地集成到通用 UI 方法中。当在开发模式(`RUN_MODE=dev`)下运行 Gitea 时,在 `http(s)://your-gitea-url:port/devtest` 下会提供一个包含一些标准化 UI 示例的页面。 diff --git a/docs/content/development/hacking-on-gitea.en-us.md b/docs/content/development/hacking-on-gitea.en-us.md index 4b132c49d9bd2..df8a9047d620e 100644 --- a/docs/content/development/hacking-on-gitea.en-us.md +++ b/docs/content/development/hacking-on-gitea.en-us.md @@ -243,10 +243,10 @@ documentation using: make generate-swagger ``` -You should validate your generated Swagger file and spell-check it with: +You should validate your generated Swagger file: ```bash -make swagger-validate misspell-check +make swagger-validate ``` You should commit the changed swagger JSON file. The continuous integration diff --git a/docs/content/development/hacking-on-gitea.zh-cn.md b/docs/content/development/hacking-on-gitea.zh-cn.md index 364bbf1ffe2b6..2dba3c92b66d3 100644 --- a/docs/content/development/hacking-on-gitea.zh-cn.md +++ b/docs/content/development/hacking-on-gitea.zh-cn.md @@ -228,10 +228,10 @@ Gitea Logo的 PNG 和 SVG 版本是使用 `TAGS="gitea" make generate-images` make generate-swagger ``` -您应该验证生成的 Swagger 文件并使用以下命令对其进行拼写检查: +您应该验证生成的 Swagger 文件: ```bash -make swagger-validate misspell-check +make swagger-validate ``` 您应该提交更改后的 swagger JSON 文件。持续集成服务器将使用以下方法检查是否已完成: diff --git a/docs/content/help/support.zh-cn.md b/docs/content/help/support.zh-cn.md index de56d8abe0e1d..91b37c586c71a 100644 --- a/docs/content/help/support.zh-cn.md +++ b/docs/content/help/support.zh-cn.md @@ -15,11 +15,64 @@ menu: identifier: "support" --- -## 需要帮助? +# 支持选项 -如果您在使用或者开发过程中遇到问题,请到以下渠道咨询: +- [付费商业支持](https://about.gitea.com/) +- [Discord](https://discord.gg/Gitea) +- [Discourse 论坛](https://discourse.gitea.io/) +- [Matrix](https://matrix.to/#/#gitea-space:matrix.org) + - 注意:大多数 Matrix 频道都与 Discord 中的对应频道桥接,可能在桥接过程中会出现一定程度的不稳定性。 +- 中文支持 + - [Discourse 中文分类](https://discourse.gitea.io/c/5-category/5) + - QQ 群 328432459 -- 到 [GitHub Issue](https://github.com/go-gitea/gitea/issues) 提问(因为项目维护人员来自世界各地,为保证沟通顺畅,请使用英文提问) -- 中文问题到 [Gitea 论坛](https://discourse.gitea.io/c/5-category/5) 提问 -- 访问 [Discord Gitea 聊天室 - 英文](https://discord.gg/Gitea) -- 加入 QQ群 328432459 获得进一步的支持 +# Bug 报告 + +如果您发现了 Bug,请在 GitHub 上 [创建一个问题](https://github.com/go-gitea/gitea/issues)。 + +**注意:** 在请求支持时,可能需要准备以下信息,以便帮助者获得所需的所有信息: + +1. 您的 `app.ini`(将任何敏感数据进行必要的清除)。 +2. 您看到的任何错误消息。 +3. Gitea 日志以及与情况相关的所有其他日志。 + - 收集 `trace` / `debug` 级别的日志更有用(参见下一节)。 + - 在使用 systemd 时,使用 `journalctl --lines 1000 --unit gitea` 收集日志。 + - 在使用 Docker 时,使用 `docker logs --tail 1000 ` 收集日志。 +4. 可重现的步骤,以便他人能够更快速、更容易地重现和理解问题。 + - [try.gitea.io](https://try.gitea.io) 可用于重现问题。 +5. 如果遇到慢速/挂起/死锁等问题,请在出现问题时报告堆栈跟踪。 + 转到 "Site Admin" -> "Monitoring" -> "Stacktrace" -> "Download diagnosis report"。 + +# 高级 Bug 报告提示 + +## 更多日志的配置选项 + +默认情况下,日志以 `info` 级别输出到控制台。 +如果您需要设置日志级别和/或从文件中收集日志, +您只需将以下配置复制到您的 `app.ini` 中(删除所有其他 `[log]` 部分), +然后您将在 Gitea 的日志目录中找到 `*.log` 文件(默认为 `%(GITEA_WORK_DIR)/log`)。 + +```ini +; 要显示所有 SQL 日志,您还可以在 [database] 部分中设置 LOG_SQL=true +[log] +LEVEL=debug +MODE=console,file +``` + +## 使用命令行收集堆栈跟踪 + +Gitea 可以使用 Golang 的 pprof 处理程序和工具链来收集堆栈跟踪和其他运行时信息。 + +如果 Web UI 停止工作,您可以尝试通过命令行收集堆栈跟踪: + +1. 设置 app.ini: + + ``` + [server] + ENABLE_PPROF = true + ``` + +2. 重新启动 Gitea + +3. 尝试触发bug,当请求卡住一段时间,使用或浏览器访问:获取堆栈跟踪。 +`curl http://127.0.0.1:6060/debug/pprof/goroutine?debug=1` diff --git a/docs/content/installation/database-preparation.zh-cn.md b/docs/content/installation/database-preparation.zh-cn.md index d651088395fd2..3fde004a8c3ea 100644 --- a/docs/content/installation/database-preparation.zh-cn.md +++ b/docs/content/installation/database-preparation.zh-cn.md @@ -17,7 +17,9 @@ menu: # 数据库准备 -在使用 Gitea 前,您需要准备一个数据库。Gitea 支持 PostgreSQL(>= 12)、MySQL(>= 8.0)、SQLite 和 MSSQL(>= 2012 SP4)这几种数据库。本页将指导您准备数据库。由于 PostgreSQL 和 MySQL 在生产环境中被广泛使用,因此本文档将仅涵盖这两种数据库。如果您计划使用 SQLite,则可以忽略本章内容。 +在使用 Gitea 前,您需要准备一个数据库。Gitea 支持 PostgreSQL(>= 12)、MySQL(>= 8.0)、MariaDB(>= 10.4)、SQLite(内置) 和 MSSQL(>= 2012 SP4)这几种数据库。本页将指导您准备数据库。由于 PostgreSQL 和 MySQL 在生产环境中被广泛使用,因此本文档将仅涵盖这两种数据库。如果您计划使用 SQLite,则可以忽略本章内容。 + +如果您使用不受支持的数据库版本,请通过 [联系我们](/help/support) 以获取有关我们的扩展支持的信息。我们可以为旧数据库提供测试和支持,并将这些修复集成到 Gitea 代码库中。 数据库实例可以与 Gitea 实例在相同机器上(本地数据库),也可以与 Gitea 实例在不同机器上(远程数据库)。 @@ -61,7 +63,9 @@ menu: 4. 使用 UTF-8 字符集和大小写敏感的排序规则创建数据库。 - Gitea 启动后会尝试把数据库修改为更合适的字符集,如果你想指定自己的字符集规则,可以在 app.ini 中设置 `[database].CHARSET_COLLATION`。 + `utf8mb4_bin` 是 MySQL/MariaDB 的通用排序规则。 + Gitea 启动后会尝试把数据库修改为更合适的字符集 (`utf8mb4_0900_as_cs` 或者 `uca1400_as_cs`) 并在可能的情况下更改数据库。 + 如果你想指定自己的字符集规则,可以在 `app.ini` 中设置 `[database].CHARSET_COLLATION`。 ```sql CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_bin'; @@ -85,7 +89,7 @@ menu: FLUSH PRIVILEGES; ``` -6. 通过 exit 退出数据库控制台。 +6. 通过 `exit` 退出数据库控制台。 7. 在您的 Gitea 服务器上,测试与数据库的连接: @@ -93,13 +97,13 @@ menu: mysql -u gitea -h 203.0.113.3 -p giteadb ``` - 其中 `gitea` 是数据库用户名,`giteadb` 是数据库名称,`203.0.113.3` 是数据库实例的 IP 地址。对于本地数据库,省略 -h 选项。 + 其中 `gitea` 是数据库用户名,`giteadb` 是数据库名称,`203.0.113.3` 是数据库实例的 IP 地址。对于本地数据库,省略 `-h` 选项。 到此您应该能够连接到数据库了。 ## PostgreSQL -1. 对于远程数据库设置,通过编辑数据库实例上的 postgresql.conf 文件中的 listen_addresses 将 PostgreSQL 配置为监听您的 IP 地址: +1. 对于远程数据库设置,通过编辑数据库实例上的 postgresql.conf 文件中的 `listen_addresses` 将 `PostgreSQL` 配置为监听您的 IP 地址: ```ini listen_addresses = 'localhost, 203.0.113.3' diff --git a/docs/content/installation/from-source.en-us.md b/docs/content/installation/from-source.en-us.md index 601e074745a91..cd9fd565118c6 100644 --- a/docs/content/installation/from-source.en-us.md +++ b/docs/content/installation/from-source.en-us.md @@ -27,13 +27,7 @@ Next, [install Node.js with npm](https://nodejs.org/en/download/) which is required to build the JavaScript and CSS files. The minimum supported Node.js version is @minNodeVersion@ and the latest LTS version is recommended. -**Note**: When executing make tasks that require external tools, like -`make misspell-check`, Gitea will automatically download and build these as -necessary. To be able to use these, you must have the `"$GOPATH/bin"` directory -on the executable path. If you don't add the go bin directory to the -executable path, you will have to manage this yourself. - -**Note 2**: Go version @minGoVersion@ or higher is required. However, it is recommended to +**Note**: Go version @minGoVersion@ or higher is required. However, it is recommended to obtain the same version as our continuous integration, see the advice given in [Hacking on Gitea](development/hacking-on-gitea.md) diff --git a/docs/content/installation/from-source.zh-cn.md b/docs/content/installation/from-source.zh-cn.md index c2bd5785b2281..3ff7efb4eddc7 100644 --- a/docs/content/installation/from-source.zh-cn.md +++ b/docs/content/installation/from-source.zh-cn.md @@ -21,9 +21,7 @@ menu: 接下来,[安装 Node.js 和 npm](https://nodejs.org/zh-cn/download/), 这是构建 JavaScript 和 CSS 文件所需的。最低支持的 Node.js 版本是 @minNodeVersion@,建议使用最新的 LTS 版本。 -**注意**:当执行需要外部工具的 make 任务(如`make misspell-check`)时,Gitea 将根据需要自动下载和构建这些工具。为了能够实现这个目的,你必须将`"$GOPATH/bin"`目录添加到可执行路径中。如果没有将 Go 的二进制目录添加到可执行路径中,你需要自行解决产生的问题。 - -**注意2**:需要 Go 版本 @minGoVersion@ 或更高版本。不过,建议获取与我们的持续集成(continuous integration, CI)相同的版本,请参阅在 [Hacking on Gitea](development/hacking-on-gitea.md) 中给出的建议。 +**注意**:需要 Go 版本 @minGoVersion@ 或更高版本。不过,建议获取与我们的持续集成(continuous integration, CI)相同的版本,请参阅在 [Hacking on Gitea](development/hacking-on-gitea.md) 中给出的建议。 ## 下载 diff --git a/docs/content/installation/windows-service.zh-cn.md b/docs/content/installation/windows-service.zh-cn.md index 985492b7e8c35..d5761d2c19157 100644 --- a/docs/content/installation/windows-service.zh-cn.md +++ b/docs/content/installation/windows-service.zh-cn.md @@ -15,7 +15,7 @@ menu: identifier: "windows-service" --- -# 准备工作 +## 准备工作 在 C:\gitea\custom\conf\app.ini 中进行了以下更改: @@ -27,7 +27,7 @@ RUN_USER = COMPUTERNAME$ COMPUTERNAME 是从命令行中运行 `echo %COMPUTERNAME%` 后得到的响应。如果响应是 `USER-PC`,那么 `RUN_USER = USER-PC$`。 -## 使用绝对路径 +### 使用绝对路径 如果您使用 SQLite3,请将 `PATH` 更改为包含完整路径: @@ -36,7 +36,7 @@ COMPUTERNAME 是从命令行中运行 `echo %COMPUTERNAME%` 后得到的响应 PATH = c:/gitea/data/gitea.db ``` -# 注册为Windows服务 +## 注册为Windows服务 要注册为Windows服务,首先以Administrator身份运行 `cmd`,然后执行以下命令: @@ -48,7 +48,16 @@ sc.exe create gitea start= auto binPath= "\"C:\gitea\gitea.exe\" web --config \" 之后在控制面板打开 "Windows Services",搜索 "gitea",右键选择 "Run"。在浏览器打开 `http://localhost:3000` 就可以访问了。(如果你修改了端口,请访问对应的端口,3000是默认端口)。 -## 添加启动依赖项 +### 服务启动类型 + +据观察,在启动期间加载的系统上,Gitea 服务可能无法启动,并在 Windows 事件日志中记录超时。 +在这种情况下,将启动类型更改为`Automatic-Delayed`。这可以在服务创建期间完成,或者通过运行配置命令来完成。 + +``` +sc.exe config gitea start= delayed-auto +``` + +### 添加启动依赖项 要将启动依赖项添加到 Gitea Windows 服务(例如 Mysql、Mariadb),作为管理员,然后运行以下命令: diff --git a/docs/content/usage/actions/act-runner.en-us.md b/docs/content/usage/actions/act-runner.en-us.md index 3fad9cbfe8867..b2806bf5dd320 100644 --- a/docs/content/usage/actions/act-runner.en-us.md +++ b/docs/content/usage/actions/act-runner.en-us.md @@ -120,6 +120,8 @@ A registration token can also be obtained from the gitea [command-line interface gitea --config /etc/gitea/app.ini actions generate-runner-token ``` +Tokens are valid for registering multiple runners, until they are revoked and replaced by a new token using the token reset link in the web interface. + ### Register the runner The act runner can be registered by running the following command: diff --git a/docs/content/usage/actions/quickstart.en-us.md b/docs/content/usage/actions/quickstart.en-us.md index 2a2cf72584772..0514b6ddf2ef6 100644 --- a/docs/content/usage/actions/quickstart.en-us.md +++ b/docs/content/usage/actions/quickstart.en-us.md @@ -61,8 +61,8 @@ It is always a bad idea to use a loopback address such as `127.0.0.1` or `localh If you are unsure which address to use, the LAN address is usually the right choice. `token` is used for authentication and identification, such as `P2U1U0oB4XaRCi8azcngmPCLbRpUGapalhmddh23`. -It is one-time use only and cannot be used to register multiple runners. -You can obtain different levels of 'tokens' from the following places to create the corresponding level of' runners': +Each token can be used to create multiple runners, until it is replaced with a new token using the reset link. +You can obtain different levels of 'tokens' from the following places to create the corresponding level of 'runners': - Instance level: The admin settings page, like `/admin/actions/runners`. - Organization level: The organization settings page, like `//settings/actions/runners`. diff --git a/docs/content/usage/packages/overview.en-us.md b/docs/content/usage/packages/overview.en-us.md index 44d18ff482303..89fc6f286e65f 100644 --- a/docs/content/usage/packages/overview.en-us.md +++ b/docs/content/usage/packages/overview.en-us.md @@ -42,7 +42,7 @@ The following package managers are currently supported: | [PyPI](usage/packages/pypi.md) | Python | `pip`, `twine` | | [RPM](usage/packages/rpm.md) | - | `yum`, `dnf`, `zypper` | | [RubyGems](usage/packages/rubygems.md) | Ruby | `gem`, `Bundler` | -| [Swift](usage/packages/rubygems.md) | Swift | `swift` | +| [Swift](usage/packages/swift.md) | Swift | `swift` | | [Vagrant](usage/packages/vagrant.md) | - | `vagrant` | **The following paragraphs only apply if Packages are not globally disabled!** diff --git a/docs/content/usage/packages/swift.en-us.md b/docs/content/usage/packages/swift.en-us.md index 606fa20b362a8..38eb155641a90 100644 --- a/docs/content/usage/packages/swift.en-us.md +++ b/docs/content/usage/packages/swift.en-us.md @@ -26,7 +26,8 @@ To work with the Swift package registry, you need to use [swift](https://www.swi To register the package registry and provide credentials, execute: ```shell -swift package-registry set https://gitea.example.com/api/packages/{owner}/swift -login {username} -password {password} +swift package-registry set https://gitea.example.com/api/packages/{owner}/swift +swift package-registry login https://gitea.example.com/api/packages/{owner}/swift --username {username} --password {password} ``` | Placeholder | Description | diff --git a/docs/content/usage/profile-readme.zh-cn.md b/docs/content/usage/profile-readme.zh-cn.md index 804f69d2e6f12..b69d4aa921dd8 100644 --- a/docs/content/usage/profile-readme.zh-cn.md +++ b/docs/content/usage/profile-readme.zh-cn.md @@ -15,6 +15,10 @@ menu: # 个人资料 README -要在您的 Gitea 个人资料页面显示一个 Markdown 文件,只需创建一个名为 ".profile" 的仓库,并编辑其中的 README.md 文件。Gitea 将自动获取该文件并在您的仓库上方显示。 +要在您的 Gitea 个人资料页面显示一个 Markdown 文件,只需创建一个名为 `.profile` 的仓库,并编辑其中的 `README.md` 文件。Gitea 将自动获取该文件并在您的仓库上方显示。 注意:您可以将此仓库设为私有。这样可以隐藏您的源文件,使其对公众不可见,并允许您将某些文件设为私有。但是,README.md 文件将是您个人资料上唯一存在的文件。如果您希望完全私有化 .profile 仓库,则需删除或重命名 README.md 文件。 + +用户示例 `.profile/README.md`: + +![个人资料自述文件截图](/images/usage/profile-readme.png) diff --git a/models/error.go b/models/error.go index 83dfe29805774..75c53245de5ae 100644 --- a/models/error.go +++ b/models/error.go @@ -493,6 +493,23 @@ func (err ErrMergeUnrelatedHistories) Error() string { return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) } +// ErrMergeDivergingFastForwardOnly represents an error if a fast-forward-only merge fails because the branches diverge +type ErrMergeDivergingFastForwardOnly struct { + StdOut string + StdErr string + Err error +} + +// IsErrMergeDivergingFastForwardOnly checks if an error is a ErrMergeDivergingFastForwardOnly. +func IsErrMergeDivergingFastForwardOnly(err error) bool { + _, ok := err.(ErrMergeDivergingFastForwardOnly) + return ok +} + +func (err ErrMergeDivergingFastForwardOnly) Error() string { + return fmt.Sprintf("Merge DivergingFastForwardOnly Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) +} + // ErrRebaseConflicts represents an error if rebase fails with a conflict type ErrRebaseConflicts struct { Style repo_model.MergeStyle diff --git a/models/issues/comment.go b/models/issues/comment.go index c63fcab8944f5..a586caf1b57e0 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -695,8 +695,15 @@ func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository } func (c *Comment) loadReview(ctx context.Context) (err error) { + if c.ReviewID == 0 { + return nil + } if c.Review == nil { if c.Review, err = GetReviewByID(ctx, c.ReviewID); err != nil { + // review request which has been replaced by actual reviews doesn't exist in database anymore, so ignorem them. + if c.Type == CommentTypeReviewRequest { + return nil + } return err } } diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go index 93af45870ed69..cb7df3270d6ad 100644 --- a/models/issues/comment_list.go +++ b/models/issues/comment_list.go @@ -430,7 +430,8 @@ func (comments CommentList) loadReviews(ctx context.Context) error { for _, comment := range comments { comment.Review = reviews[comment.ReviewID] if comment.Review == nil { - if comment.ReviewID > 0 { + // review request which has been replaced by actual reviews doesn't exist in database anymore, so don't log errors for them. + if comment.ReviewID > 0 && comment.Type != CommentTypeReviewRequest { log.Error("comment with review id [%d] but has no review record", comment.ReviewID) } continue diff --git a/models/issues/review.go b/models/issues/review.go index f2022ae0aa478..ba4e02f76560c 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -621,6 +621,9 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo return nil, err } + // func caller use the created comment to retrieve created review too. + comment.Review = review + return comment, committer.Commit() } diff --git a/models/repo/git.go b/models/repo/git.go index 610c55429657c..388bf8652237e 100644 --- a/models/repo/git.go +++ b/models/repo/git.go @@ -21,6 +21,8 @@ const ( MergeStyleRebaseMerge MergeStyle = "rebase-merge" // MergeStyleSquash squash commits into single commit before merging MergeStyleSquash MergeStyle = "squash" + // MergeStyleFastForwardOnly fast-forward merge if possible, otherwise fail + MergeStyleFastForwardOnly MergeStyle = "fast-forward-only" // MergeStyleManuallyMerged pr has been merged manually, just mark it as merged directly MergeStyleManuallyMerged MergeStyle = "manually-merged" // MergeStyleRebaseUpdate not a merge style, used to update pull head by rebase diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index 8a3ba1ee89092..31a2a2e248228 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -122,6 +122,7 @@ type PullRequestsConfig struct { AllowRebase bool AllowRebaseMerge bool AllowSquash bool + AllowFastForwardOnly bool AllowManualMerge bool AutodetectManualMerge bool AllowRebaseUpdate bool @@ -148,6 +149,7 @@ func (cfg *PullRequestsConfig) IsMergeStyleAllowed(mergeStyle MergeStyle) bool { mergeStyle == MergeStyleRebase && cfg.AllowRebase || mergeStyle == MergeStyleRebaseMerge && cfg.AllowRebaseMerge || mergeStyle == MergeStyleSquash && cfg.AllowSquash || + mergeStyle == MergeStyleFastForwardOnly && cfg.AllowFastForwardOnly || mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge } diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index 4c668ad04bf1e..cb90c12f2b716 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -44,12 +44,12 @@ func fatalTestError(fmtStr string, args ...any) { } // InitSettings initializes config provider and load common settings for tests -func InitSettings(extraConfigs ...string) { +func InitSettings() { if setting.CustomConf == "" { setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini") _ = os.Remove(setting.CustomConf) } - setting.InitCfgProvider(setting.CustomConf, strings.Join(extraConfigs, "\n")) + setting.InitCfgProvider(setting.CustomConf) setting.LoadCommonSettings() if err := setting.PrepareAppDataPath(); err != nil { diff --git a/models/user/user.go b/models/user/user.go index dd993569c069f..f141023374f57 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -425,14 +425,14 @@ func (u *User) GetDisplayName() string { } // GetCompleteName returns the the full name and username in the form of -// "Full Name (@username)" if full name is not empty, otherwise it returns -// "@username". +// "Full Name (username)" if full name is not empty, otherwise it returns +// "username". func (u *User) GetCompleteName() string { trimmedFullName := strings.TrimSpace(u.FullName) if len(trimmedFullName) > 0 { - return fmt.Sprintf("%s (@%s)", trimmedFullName, u.Name) + return fmt.Sprintf("%s (%s)", trimmedFullName, u.Name) } - return fmt.Sprintf("@%s", u.Name) + return u.Name } func gitSafeName(name string) string { diff --git a/modules/contexttest/context_tests.go b/modules/contexttest/context_tests.go index 9ca028bb6ec26..c9bacf259f530 100644 --- a/modules/contexttest/context_tests.go +++ b/modules/contexttest/context_tests.go @@ -40,8 +40,19 @@ func mockRequest(t *testing.T, reqPath string) *http.Request { return req } +type MockContextOption struct { + Render context.Render +} + // MockContext mock context for unit tests -func MockContext(t *testing.T, reqPath string) (*context.Context, *httptest.ResponseRecorder) { +func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*context.Context, *httptest.ResponseRecorder) { + var opt MockContextOption + if len(opts) > 0 { + opt = opts[0] + } + if opt.Render == nil { + opt.Render = &MockRender{} + } resp := httptest.NewRecorder() req := mockRequest(t, reqPath) base, baseCleanUp := context.NewBaseContext(resp, req) @@ -49,7 +60,7 @@ func MockContext(t *testing.T, reqPath string) (*context.Context, *httptest.Resp base.Data = middleware.GetContextData(req.Context()) base.Locale = &translation.MockLocale{} - ctx := context.NewWebContext(base, &MockRender{}, nil) + ctx := context.NewWebContext(base, opt.Render, nil) chiCtx := chi.NewRouteContext() ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx) diff --git a/modules/git/error.go b/modules/git/error.go index dc10d451b3bbf..91d25eca69a4d 100644 --- a/modules/git/error.go +++ b/modules/git/error.go @@ -96,7 +96,7 @@ func (err ErrBranchNotExist) Unwrap() error { return util.ErrNotExist } -// ErrPushOutOfDate represents an error if merging fails due to unrelated histories +// ErrPushOutOfDate represents an error if merging fails due to the base branch being updated type ErrPushOutOfDate struct { StdOut string StdErr string diff --git a/modules/git/repo.go b/modules/git/repo.go index db99d285a86a6..60078f3273705 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -271,7 +271,7 @@ func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error return time.Time{}, err } commitTime := strings.TrimSpace(stdout) - return time.Parse(GitTimeLayout, commitTime) + return time.Parse("Mon Jan _2 15:04:05 2006 -0700", commitTime) } // DivergeObject represents commit count diverging commits diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index af9a75b29ca9f..ae5dbd171fc7f 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -183,11 +183,7 @@ func parseTagRef(objectFormat ObjectFormat, ref map[string]string) (tag *Tag, er } } - tag.Tagger, err = newSignatureFromCommitline([]byte(ref["creator"])) - if err != nil { - return nil, fmt.Errorf("parse tagger: %w", err) - } - + tag.Tagger = parseSignatureFromCommitLine(ref["creator"]) tag.Message = ref["contents"] // strip PGP signature if present in contents field pgpStart := strings.Index(tag.Message, beginpgp) diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index da7b1455a8992..9816e311a8815 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -227,7 +227,7 @@ func TestRepository_parseTagRef(t *testing.T) { ID: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), Type: "commit", - Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"), + Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", Signature: nil, }, @@ -256,7 +256,7 @@ func TestRepository_parseTagRef(t *testing.T) { ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), Type: "tag", - Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"), + Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", Signature: nil, }, @@ -314,7 +314,7 @@ qbHDASXl ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), Type: "tag", - Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"), + Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md", Signature: &CommitGPGSignature{ Signature: `-----BEGIN PGP SIGNATURE----- @@ -363,14 +363,3 @@ Add changelog of v1.9.1 (#7859) }) } } - -func parseAuthorLine(t *testing.T, committer string) *Signature { - t.Helper() - - sig, err := newSignatureFromCommitline([]byte(committer)) - if err != nil { - t.Fatalf("parse author line '%s': %v", committer, err) - } - - return sig -} diff --git a/modules/git/signature.go b/modules/git/signature.go index b5b17f23b0477..f50a097758bdc 100644 --- a/modules/git/signature.go +++ b/modules/git/signature.go @@ -4,7 +4,46 @@ package git -const ( - // GitTimeLayout is the (default) time layout used by git. - GitTimeLayout = "Mon Jan _2 15:04:05 2006 -0700" +import ( + "strconv" + "strings" + "time" + + "code.gitea.io/gitea/modules/log" ) + +// Helper to get a signature from the commit line, which looks like: +// +// full name 1378823654 +0200 +// +// Haven't found the official reference for the standard format yet. +// This function never fails, if the "line" can't be parsed, it returns a default Signature with "zero" time. +func parseSignatureFromCommitLine(line string) *Signature { + sig := &Signature{} + s1, sx, ok1 := strings.Cut(line, " <") + s2, s3, ok2 := strings.Cut(sx, "> ") + if !ok1 || !ok2 { + sig.Name = line + return sig + } + sig.Name, sig.Email = s1, s2 + + if strings.Count(s3, " ") == 1 { + ts, tz, _ := strings.Cut(s3, " ") + seconds, _ := strconv.ParseInt(ts, 10, 64) + if tzTime, err := time.Parse("-0700", tz); err == nil { + sig.When = time.Unix(seconds, 0).In(tzTime.Location()) + } + } else { + // the old gitea code tried to parse the date in a few different formats, but it's not clear why. + // according to public document, only the standard format "timestamp timezone" could be found, so drop other formats. + log.Error("suspicious commit line format: %q", line) + for _, fmt := range []string{ /*"Mon Jan _2 15:04:05 2006 -0700"*/ } { + if t, err := time.Parse(fmt, s3); err == nil { + sig.When = t + break + } + } + } + return sig +} diff --git a/modules/git/signature_gogit.go b/modules/git/signature_gogit.go index c984ad6e20ddc..1fc6aabceb4f4 100644 --- a/modules/git/signature_gogit.go +++ b/modules/git/signature_gogit.go @@ -7,52 +7,8 @@ package git import ( - "bytes" - "strconv" - "strings" - "time" - "github.com/go-git/go-git/v5/plumbing/object" ) // Signature represents the Author or Committer information. type Signature = object.Signature - -// Helper to get a signature from the commit line, which looks like these: -// -// author Patrick Gundlach 1378823654 +0200 -// author Patrick Gundlach Thu, 07 Apr 2005 22:13:13 +0200 -// -// but without the "author " at the beginning (this method should) -// be used for author and committer. -// -// FIXME: include timezone for timestamp! -func newSignatureFromCommitline(line []byte) (_ *Signature, err error) { - sig := new(Signature) - emailStart := bytes.IndexByte(line, '<') - if emailStart > 0 { // Empty name has already occurred, even if it shouldn't - sig.Name = strings.TrimSpace(string(line[:emailStart-1])) - } - emailEnd := bytes.IndexByte(line, '>') - sig.Email = string(line[emailStart+1 : emailEnd]) - - // Check date format. - if len(line) > emailEnd+2 { - firstChar := line[emailEnd+2] - if firstChar >= 48 && firstChar <= 57 { - timestop := bytes.IndexByte(line[emailEnd+2:], ' ') - timestring := string(line[emailEnd+2 : emailEnd+2+timestop]) - seconds, _ := strconv.ParseInt(timestring, 10, 64) - sig.When = time.Unix(seconds, 0) - } else { - sig.When, err = time.Parse(GitTimeLayout, string(line[emailEnd+2:])) - if err != nil { - return nil, err - } - } - } else { - // Fall back to unix 0 time - sig.When = time.Unix(0, 0) - } - return sig, nil -} diff --git a/modules/git/signature_nogogit.go b/modules/git/signature_nogogit.go index 25277f99d52e6..0d19c0abdcc9b 100644 --- a/modules/git/signature_nogogit.go +++ b/modules/git/signature_nogogit.go @@ -7,21 +7,17 @@ package git import ( - "bytes" "fmt" - "strconv" - "strings" "time" + + "code.gitea.io/gitea/modules/util" ) -// Signature represents the Author or Committer information. +// Signature represents the Author, Committer or Tagger information. type Signature struct { - // Name represents a person name. It is an arbitrary string. - Name string - // Email is an email, but it cannot be assumed to be well-formed. - Email string - // When is the timestamp of the signature. - When time.Time + Name string // the committer name, it can be anything + Email string // the committer email, it can be anything + When time.Time // the timestamp of the signature } func (s *Signature) String() string { @@ -30,71 +26,5 @@ func (s *Signature) String() string { // Decode decodes a byte array representing a signature to signature func (s *Signature) Decode(b []byte) { - sig, _ := newSignatureFromCommitline(b) - s.Email = sig.Email - s.Name = sig.Name - s.When = sig.When -} - -// Helper to get a signature from the commit line, which looks like these: -// -// author Patrick Gundlach 1378823654 +0200 -// author Patrick Gundlach Thu, 07 Apr 2005 22:13:13 +0200 -// -// but without the "author " at the beginning (this method should) -// be used for author and committer. -// FIXME: there are a lot of "return sig, err" (but the err is also nil), that's the old behavior, to avoid breaking -func newSignatureFromCommitline(line []byte) (sig *Signature, err error) { - sig = new(Signature) - emailStart := bytes.LastIndexByte(line, '<') - emailEnd := bytes.LastIndexByte(line, '>') - if emailStart == -1 || emailEnd == -1 || emailEnd < emailStart { - return sig, err - } - - if emailStart > 0 { // Empty name has already occurred, even if it shouldn't - sig.Name = strings.TrimSpace(string(line[:emailStart-1])) - } - sig.Email = string(line[emailStart+1 : emailEnd]) - - hasTime := emailEnd+2 < len(line) - if !hasTime { - return sig, err - } - - // Check date format. - firstChar := line[emailEnd+2] - if firstChar >= 48 && firstChar <= 57 { - idx := bytes.IndexByte(line[emailEnd+2:], ' ') - if idx < 0 { - return sig, err - } - - timestring := string(line[emailEnd+2 : emailEnd+2+idx]) - seconds, _ := strconv.ParseInt(timestring, 10, 64) - sig.When = time.Unix(seconds, 0) - - idx += emailEnd + 3 - if idx >= len(line) || idx+5 > len(line) { - return sig, err - } - - timezone := string(line[idx : idx+5]) - tzhours, err1 := strconv.ParseInt(timezone[0:3], 10, 64) - tzmins, err2 := strconv.ParseInt(timezone[3:], 10, 64) - if err1 != nil || err2 != nil { - return sig, err - } - if tzhours < 0 { - tzmins *= -1 - } - tz := time.FixedZone("", int(tzhours*60*60+tzmins*60)) - sig.When = sig.When.In(tz) - } else { - sig.When, err = time.Parse(GitTimeLayout, string(line[emailEnd+2:])) - if err != nil { - return sig, err - } - } - return sig, err + *s = *parseSignatureFromCommitLine(util.UnsafeBytesToString(b)) } diff --git a/modules/git/signature_test.go b/modules/git/signature_test.go new file mode 100644 index 0000000000000..92681feea9f68 --- /dev/null +++ b/modules/git/signature_test.go @@ -0,0 +1,47 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestParseSignatureFromCommitLine(t *testing.T) { + tests := []struct { + line string + want *Signature + }{ + { + line: "a b 12345 +0100", + want: &Signature{ + Name: "a b", + Email: "c@d.com", + When: time.Unix(12345, 0).In(time.FixedZone("", 3600)), + }, + }, + { + line: "bad line", + want: &Signature{Name: "bad line"}, + }, + { + line: "bad < line", + want: &Signature{Name: "bad < line"}, + }, + { + line: "bad > line", + want: &Signature{Name: "bad > line"}, + }, + { + line: "bad-line ", + want: &Signature{Name: "bad-line "}, + }, + } + for _, test := range tests { + got := parseSignatureFromCommitLine(test.line) + assert.EqualValues(t, test.want, got) + } +} diff --git a/modules/git/tag.go b/modules/git/tag.go index 01a8d6f6a552b..94e5cd7c63b78 100644 --- a/modules/git/tag.go +++ b/modules/git/tag.go @@ -7,6 +7,8 @@ import ( "bytes" "sort" "strings" + + "code.gitea.io/gitea/modules/util" ) const ( @@ -59,11 +61,7 @@ l: // A commit can have one or more parents tag.Type = string(line[spacepos+1:]) case "tagger": - sig, err := newSignatureFromCommitline(line[spacepos+1:]) - if err != nil { - return nil, err - } - tag.Tagger = sig + tag.Tagger = parseSignatureFromCommitLine(util.UnsafeBytesToString(line[spacepos+1:])) } nextline += eol + 1 case eol == 0: diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go index 2fadbfeb064e1..0f70f1348552c 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch.go +++ b/modules/indexer/code/elasticsearch/elasticsearch.go @@ -180,11 +180,17 @@ func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha st } if len(reqs) > 0 { - _, err := b.inner.Client.Bulk(). - Index(b.inner.VersionedIndexName()). - Add(reqs...). - Do(ctx) - return err + esBatchSize := 50 + + for i := 0; i < len(reqs); i += esBatchSize { + _, err := b.inner.Client.Bulk(). + Index(b.inner.VersionedIndexName()). + Add(reqs[i:min(i+esBatchSize, len(reqs))]...). + Do(ctx) + if err != nil { + return err + } + } } return nil } diff --git a/modules/markup/markdown/ast.go b/modules/markup/markdown/ast.go index 3e6e291ab25a4..77ce5cb359b96 100644 --- a/modules/markup/markdown/ast.go +++ b/modules/markup/markdown/ast.go @@ -182,12 +182,7 @@ func IsColorPreview(node ast.Node) bool { return ok } -const ( - AttentionNote string = "Note" - AttentionWarning string = "Warning" -) - -// Attention is an inline for a color preview +// Attention is an inline for an attention type Attention struct { ast.BaseInline AttentionType string diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index 178e3d2fddf87..36ce6397f4d83 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -53,7 +53,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa } } - attentionMarkedBlockquotes := make(container.Set[*ast.Blockquote]) _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { return ast.WalkContinue, nil @@ -197,18 +196,55 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa if css.ColorHandler(strings.ToLower(string(colorContent))) { v.AppendChild(v, NewColorPreview(colorContent)) } - case *ast.Emphasis: - // check if inside blockquote for attention, expected hierarchy is - // Emphasis < Paragraph < Blockquote - blockquote, isInBlockquote := n.Parent().Parent().(*ast.Blockquote) - if isInBlockquote && !attentionMarkedBlockquotes.Contains(blockquote) { - fullText := string(n.Text(reader.Source())) - if fullText == AttentionNote || fullText == AttentionWarning { - v.SetAttributeString("class", []byte("attention-"+strings.ToLower(fullText))) - v.Parent().InsertBefore(v.Parent(), v, NewAttention(fullText)) - attentionMarkedBlockquotes.Add(blockquote) - } + case *ast.Blockquote: + // We only want attention blockquotes when the AST looks like: + // Text: "[" + // Text: "!TYPE" + // Text(SoftLineBreak): "]" + + // grab these nodes and make sure we adhere to the attention blockquote structure + firstParagraph := v.FirstChild() + if firstParagraph.ChildCount() < 3 { + return ast.WalkContinue, nil + } + firstTextNode, ok := firstParagraph.FirstChild().(*ast.Text) + if !ok || string(firstTextNode.Segment.Value(reader.Source())) != "[" { + return ast.WalkContinue, nil + } + secondTextNode, ok := firstTextNode.NextSibling().(*ast.Text) + if !ok || !attentionTypeRE.MatchString(string(secondTextNode.Segment.Value(reader.Source()))) { + return ast.WalkContinue, nil } + thirdTextNode, ok := secondTextNode.NextSibling().(*ast.Text) + if !ok || string(thirdTextNode.Segment.Value(reader.Source())) != "]" { + return ast.WalkContinue, nil + } + + // grab attention type from markdown source + attentionType := strings.ToLower(strings.TrimPrefix(string(secondTextNode.Segment.Value(reader.Source())), "!")) + + // color the blockquote + v.SetAttributeString("class", []byte("gt-py-3 attention attention-"+attentionType)) + + // create an emphasis to make it bold + emphasis := ast.NewEmphasis(2) + emphasis.SetAttributeString("class", []byte("attention-"+attentionType)) + firstParagraph.InsertBefore(firstParagraph, firstTextNode, emphasis) + + // capitalize first letter + attentionText := ast.NewString([]byte(strings.ToUpper(string(attentionType[0])) + attentionType[1:])) + + // replace the ![TYPE] with icon+Type + emphasis.AppendChild(emphasis, attentionText) + for i := 0; i < 2; i++ { + lineBreak := ast.NewText() + lineBreak.SetSoftLineBreak(true) + firstParagraph.InsertAfter(firstParagraph, emphasis, lineBreak) + } + firstParagraph.InsertBefore(firstParagraph, emphasis, NewAttention(attentionType)) + firstParagraph.RemoveChild(firstParagraph, firstTextNode) + firstParagraph.RemoveChild(firstParagraph, secondTextNode) + firstParagraph.RemoveChild(firstParagraph, thirdTextNode) } return ast.WalkContinue, nil }) @@ -339,17 +375,23 @@ func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Nod // renderAttention renders a quote marked with i.e. "> **Note**" or "> **Warning**" with a corresponding svg func (r *HTMLRenderer) renderAttention(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if entering { - _, _ = w.WriteString(``) var octiconType string switch n.AttentionType { - case AttentionNote: + case "note": octiconType = "info" - case AttentionWarning: + case "tip": + octiconType = "light-bulb" + case "important": + octiconType = "report" + case "warning": octiconType = "alert" + case "caution": + octiconType = "stop" } _, _ = w.WriteString(string(svg.RenderHTML("octicon-" + octiconType))) } else { @@ -417,7 +459,10 @@ func (r *HTMLRenderer) renderSummary(w util.BufWriter, source []byte, node ast.N return ast.WalkContinue, nil } -var validNameRE = regexp.MustCompile("^[a-z ]+$") +var ( + validNameRE = regexp.MustCompile("^[a-z ]+$") + attentionTypeRE = regexp.MustCompile("^!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)$") +) func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index abc641fbe2dfc..ac1cedff6d4c1 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -133,18 +133,18 @@ type Writer struct { Ctx *markup.RenderContext } -const mailto = "mailto:" - -func (r *Writer) resolveLink(l org.RegularLink) string { - link := html.EscapeString(l.URL) - if l.Protocol == "file" { - link = link[len("file:"):] - } - if len(link) > 0 && !markup.IsLinkStr(link) && - link[0] != '#' && !strings.HasPrefix(link, mailto) { +func (r *Writer) resolveLink(kind, link string) string { + link = strings.TrimPrefix(link, "file:") + if !strings.HasPrefix(link, "#") && // not a URL fragment + !markup.IsLinkStr(link) && // not an absolute URL + !strings.HasPrefix(link, "mailto:") { + if kind == "regular" { + // orgmode reports the link kind as "regular" for "[[ImageLink.svg][The Image Desc]]" + // so we need to try to guess the link kind again here + kind = org.RegularLink{URL: link}.Kind() + } base := r.Ctx.Links.Base - switch l.Kind() { - case "image", "video": + if kind == "image" || kind == "video" { base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsWiki) } link = util.URLJoin(base, link) @@ -154,29 +154,29 @@ func (r *Writer) resolveLink(l org.RegularLink) string { // WriteRegularLink renders images, links or videos func (r *Writer) WriteRegularLink(l org.RegularLink) { - link := r.resolveLink(l) + link := r.resolveLink(l.Kind(), l.URL) // Inspired by https://github.com/niklasfasching/go-org/blob/6eb20dbda93cb88c3503f7508dc78cbbc639378f/org/html_writer.go#L406-L427 switch l.Kind() { case "image": if l.Description == nil { - fmt.Fprintf(r, `%s`, link, link) + _, _ = fmt.Fprintf(r, `%s`, link, link) } else { - imageSrc := r.resolveLink(l.Description[0].(org.RegularLink)) - fmt.Fprintf(r, `%s`, link, imageSrc, imageSrc) + imageSrc := r.resolveLink(l.Kind(), org.String(l.Description...)) + _, _ = fmt.Fprintf(r, `%s`, link, imageSrc, imageSrc) } case "video": if l.Description == nil { - fmt.Fprintf(r, ``, link, link) + _, _ = fmt.Fprintf(r, ``, link, link) } else { - videoSrc := r.resolveLink(l.Description[0].(org.RegularLink)) - fmt.Fprintf(r, ``, link, videoSrc, videoSrc) + videoSrc := r.resolveLink(l.Kind(), org.String(l.Description...)) + _, _ = fmt.Fprintf(r, ``, link, videoSrc, videoSrc) } default: description := link if l.Description != nil { description = r.WriteNodesAsString(l.Description...) } - fmt.Fprintf(r, `%s`, link, description) + _, _ = fmt.Fprintf(r, `%s`, link, description) } } diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go index abf5ca8fcf385..95f53c9cc9ff5 100644 --- a/modules/markup/orgmode/orgmode_test.go +++ b/modules/markup/orgmode/orgmode_test.go @@ -10,26 +10,21 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) -const ( - AppURL = "http://localhost:3000/" - Repo = "gogits/gogs" - AppSubURL = AppURL + Repo + "/" -) +const AppURL = "http://localhost:3000/" func TestRender_StandardLinks(t *testing.T) { setting.AppURL = AppURL - setting.AppSubURL = AppSubURL test := func(input, expected string) { buffer, err := RenderString(&markup.RenderContext{ Ctx: git.DefaultContext, Links: markup.Links{ - Base: setting.AppSubURL, + Base: "/relative-path", + BranchPath: "branch/main", }, }, input) assert.NoError(t, err) @@ -38,32 +33,30 @@ func TestRender_StandardLinks(t *testing.T) { test("[[https://google.com/]]", `

https://google.com/

`) - - lnk := util.URLJoin(AppSubURL, "WikiPage") - test("[[WikiPage][WikiPage]]", - `

WikiPage

`) + test("[[WikiPage][The WikiPage Desc]]", + `

The WikiPage Desc

`) + test("[[ImageLink.svg][The Image Desc]]", + `

The Image Desc

`) } func TestRender_Media(t *testing.T) { setting.AppURL = AppURL - setting.AppSubURL = AppSubURL test := func(input, expected string) { buffer, err := RenderString(&markup.RenderContext{ Ctx: git.DefaultContext, Links: markup.Links{ - Base: setting.AppSubURL, + Base: "./relative-path", }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } - url := "../../.images/src/02/train.jpg" - result := util.URLJoin(AppSubURL, url) - - test("[[file:"+url+"]]", - `

`+result+`

`) + test("[[file:../../.images/src/02/train.jpg]]", + `

.images/src/02/train.jpg

`) + test("[[file:train.jpg]]", + `

relative-path/train.jpg

`) // With description. test("[[https://example.com][https://example.com/example.svg]]", @@ -80,11 +73,20 @@ func TestRender_Media(t *testing.T) { `

https://example.com/example.svg

`) test("[[https://example.com/example.mp4]]", `

`) + + // test [[LINK][DESCRIPTION]] syntax with "file:" prefix + test(`[[https://example.com/][file:https://example.com/foo%20bar.svg]]`, + `

https://example.com/foo%20bar.svg

`) + test(`[[file:https://example.com/foo%20bar.svg][Goto Image]]`, + `

Goto Image

`) + test(`[[file:https://example.com/link][https://example.com/image.jpg]]`, + `

https://example.com/image.jpg

`) + test(`[[file:https://example.com/link][file:https://example.com/image.jpg]]`, + `

https://example.com/image.jpg

`) } func TestRender_Source(t *testing.T) { setting.AppURL = AppURL - setting.AppSubURL = AppSubURL test := func(input, expected string) { buffer, err := RenderString(&markup.RenderContext{ diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go index 992e85b9898d9..ffc33c3b8e5b9 100644 --- a/modules/markup/sanitizer.go +++ b/modules/markup/sanitizer.go @@ -64,9 +64,10 @@ func createDefaultPolicy() *bluemonday.Policy { policy.AllowAttrs("class").Matching(regexp.MustCompile(`^color-preview$`)).OnElements("span") // For attention + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^gt-py-3 attention attention-\w+$`)).OnElements("blockquote") policy.AllowAttrs("class").Matching(regexp.MustCompile(`^attention-\w+$`)).OnElements("strong") - policy.AllowAttrs("class").Matching(regexp.MustCompile(`^attention-icon attention-\w+$`)).OnElements("span", "strong") - policy.AllowAttrs("class").Matching(regexp.MustCompile(`^svg octicon-\w+$`)).OnElements("svg") + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^gt-mr-2 gt-vm attention-\w+$`)).OnElements("span", "strong") + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^svg octicon-(\w|-)+$`)).OnElements("svg") policy.AllowAttrs("viewBox", "width", "height", "aria-hidden").OnElements("svg") policy.AllowAttrs("fill-rule", "d").OnElements("path") diff --git a/modules/repository/create.go b/modules/repository/create.go index 7c954a14121b6..ca2150b972e06 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -87,7 +87,11 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: tp, - Config: &repo_model.PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle), AllowRebaseUpdate: true}, + Config: &repo_model.PullRequestsConfig{ + AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, AllowFastForwardOnly: true, + DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle), + AllowRebaseUpdate: true, + }, }) } else { units = append(units, repo_model.RepoUnit{ diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go index 132f4acea1c79..3fa3f3b50b822 100644 --- a/modules/setting/config_provider.go +++ b/modules/setting/config_provider.go @@ -196,7 +196,7 @@ func NewConfigProviderFromData(configContent string) (ConfigProvider, error) { // NewConfigProviderFromFile load configuration from file. // NOTE: do not print any log except error. -func NewConfigProviderFromFile(file string, extraConfigs ...string) (ConfigProvider, error) { +func NewConfigProviderFromFile(file string) (ConfigProvider, error) { cfg := ini.Empty(configProviderLoadOptions()) loadedFromEmpty := true @@ -213,12 +213,6 @@ func NewConfigProviderFromFile(file string, extraConfigs ...string) (ConfigProvi } } - for _, s := range extraConfigs { - if err := cfg.Append([]byte(s)); err != nil { - return nil, fmt.Errorf("unable to append more config: %v", err) - } - } - cfg.NameMapper = ini.SnackCase return &iniConfigProvider{ file: file, diff --git a/modules/setting/indexer.go b/modules/setting/indexer.go index 16f3d80168cbc..15f61502427db 100644 --- a/modules/setting/indexer.go +++ b/modules/setting/indexer.go @@ -53,21 +53,24 @@ var Indexer = struct { func loadIndexerFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("indexer") Indexer.IssueType = sec.Key("ISSUE_INDEXER_TYPE").MustString("bleve") - Indexer.IssuePath = filepath.ToSlash(sec.Key("ISSUE_INDEXER_PATH").MustString(filepath.ToSlash(filepath.Join(AppDataPath, "indexers/issues.bleve")))) - if !filepath.IsAbs(Indexer.IssuePath) { - Indexer.IssuePath = filepath.ToSlash(filepath.Join(AppWorkPath, Indexer.IssuePath)) - } - Indexer.IssueConnStr = sec.Key("ISSUE_INDEXER_CONN_STR").MustString(Indexer.IssueConnStr) - - if Indexer.IssueType == "meilisearch" { - u, err := url.Parse(Indexer.IssueConnStr) - if err != nil { - log.Warn("Failed to parse ISSUE_INDEXER_CONN_STR: %v", err) - u = &url.URL{} + if Indexer.IssueType == "bleve" { + Indexer.IssuePath = filepath.ToSlash(sec.Key("ISSUE_INDEXER_PATH").MustString(filepath.ToSlash(filepath.Join(AppDataPath, "indexers/issues.bleve")))) + if !filepath.IsAbs(Indexer.IssuePath) { + Indexer.IssuePath = filepath.ToSlash(filepath.Join(AppWorkPath, Indexer.IssuePath)) + } + fatalDuplicatedPath("issue_indexer", Indexer.IssuePath) + } else { + Indexer.IssueConnStr = sec.Key("ISSUE_INDEXER_CONN_STR").MustString(Indexer.IssueConnStr) + if Indexer.IssueType == "meilisearch" { + u, err := url.Parse(Indexer.IssueConnStr) + if err != nil { + log.Warn("Failed to parse ISSUE_INDEXER_CONN_STR: %v", err) + u = &url.URL{} + } + Indexer.IssueConnAuth, _ = u.User.Password() + u.User = nil + Indexer.IssueConnStr = u.String() } - Indexer.IssueConnAuth, _ = u.User.Password() - u.User = nil - Indexer.IssueConnStr = u.String() } Indexer.IssueIndexerName = sec.Key("ISSUE_INDEXER_NAME").MustString(Indexer.IssueIndexerName) diff --git a/modules/setting/path.go b/modules/setting/path.go index 0fdc305aa160c..b2cca0acbffd2 100644 --- a/modules/setting/path.go +++ b/modules/setting/path.go @@ -66,8 +66,12 @@ func init() { AppWorkPath = filepath.Dir(AppPath) } + fatalDuplicatedPath("app_work_path", AppWorkPath) + appWorkPathBuiltin = AppWorkPath customPathBuiltin = CustomPath + + fatalDuplicatedPath("custom_path", CustomPath) customConfBuiltin = CustomConf } diff --git a/modules/setting/repository.go b/modules/setting/repository.go index a6f0ed8833589..7990021aaa2b5 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -285,6 +285,9 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { } else { RepoRootPath = filepath.Clean(RepoRootPath) } + + fatalDuplicatedPath("repository.ROOT", RepoRootPath) + defaultDetectedCharsetsOrder := make([]string, 0, len(Repository.DetectedCharsetsOrder)) for _, charset := range Repository.DetectedCharsetsOrder { defaultDetectedCharsetsOrder = append(defaultDetectedCharsetsOrder, strings.ToLower(strings.TrimSpace(charset))) diff --git a/modules/setting/server.go b/modules/setting/server.go index c09b91612a942..0dea4e1ac75b8 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -7,7 +7,6 @@ import ( "encoding/base64" "net" "net/url" - "path" "path/filepath" "strconv" "strings" @@ -321,17 +320,19 @@ func loadServerFrom(rootCfg ConfigProvider) { } StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(StaticRootPath) StaticCacheTime = sec.Key("STATIC_CACHE_TIME").MustDuration(6 * time.Hour) - AppDataPath = sec.Key("APP_DATA_PATH").MustString(path.Join(AppWorkPath, "data")) + AppDataPath = sec.Key("APP_DATA_PATH").MustString(filepath.Join(AppWorkPath, "data")) if !filepath.IsAbs(AppDataPath) { AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath)) } + fatalDuplicatedPath("app_data_path", AppDataPath) EnableGzip = sec.Key("ENABLE_GZIP").MustBool() EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false) - PprofDataPath = sec.Key("PPROF_DATA_PATH").MustString(path.Join(AppWorkPath, "data/tmp/pprof")) + PprofDataPath = sec.Key("PPROF_DATA_PATH").MustString(filepath.Join(AppWorkPath, "data/tmp/pprof")) if !filepath.IsAbs(PprofDataPath) { PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath) } + fatalDuplicatedPath("pprof_data_path", PprofDataPath) landingPage := sec.Key("LANDING_PAGE").MustString("home") switch landingPage { diff --git a/modules/setting/session.go b/modules/setting/session.go index 664c66f86957d..8b9b754b38702 100644 --- a/modules/setting/session.go +++ b/modules/setting/session.go @@ -5,7 +5,6 @@ package setting import ( "net/http" - "path" "path/filepath" "strings" @@ -44,9 +43,10 @@ func loadSessionFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("session") SessionConfig.Provider = sec.Key("PROVIDER").In("memory", []string{"memory", "file", "redis", "mysql", "postgres", "couchbase", "memcache", "db"}) - SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(path.Join(AppDataPath, "sessions")), "\" ") + SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(filepath.Join(AppDataPath, "sessions")), "\" ") if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) { - SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig) + SessionConfig.ProviderConfig = filepath.Join(AppWorkPath, SessionConfig.ProviderConfig) + fatalDuplicatedPath("session", SessionConfig.ProviderConfig) } SessionConfig.CookieName = sec.Key("COOKIE_NAME").MustString("i_like_gitea") SessionConfig.CookiePath = AppSubURL + "/" // there was a bug, old code only set CookePath=AppSubURL, no trailing slash diff --git a/modules/setting/setting.go b/modules/setting/setting.go index d444d9a0175c6..6e7ce7e67fd3e 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -90,9 +90,9 @@ func PrepareAppDataPath() error { return nil } -func InitCfgProvider(file string, extraConfigs ...string) { +func InitCfgProvider(file string) { var err error - if CfgProvider, err = NewConfigProviderFromFile(file, extraConfigs...); err != nil { + if CfgProvider, err = NewConfigProviderFromFile(file); err != nil { log.Fatal("Unable to init config provider from %q: %v", file, err) } CfgProvider.DisableSaving() // do not allow saving the CfgProvider into file, it will be polluted by the "MustXxx" calls @@ -226,3 +226,12 @@ func LoadSettingsForInstall() { loadServiceFrom(CfgProvider) loadMailerFrom(CfgProvider) } + +var uniquePaths = make(map[string]string) + +func fatalDuplicatedPath(name, p string) { + if targetName, ok := uniquePaths[p]; ok && targetName != name { + log.Fatal("storage path %q is being used by %q and %q and all storage paths must be unique to prevent data loss.", p, targetName, name) + } + uniquePaths[p] = name +} diff --git a/modules/setting/storage.go b/modules/setting/storage.go index f937c7cff3990..23b08df1013b8 100644 --- a/modules/setting/storage.go +++ b/modules/setting/storage.go @@ -240,6 +240,8 @@ func getStorageForLocal(targetSec, overrideSec ConfigSection, tp targetSecType, } } + fatalDuplicatedPath("storage."+name, storage.Path) + return &storage, nil } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 51e175fba8ea2..56d6158bd8fdf 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -98,6 +98,7 @@ type Repository struct { AllowRebase bool `json:"allow_rebase"` AllowRebaseMerge bool `json:"allow_rebase_explicit"` AllowSquash bool `json:"allow_squash_merge"` + AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge"` AllowRebaseUpdate bool `json:"allow_rebase_update"` DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"` DefaultMergeStyle string `json:"default_merge_style"` @@ -195,6 +196,8 @@ type EditRepoOption struct { AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"` // either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. AllowSquash *bool `json:"allow_squash_merge,omitempty"` + // either `true` to allow fast-forward-only merging pull requests, or `false` to prevent fast-forward-only merging. + AllowFastForwardOnly *bool `json:"allow_fast_forward_only_merge,omitempty"` // either `true` to allow mark pr as merged manually, or `false` to prevent it. AllowManualMerge *bool `json:"allow_manual_merge,omitempty"` // either `true` to enable AutodetectManualMerge, or `false` to prevent it. Note: In some special cases, misjudgments can occur. @@ -203,7 +206,7 @@ type EditRepoOption struct { AllowRebaseUpdate *bool `json:"allow_rebase_update,omitempty"` // set to `true` to delete pr branch after merge by default DefaultDeleteBranchAfterMerge *bool `json:"default_delete_branch_after_merge,omitempty"` - // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", or "squash". + // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", "squash", or "fast-forward-only". DefaultMergeStyle *string `json:"default_merge_style,omitempty"` // set to `true` to allow edits from maintainers by default DefaultAllowMaintainerEdit *bool `json:"default_allow_maintainer_edit,omitempty"` diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 2424ee3fb621d..5164217616fc6 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -4,6 +4,7 @@ explore=Εξερεύνηση help=Βοήθεια logo=Λογότυπο sign_in=Είσοδος +sign_in_with_provider=Είσοδος με %s sign_in_or=ή sign_out=Έξοδος sign_up=Εγγραφή @@ -16,6 +17,7 @@ template=Πρότυπο language=Γλώσσα notifications=Ειδοποιήσεις active_stopwatch=Ενεργή Καταγραφή Χρόνου +tracked_time_summary=Περίληψη του χρόνου παρακολούθησης με βάση τα φίλτρα της λίστας ζητημάτων create_new=Δημιουργία… user_profile_and_more=Προφίλ και ρυθμίσεις… signed_in_as=Είσοδος ως @@ -79,6 +81,7 @@ milestones=Ορόσημα ok=OK cancel=Ακύρωση +retry=Επανάληψη rerun=Επανεκτέλεση rerun_all=Επανεκτέλεση όλων save=Αποθήκευση @@ -88,12 +91,15 @@ remove=Αφαίρεση remove_all=Αφαίρεση Όλων remove_label_str=`Αφαίρεση του αντικειμένου "%s"` edit=Επεξεργασία +view=Προβολή enabled=Ενεργοποιημένο disabled=Απενεργοποιημένο +locked=Κλειδωμένο copy=Αντιγραφή copy_url=Αντιγραφή URL +copy_hash=Αντιγραφή hash copy_content=Αντιγραφή περιεχομένου copy_branch=Αντιγραφή ονόματος κλάδου copy_success=Αντιγράφηκε! @@ -106,6 +112,7 @@ loading=Φόρτωση… error=Σφάλμα error404=Η σελίδα που προσπαθείτε να φτάσετε είτε δεν υπάρχει είτε δεν είστε εξουσιοδοτημένοι για να την δείτε. +go_back=Επιστροφή never=Ποτέ unknown=Άγνωστη @@ -127,7 +134,9 @@ concept_user_organization=Οργανισμός show_timestamps=Εμφάνιση χρονοσημάνσεων show_log_seconds=Εμφάνιση δευτερολέπτων show_full_screen=Εμφάνιση πλήρους οθόνης +download_logs=Λήψη καταγραφών +confirm_delete_selected=Επιβεβαιώνετε τη διαγραφή όλων των επιλεγμένων στοιχείων; name=Όνομα value=Τιμή @@ -166,6 +175,7 @@ string.desc=Z - A [error] occurred=Παρουσιάστηκε ένα σφάλμα +report_message=Αν πιστεύετε ότι αυτό είναι ένα πρόβλημα στο Gitea, παρακαλούμε αναζητήστε ζητήματα στο GitHub ή ανοίξτε ένα νέο ζήτημα εάν είναι απαραίτητο. missing_csrf=Bad Request: δεν υπάρχει διακριτικό CSRF invalid_csrf=Λάθος Αίτημα: μη έγκυρο διακριτικό CSRF not_found=Ο προορισμός δεν βρέθηκε. @@ -174,6 +184,7 @@ network_error=Σφάλμα δικτύου [startpage] app_desc=Μια ανώδυνη, αυτο-φιλοξενούμενη υπηρεσία Git install=Εύκολο στην εγκατάσταση +install_desc=Απλά εκτελέστε το αρχείο προγράμματος για την πλατφόρμα σας, χρήσιμοποιήστε το με το Docker, ή εγκαταστήστε το πακέτο. platform=Πολυπλατφορμικό platform_desc=Ο Gitea τρέχει οπουδήποτε Go μπορεί να γίνει compile για: Windows, macOS, Linux, ARM, κλπ. Επιλέξτε αυτό που αγαπάτε! lightweight=Ελαφρύ @@ -218,6 +229,7 @@ repo_path_helper=Τα απομακρυσμένα αποθετήρια Git θα lfs_path=Ριζική Διαδρομή Git LFS lfs_path_helper=Τα αρχεία που παρακολουθούνται από το Git LFS θα αποθηκεύονται σε αυτόν τον φάκελο. Αφήστε κενό για να το απενεργοποιήσετε. run_user=Εκτέλεση Σαν Χρήστη +run_user_helper=Το όνομα του χρήστη του λειτουργικού συστήματος ο οποίος εκτελεί το Gitea. Επισημαίνεται ότι αυτός ο χρήστης πρέπει να έχει πρόσβαση στο ριζικό φάκελο του αποθετηρίου. domain=Domain Διακομιστή domain_helper=Όνομα domain διακομιστή ή η διεύθυνση του. ssh_port=Θύρα της υπηρεσίας SSH @@ -290,6 +302,7 @@ password_algorithm_helper=Ορίστε τον αλγόριθμο κατακερ enable_update_checker=Ενεργοποίηση Ελεγκτή Ενημερώσεων enable_update_checker_helper=Ελέγχει περιοδικά για νέες εκδόσεις κάνοντας σύνδεση στο gitea.io. env_config_keys=Ρυθμίσεις Περιβάλλοντος +env_config_keys_prompt=Οι ακόλουθες μεταβλητές περιβάλλοντος θα εφαρμοστούν επίσης στο αρχείο ρυθμίσεων σας: [home] uname_holder=Όνομα Χρήστη ή Διεύθυνση Email @@ -348,9 +361,11 @@ disable_register_prompt=Η εγγραφή είναι απενεργοποιημ disable_register_mail=Η Επιβεβαίωση email για την εγγραφή είναι απενεργοποιημένη. manual_activation_only=Επικοινωνήστε με το διαχειριστή της υπηρεσίας για να ολοκληρώσετε την ενεργοποίηση. remember_me=Απομνημόνευση αυτής της συσκευής +remember_me.compromised=Το διακριτικό σύνδεσης δεν είναι πλέον έγκυρο, αυτό ίσως υποδεικνύει έναν κλεμμένο λογαριασμό. Παρακαλώ ελέγξτε το λογαριασμό σας για ασυνήθιστες δραστηριότητες. forgot_password_title=Ξέχασα Τον Κωδικό Πρόσβασης forgot_password=Ξεχάσατε τον κωδικό πρόσβασης; sign_up_now=Χρειάζεστε λογαριασμό; Εγγραφείτε τώρα. +sign_up_successful=Ο λογαριασμός δημιουργήθηκε επιτυχώς. Καλώς ορίσατε! confirmation_mail_sent_prompt=Ένα νέο email επιβεβαίωσης έχει σταλεί στο %s. Παρακαλώ ελέγξτε τα εισερχόμενα σας μέσα στις επόμενες %s για να ολοκληρώσετε τη διαδικασία εγγραφής. must_change_password=Ενημερώστε τον κωδικό πρόσβασης σας allow_password_change=Απαιτείται από το χρήστη να αλλάξει τον κωδικό πρόσβασης (συνιστόμενο) @@ -358,6 +373,7 @@ reset_password_mail_sent_prompt=Ένα email επιβεβαίωσης έχει active_your_account=Ενεργοποιήστε Το Λογαριασμό Σας account_activated=Ο λογαριασμός έχει ενεργοποιηθεί prohibit_login=Απαγορεύεται η Σύνδεση +prohibit_login_desc=Ο λογαριασμός σας δεν επιτρέπεται να συνδεθεί, παρακαλούμε επικοινωνήστε με το διαχειριστή σας. resent_limit_prompt=Έχετε ήδη ζητήσει ένα email ενεργοποίησης πρόσφατα. Παρακαλώ περιμένετε 3 λεπτά και προσπαθήστε ξανά. has_unconfirmed_mail=Γεια σας %s, έχετε μια ανεπιβεβαίωτη διεύθυνση ηλεκτρονικού ταχυδρομείου (%s). Εάν δεν έχετε λάβει email επιβεβαίωσης ή χρειάζεται να αποστείλετε εκ νέου ένα νέο, παρακαλώ κάντε κλικ στο παρακάτω κουμπί. resend_mail=Κάντε κλικ εδώ για να στείλετε ξανά το email ενεργοποίησης @@ -367,6 +383,7 @@ reset_password=Ανάκτηση Λογαριασμού invalid_code=Ο κωδικός επιβεβαίωσης δεν είναι έγκυρος ή έχει λήξει. invalid_password=Ο κωδικός πρόσβασης σας δεν ταιριάζει με τον κωδικό που χρησιμοποιήθηκε για τη δημιουργία του λογαριασμού. reset_password_helper=Ανάκτηση Λογαριασμού +reset_password_wrong_user=Έχετε συνδεθεί ως %s, αλλά ο σύνδεσμος ανάκτησης λογαριασμού προορίζεται για το %s password_too_short=Το μήκος του κωδικού πρόσβασης δεν μπορεί να είναι μικρότερο από %d χαρακτήρες. non_local_account=Οι μη τοπικοί χρήστες δεν μπορούν να ενημερώσουν τον κωδικό πρόσβασής τους μέσω του διεπαφής web του Gitea. verify=Επαλήθευση @@ -391,6 +408,7 @@ openid_connect_title=Σύνδεση σε υπάρχων λογαριασμό openid_connect_desc=Το επιλεγμένο OpenID URI είναι άγνωστο. Συνδέστε το με ένα νέο λογαριασμό εδώ. openid_register_title=Δημιουργία νέου λογαριασμού openid_register_desc=Το επιλεγμένο OpenID URI είναι άγνωστο. Συνδέστε το με ένα νέο λογαριασμό εδώ. +openid_signin_desc=Εισάγετε το OpenID URI σας. Για παράδειγμα: alice.openid.example.org ή https://openid.example.org/alice. disable_forgot_password_mail=Η ανάκτηση λογαριασμού είναι απενεργοποιημένη επειδή δεν έχει οριστεί email. Παρακαλούμε επικοινωνήστε με το διαχειριστή. disable_forgot_password_mail_admin=Η ανάκτηση λογαριασμού είναι διαθέσιμη μόνο όταν έχει οριστεί το email. Παρακαλούμε ορίστει το email σας για να ενεργοποιήσετε την ανάκτηση λογαριασμού. email_domain_blacklisted=Δεν μπορείτε να εγγραφείτε με τη διεύθυνση email σας. @@ -400,7 +418,9 @@ authorize_application_created_by=Αυτή η εφαρμογή δημιουργή authorize_application_description=Εάν παραχωρήσετε την πρόσβαση, θα μπορεί να έχει πρόσβαση και να γράφει σε όλες τις πληροφορίες του λογαριασμού σας, συμπεριλαμβανομένων των ιδιωτικών αποθετηρίων και οργανισμών. authorize_title=Εξουσιοδότηση του "%s" για έχει πρόσβαση στο λογαριασμό σας; authorization_failed=Αποτυχία εξουσιοδότησης +authorization_failed_desc=Η εξουσιοδότηση απέτυχε επειδή εντοπίστηκε μια μη έγκυρη αίτηση. Παρακαλούμε επικοινωνήστε με το συντηρητή της εφαρμογής που προσπαθήσατε να εξουσιοδοτήσετε. sspi_auth_failed=Αποτυχία ταυτοποίησης SSPI +password_pwned=Ο κωδικός πρόσβασης που επιλέξατε είναι σε μια λίστα κλεμμένων κωδικών πρόσβασης που προηγουμένως εκτέθηκαν σε παραβίαση δημόσιων δεδομένων. Παρακαλώ δοκιμάστε ξανά με διαφορετικό κωδικό πρόσβασης και σκεφτείτε να αλλάξετε αυτόν τον κωδικό πρόσβασης όπου αλλού χρησιμοποιείται. password_pwned_err=Δεν ήταν δυνατή η ολοκλήρωση του αιτήματος προς το HaveIBeenPwned [mail] @@ -415,6 +435,7 @@ activate_account.text_1=Γεια σας %[1]s, ευχαριστούμε activate_account.text_2=Παρακαλούμε κάντε κλικ στον παρακάτω σύνδεσμο για να ενεργοποιήσετε το λογαριασμό σας μέσα σε %s: activate_email=Επιβεβαιώστε τη διεύθυνση email σας +activate_email.title=%s, παρακαλώ επαληθεύστε τη διεύθυνση email σας activate_email.text=Παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για να επαληθεύσετε τη διεύθυνση email σας στο %s: register_notify=Καλώς ήλθατε στο Gitea @@ -584,6 +605,8 @@ user_bio=Βιογραφικό disabled_public_activity=Αυτός ο χρήστης έχει απενεργοποιήσει τη δημόσια προβολή της δραστηριότητας. email_visibility.limited=Η διεύθυνση email σας είναι ορατή σε όλους τους ταυτοποιημένους χρήστες email_visibility.private=Η διεύθυνση email σας είναι ορατή μόνο σε εσάς και στους διαχειριστές +show_on_map=Εμφάνιση της τοποθεσίας στο χάρτη +settings=Ρυθμίσεις Χρήστη form.name_reserved=Το όνομα χρήστη "%s" είναι δεσμευμένο. form.name_pattern_not_allowed=Το μοτίβο "%s" δεν επιτρέπεται μέσα σε ένα όνομα χρήστη. @@ -605,9 +628,13 @@ delete=Διαγραφή Λογαριασμού twofa=Έλεγχος Ταυτότητας Δύο Παραγόντων account_link=Συνδεδεμένοι Λογαριασμοί organization=Οργανισμοί +uid=UID webauthn=Κλειδιά Ασφαλείας public_profile=Δημόσιο Προφίλ +biography_placeholder=Πείτε μας λίγο για τον εαυτό σας! (Μπορείτε να γράψετε με Markdown) +location_placeholder=Μοιραστείτε την κατά προσέγγιση τοποθεσία σας με άλλους +profile_desc=Ελέγξτε πώς εμφανίζεται το προφίλ σας σε άλλους χρήστες. Η κύρια διεύθυνση email σας θα χρησιμοποιηθεί για ειδοποιήσεις, ανάκτηση κωδικού πρόσβασης και λειτουργίες Git που βασίζονται στο web. password_username_disabled=Οι μη τοπικοί χρήστες δεν επιτρέπεται να αλλάξουν το όνομα χρήστη τους. Επικοινωνήστε με το διαχειριστή σας για περισσότερες λεπτομέρειες. full_name=Πλήρες Όνομα website=Ιστοσελίδα @@ -619,6 +646,8 @@ update_language_not_found=Η γλώσσα "%s" δεν είναι διαθέσι update_language_success=Η γλώσσα ενημερώθηκε. update_profile_success=Το προφίλ σας έχει ενημερωθεί. change_username=Το όνομα χρήστη σας έχει αλλάξει. +change_username_prompt=Σημείωση: Αλλάζοντας το όνομα χρήστη σας αλλάζει επίσης το URL του λογαριασμού σας. +change_username_redirect_prompt=Το παλιό όνομα χρήστη θα ανακατευθύνει μέχρι να ζητηθεί ξανά. continue=Συνέχεια cancel=Ακύρωση language=Γλώσσα @@ -643,6 +672,7 @@ comment_type_group_project=Έργο comment_type_group_issue_ref=Αναφορά ζητήματος saved_successfully=Οι ρυθμίσεις σας αποθηκεύτηκαν επιτυχώς. privacy=Απόρρητο +keep_activity_private=Απόκρυψη Δραστηριότητας από τη σελίδα προφίλ keep_activity_private_popup=Με αυτή την επιλογή η δραστηριότητα σας είναι ορατή μόνο σε εσάς και τους διαχειριστές lookup_avatar_by_mail=Αναζήτηση ενός Avatar με διεύθυνση email @@ -652,12 +682,14 @@ choose_new_avatar=Επιλέξτε νέα εικόνα update_avatar=Ενημέρωση Εικόνας delete_current_avatar=Διαγραφή Τρέχουσας Εικόνας uploaded_avatar_not_a_image=Το αρχείο που ανεβάσατε δεν είναι εικόνα. +uploaded_avatar_is_too_big=Το μέγεθος αρχείου που ανέβηκε (%d KiB) υπερβαίνει το μέγιστο μέγεθος (%d KiB). update_avatar_success=Η εικόνα σας έχει ενημερωθεί. update_user_avatar_success=Το avatar του χρήστη ενημερώθηκε. change_password=Ενημέρωση Κωδικού Πρόσβασης old_password=Τρέχων Κωδικός Πρόσβασης new_password=Νέος Κωδικός Πρόσβασης +retype_new_password=Επιβεβαίωση Νέου Κωδικού Πρόσβασης password_incorrect=Ο τρέχων κωδικός πρόσβασης είναι λάθος. change_password_success=Ο κωδικός πρόσβασής σας έχει ενημερωθεί. Από εδώ και τώρα συνδέεστε χρησιμοποιώντας τον νέο κωδικό πρόσβασής σας. password_change_disabled=Οι μη τοπικοί χρήστες δεν μπορούν να ενημερώσουν τον κωδικό πρόσβασής τους μέσω του διεπαφής web του Gitea. @@ -666,6 +698,7 @@ emails=Διευθύνσεις Email manage_emails=Διαχείριση Διευθύνσεων Email manage_themes=Επιλέξτε προεπιλεγμένο θέμα διεπαφής manage_openid=Διαχείριση Διευθύνσεων OpenID +email_desc=Η κύρια διεύθυνση ηλεκτρονικού ταχυδρομείου σας θα χρησιμοποιηθεί για ειδοποιήσεις, ανάκτηση του κωδικού πρόσβασης και, εφόσον δεν είναι κρυμμένη, λειτουργίες Git στον ιστότοπο. theme_desc=Αυτό θα είναι το προεπιλεγμένο θέμα διεπαφής σας σε όλη την ιστοσελίδα. primary=Κύριο activated=Ενεργό @@ -673,6 +706,7 @@ requires_activation=Απαιτείται ενεργοποίηση primary_email=Αλλαγή κυριότητας activate_email=Αποστολή Ενεργοποίησης activations_pending=Εκκρεμούν Ενεργοποιήσεις +can_not_add_email_activations_pending=Εκκρεμεί μια ενεργοποίηση, δοκιμάστε ξανά σε λίγα λεπτά αν θέλετε να προσθέσετε ένα νέο email. delete_email=Αφαίρεση email_deletion=Αφαίρεση Διεύθυνσης Email email_deletion_desc=Η διεύθυνση ηλεκτρονικού ταχυδρομείου και οι σχετικές πληροφορίες θα αφαιρεθούν από τον λογαριασμό σας. Οι υποβολές Git από αυτή τη διεύθυνση email θα παραμείνουν αμετάβλητες. Συνέχεια; @@ -691,6 +725,7 @@ add_email_success=Η νέα διεύθυνση email έχει προστεθεί email_preference_set_success=Οι προτιμήσεις email έχουν οριστεί επιτυχώς. add_openid_success=Προστέθηκε η νέα διεύθυνση OpenID. keep_email_private=Απόκρυψη Διεύθυνσης Email +keep_email_private_popup=Αυτό θα κρύψει τη διεύθυνση ηλεκτρονικού ταχυδρομείου σας από το προφίλ σας, καθώς και όταν κάνετε ένα pull request ή επεξεργαστείτε ένα αρχείο χρησιμοποιώντας τη διεπαφή ιστού. Οι ωθούμενες υποβολές δεν θα τροποποιηθούν. Χρησιμοποιήστε το %s στις υποβολές για να τις συσχετίσετε με το λογαριασμό σας. openid_desc=Το OpenID σας επιτρέπει να αναθέσετε τον έλεγχο ταυτότητας σε έναν εξωτερικό πάροχο. manage_ssh_keys=Διαχείριση SSH Κλειδιών @@ -769,7 +804,9 @@ ssh_disabled=SSH Απενεργοποιημένο ssh_signonly=Το SSH είναι απενεργοποιημένο αυτή τη στιγμή, έτσι αυτά τα κλειδιά είναι μόνο για την επαλήθευση υπογραφής των υποβολών. ssh_externally_managed=Αυτό το κλειδί SSH διαχειρίζεται εξωτερικά για αυτόν το χρήστη manage_social=Διαχείριση Συσχετιζόμενων Λογαριασμών Κοινωνικών Δικτύων +social_desc=Αυτοί οι κοινωνικοί λογαριασμοί μπορούν να χρησιμοποιηθούν για να συνδεθείτε στο λογαριασμό σας. Βεβαιωθείτε ότι τους αναγνωρίζετε όλους. unbind=Αποσύνδεση +unbind_success=Ο κοινωνικός λογαριασμός έχει διαγραφεί επιτυχώς. manage_access_token=Διαχείριση Διακριτικών Πρόσβασης generate_new_token=Δημιουργία Νέου Διακριτικού @@ -790,6 +827,8 @@ permissions_access_all=Όλα (δημόσια, ιδιωτικά, και περι select_permissions=Επιλέξτε δικαιώματα permission_no_access=Καμία Πρόσβαση permission_read=Αναγνωσμένες +permission_write=Ανάγνωση και Εγγραφή +access_token_desc=Τα επιλεγμένα δικαιώματα διακριτικών περιορίζουν την άδεια μόνο στις αντίστοιχες διαδρομές API. Διαβάστε την τεκμηρίωση για περισσότερες πληροφορίες. at_least_one_permission=Πρέπει να επιλέξετε τουλάχιστον ένα δικαίωμα για να δημιουργήσετε ένα διακριτικό permissions_list=Δικαιώματα: @@ -801,6 +840,8 @@ remove_oauth2_application_desc=Η αφαίρεση μιας εφαρμογής O remove_oauth2_application_success=Η εφαρμογή έχει διαγραφεί. create_oauth2_application=Δημιουργία νέας εφαρμογής OAuth2 create_oauth2_application_button=Δημιουργία Εφαρμογής +create_oauth2_application_success=Έχετε δημιουργήσει με επιτυχία μια νέα εφαρμογή OAuth2. +update_oauth2_application_success=Έχετε ενημερώσει με επιτυχία την εφαρμογή OAuth2. oauth2_application_name=Όνομα Εφαρμογής oauth2_confidential_client=Εμπιστευτικός Πελάτης. Επιλέξτε το για εφαρμογές που διατηρούν το μυστικό κωδικό κρυφό, όπως πχ οι εφαρμογές ιστού. Μην επιλέγετε για εγγενείς εφαρμογές, συμπεριλαμβανομένων εφαρμογών επιφάνειας εργασίας και εφαρμογών για κινητά. oauth2_redirect_uris=URI Ανακατεύθυνσης. Χρησιμοποιήστε μια νέα γραμμή για κάθε URI. @@ -809,13 +850,18 @@ oauth2_client_id=Ταυτότητα Πελάτη oauth2_client_secret=Μυστικό Πελάτη oauth2_regenerate_secret=Αναδημιουργία Μυστικού oauth2_regenerate_secret_hint=Χάσατε το μυστικό σας; +oauth2_client_secret_hint=Το μυστικό δε θα εμφανιστεί ξανά αν κλείσετε ή ανανεώσετε αυτή τη σελίδα. Παρακαλώ βεβαιωθείτε ότι το έχετε αποθηκεύσει. oauth2_application_edit=Επεξεργασία oauth2_application_create_description=Οι εφαρμογές OAuth2 δίνει πρόσβαση στην εξωτερική εφαρμογή σας σε λογαριασμούς χρηστών σε αυτή την υπηρεσία. +oauth2_application_remove_description=Αφαιρώντας μια εφαρμογή OAuth2 θα αποτραπεί η πρόσβαση αυτής, σε εξουσιοδοτημένους λογαριασμούς χρηστών σε αυτή την υπηρεσία. Συνέχεια; +oauth2_application_locked=Το Gitea κάνει προεγγραφή σε μερικές εφαρμογές OAuth2 κατά την εκκίνηση αν είναι ενεργοποιημένες στις ρυθμίσεις. Για την αποφυγή απροσδόκητης συμπεριφοράς, αυτές δεν μπορούν ούτε να επεξεργαστούν ούτε να καταργηθούν. Παρακαλούμε ανατρέξτε στην τεκμηρίωση OAuth2 για περισσότερες πληροφορίες. authorized_oauth2_applications=Εξουσιοδοτημένες Εφαρμογές OAuth2 +authorized_oauth2_applications_description=Έχετε χορηγήσει πρόσβαση στον προσωπικό σας λογαριασμό σε αυτές τις εφαρμογές τρίτων. Ανακαλέστε την πρόσβαση για εφαρμογές που δεν χρειάζεστε πλέον. revoke_key=Ανάκληση revoke_oauth2_grant=Ανάκληση Πρόσβασης revoke_oauth2_grant_description=Η ανάκληση πρόσβασης για αυτή την εξωτερική εφαρμογή θα αποτρέψει αυτή την εφαρμογή από την πρόσβαση στα δεδομένα σας. Σίγουρα; +revoke_oauth2_grant_success=Η πρόσβαση ανακλήθηκε επιτυχώς. twofa_desc=Ο έλεγχος ταυτότητας δύο παραγόντων ενισχύει την ασφάλεια του λογαριασμού σας. twofa_is_enrolled=Ο λογαριασμός σας είναι εγγεγραμμένος σε έλεγχο ταυτότητας δύο παραγόντων. @@ -850,6 +896,7 @@ remove_account_link_success=Ο συνδεδεμένος λογαριασμός orgs_none=Δεν είστε μέλος σε κάποιο οργανισμό. +repos_none=Δεν κατέχετε κάποιο αποθετήριο. delete_account=Διαγραφή Του Λογαριασμού Σας delete_prompt=Αυτή η ενέργεια θα διαγράψει μόνιμα το λογαριασμό σας. ΔΕΝ ΘΑ ΜΠΟΡΕΙ να επανέλθει. @@ -882,6 +929,7 @@ template_helper=Μετατροπή σε πρότυπο αποθετήριο template_description=Τα πρότυπα αποθετήρια επιτρέπουν στους χρήστες να δημιουργήσουν νέα αποθετήρια με την ίδια δομή, αρχεία και προαιρετικές ρυθμίσεις. visibility=Ορατότητα visibility_description=Μόνο ο ιδιοκτήτης ή τα μέλη του οργανισμού εάν έχουν δικαιώματα, θα είναι σε θέση να το δουν. +visibility_helper=Αλλάξτε το αποθετήριο σε ιδιωτικό visibility_helper_forced=Ο διαχειριστής σας αναγκάζει τα νέα αποθετήρια να είναι ιδιωτικά. visibility_fork_helper=(Αλλάζοντας αυτό θα επηρεάσει όλα τα forks.) clone_helper=Χρειάζεστε βοήθεια για τη κλωνοποίηση; Επισκεφθείτε τη Βοήθεια. @@ -890,6 +938,8 @@ fork_from=Fork Από Το already_forked=Έχετε ήδη κάνει fork το %s fork_to_different_account=Fork σε διαφορετικό λογαριασμό fork_visibility_helper=Η ορατότητα ενός fork αποθετηρίου δεν μπορεί να αλλάξει. +fork_branch=Κλάδος που θα κλωνοποιηθεί στο fork +all_branches=Όλοι οι κλάδοι use_template=Χρήση αυτού του πρότυπου clone_in_vsc=Κλωνοποίηση στο VS Code download_zip=Λήψη ZIP @@ -918,6 +968,7 @@ trust_model_helper_collaborator_committer=Συνεργάτης+Υποβολέα trust_model_helper_default=Προεπιλογή: Χρησιμοποιήστε το προεπιλεγμένο μοντέλο εμπιστοσύνης για αυτήν την εγκατάσταση create_repo=Δημιουργία Αποθετηρίου default_branch=Προεπιλεγμένος Κλάδος +default_branch_label=προεπιλογή default_branch_helper=Ο προεπιλεγμένος κλάδος είναι ο βασικός κλάδος για pull requests και υποβολές κώδικα. mirror_prune=Καθαρισμός mirror_prune_desc=Αφαίρεση παρωχημένων αναφορών απομακρυσμένης-παρακολούθησης @@ -926,6 +977,8 @@ mirror_interval_invalid=Το χρονικό διάστημα του ειδώλο mirror_sync_on_commit=Συγχρονισμός κατά την ώθηση mirror_address=Κλωνοποίηση Από Το URL mirror_address_desc=Τοποθετήστε όλα τα απαιτούμενα διαπιστευτήρια στην ενότητα Εξουσιοδότηση. +mirror_address_url_invalid=Η διεύθυνση URL που δόθηκε δεν είναι έγκυρη. Πρέπει να κάνετε escape όλα τα στοιχεία του url σωστά. +mirror_address_protocol_invalid=Η παρεχόμενη διεύθυνση URL δεν είναι έγκυρη. Μόνο οι τοποθεσίες http(s):// ή git:// μπορούν να χρησιμοποιηθούν για τη δημιουργία ειδώλου. mirror_lfs=Large File Storage (LFS) mirror_lfs_desc=Ενεργοποίηση αντικατοπτρισμού δεδομένων LFS. mirror_lfs_endpoint=Άκρο LFS @@ -1094,6 +1147,9 @@ file_view_rendered=Προβολή Απόδοσης file_view_raw=Προβολή Ακατέργαστου file_permalink=Permalink file_too_large=Το αρχείο είναι πολύ μεγάλο για να εμφανιστεί. +invisible_runes_description=`Αυτό το αρχείο περιέχει αόρατους χαρακτήρες Unicode που δεν διακρίνονται από ανθρώπους, αλλά μπορεί να επεξεργάζονται διαφορετικά από έναν υπολογιστή. Αν νομίζετε ότι αυτό είναι σκόπιμο, μπορείτε να αγνοήσετε με ασφάλεια αυτή την προειδοποίηση. Χρησιμοποιήστε το κουμπί Escape για να τους αποκαλύψετε.` +ambiguous_runes_header=`Αυτό το αρχείο περιέχει ασαφείς χαρακτήρες Unicode ` +ambiguous_runes_description=`Αυτό το αρχείο περιέχει χαρακτήρες Unicode που μπορεί να συγχέονται με άλλους χαρακτήρες. Αν νομίζετε ότι αυτό είναι σκόπιμο, μπορείτε να αγνοήσετε με ασφάλεια αυτή την προειδοποίηση. Χρησιμοποιήστε το κουμπί Escape για να τους αποκαλύψετε.` invisible_runes_line=`Αυτή η γραμμή έχει αόρατους χαρακτήρες unicode ` ambiguous_runes_line=`Αυτή η γραμμή έχει ασαφείς χαρακτήρες unicode ` ambiguous_character=`ο %[1]c [U+%04[1]X] μπορεί να μπερδευτεί με τον %[2]c [U+%04[2]X]` @@ -1106,11 +1162,15 @@ video_not_supported_in_browser=Το πρόγραμμα περιήγησής σα audio_not_supported_in_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει την ετικέτα HTML5 'audio'. stored_lfs=Αποθηκεύτηκε με το Git LFS symbolic_link=Symbolic link +executable_file=Εκτελέσιμο Αρχείο commit_graph=Γράφημα Υποβολών commit_graph.select=Επιλογή κλάδων commit_graph.hide_pr_refs=Απόκρυψη Pull Requests commit_graph.monochrome=Μονόχρωμο commit_graph.color=Έγχρωμο +commit.contained_in=Αυτή η υποβολή περιλαμβάνεται σε: +commit.contained_in_default_branch=Αυτή η υποβολή είναι μέρος του προεπιλεγμένου κλάδου +commit.load_referencing_branches_and_tags=Φόρτωση κλάδων και ετικετών που παραπέμπουν σε αυτήν την υποβολή blame=Ευθύνη download_file=Λήψη αρχείου normal_view=Κανονική Προβολή @@ -1203,6 +1263,7 @@ commits.signed_by_untrusted_user=Υπογράφηκε από μη έμπιστο commits.signed_by_untrusted_user_unmatched=Υπογράφηκε από ένα μη έμπιστο χρήστη ο οποίος δεν ταιριάζει με τον υποβολέα commits.gpg_key_id=ID Κλειδιού GPG commits.ssh_key_fingerprint=Αποτύπωμα Κλειδιού SSH +commits.view_path=Προβολή σε αυτή τη στιγμή στο ιστορικό commit.operations=Λειτουργίες commit.revert=Απόσυρση @@ -1330,6 +1391,7 @@ issues.delete_branch_at=`διέγραψε το κλάδο %s %s` issues.filter_label=Σήμα issues.filter_label_exclude=`Χρησιμοποιήστε alt + κάντε κλικ/Enter για να εξαιρέσετε τις σημάνσεις` issues.filter_label_no_select=Όλα τα σήματα +issues.filter_label_select_no_label=Χωρίς ετικέτα issues.filter_milestone=Ορόσημο issues.filter_milestone_all=Όλα τα ορόσημα issues.filter_milestone_none=Χωρίς ορόσημα @@ -1380,9 +1442,10 @@ issues.opened_by_fake=άνοιξε το %[1]s από %[2]s issues.closed_by_fake=από %[2]s έκλεισαν %[1]s issues.previous=Προηγούμενο issues.next=Επόμενο -issues.open_title=Ανοιχτά +issues.open_title=Ανοικτό issues.closed_title=Κλειστά issues.draft_title=Προσχέδιο +issues.num_comments_1=%d σχόλιο issues.num_comments=%d σχόλια issues.commented_at=`σχολίασε %s` issues.delete_comment_confirm=Θέλετε σίγουρα να διαγράψετε αυτό το σχόλιο; @@ -1391,6 +1454,7 @@ issues.context.quote_reply=Παράθεση Απάντησης issues.context.reference_issue=Αναφορά σε νέο ζήτημα issues.context.edit=Επεξεργασία issues.context.delete=Διαγραφή +issues.no_content=Δεν υπάρχει περιγραφή. issues.close=Κλείσιμο Ζητήματος issues.comment_pull_merged_at=συγχώνευσε την υποβολή %[1]s στο %[2]s %[3]s issues.comment_manually_pull_merged_at=συγχώνευσε χειροκίνητα την υποβολή %[1]s στο %[2]s %[3]s @@ -1409,8 +1473,17 @@ issues.ref_closed_from=`έκλεισε αυτό το ζήτημ issues.ref_reopened_from=`άνοιξε ξανά αυτό το ζήτημα %[4]s %[2]s` issues.ref_from=`από %[1]s` issues.author=Συγγραφέας +issues.author_helper=Αυτός ο χρήστης είναι ο συγγραφέας. issues.role.owner=Ιδιοκτήτης +issues.role.owner_helper=Αυτός ο χρήστης είναι ο ιδιοκτήτης αυτού του αποθετηρίου. issues.role.member=Μέλος +issues.role.member_helper=Αυτός ο χρήστης είναι μέλος του οργανισμού που κατέχει αυτό το αποθετήριο. +issues.role.collaborator=Συνεργάτης +issues.role.collaborator_helper=Αυτός ο χρήστης έχει προσκληθεί να συνεργαστεί στο αποθετήριο. +issues.role.first_time_contributor=Συντελεστής για πρώτη φορά +issues.role.first_time_contributor_helper=Αυτή είναι η πρώτη συνεισφορά αυτού του χρήστη στο αποθετήριο. +issues.role.contributor=Συντελεστής +issues.role.contributor_helper=Αυτός ο χρήστης έχει προηγούμενές υποβολές στο αποθετήριο. issues.re_request_review=Επαναίτηση ανασκόπησης issues.is_stale=Έχουν υπάρξει αλλαγές σε αυτό το PR από αυτή την αναθεώρηση issues.remove_request_review=Αφαίρεση αιτήματος αναθεώρησης @@ -1425,6 +1498,9 @@ issues.label_title=Όνομα σήματος issues.label_description=Περιγραφή σήματος issues.label_color=Χρώμα σήματος issues.label_exclusive=Αποκλειστικό +issues.label_archive=Αρχειοθέτηση Σήματος +issues.label_archived_filter=Εμφάνιση αρχειοθετημένων σημάτων +issues.label_archive_tooltip=Τα αρχειοθετημένα σήματα εξαιρούνται από τις προτάσεις στην αναζήτηση με σήματα. issues.label_exclusive_desc=Ονομάστε το σήμα πεδίο/στοιχείο για να το κάνετε αμοιβαία αποκλειστικό με άλλα σήματα πεδίου/. issues.label_exclusive_warning=Τυχόν συγκρουόμενα σήματα θα αφαιρεθούν κατά την επεξεργασία των σημάτων ενός ζητήματος ή pull request. issues.label_count=%d σήματα @@ -1479,6 +1555,7 @@ issues.tracking_already_started=`Έχετε ήδη ξεκινήσει την κ issues.stop_tracking=Διακοπή Χρονομέτρου issues.stop_tracking_history=`σταμάτησε να εργάζεται %s` issues.cancel_tracking=Απόρριψη +issues.cancel_tracking_history=`ακύρωσε τη παρακολούθηση χρόνου %s` issues.add_time=Χειροκίνητη Προσθήκη Ώρας issues.del_time=Διαγραφή αυτού του αρχείου χρόνου issues.add_time_short=Προσθήκη Χρόνου @@ -1502,6 +1579,7 @@ issues.due_date_form=εεεε-μμ-ηη issues.due_date_form_add=Προσθήκη ημερομηνίας παράδοσης issues.due_date_form_edit=Επεξεργασία issues.due_date_form_remove=Διαγραφή +issues.due_date_not_writer=Χρειάζεστε πρόσβαση εγγραφής στο αποθετήριο για να ενημερώσετε την ημερομηνία λήξης ενός προβλήματος. issues.due_date_not_set=Δεν ορίστηκε ημερομηνία παράδοσης. issues.due_date_added=πρόσθεσε την ημερομηνία παράδοσης %s %s issues.due_date_modified=τροποποίησε την ημερομηνία παράδοσης από %[2]s σε %[1]s %[3]s @@ -1557,6 +1635,9 @@ issues.review.pending.tooltip=Αυτό το σχόλιο προς το παρό issues.review.review=Αξιολόγηση issues.review.reviewers=Εξεταστές issues.review.outdated=Παρωχημένο +issues.review.outdated_description=Το περιεχόμενο άλλαξε αφού έγινε αυτό το σχόλιο +issues.review.option.show_outdated_comments=Εμφάνιση παρωχημένων σχολίων +issues.review.option.hide_outdated_comments=Απόκρυψη παρωχημένων σχολίων issues.review.show_outdated=Εμφάνιση παροχημένων issues.review.hide_outdated=Απόκρυψη παροχημένων issues.review.show_resolved=Εμφάνιση επιλυμένων @@ -1596,6 +1677,13 @@ pulls.switch_comparison_type=Αλλαγή τύπου σύγκρισης pulls.switch_head_and_base=Αλλαγή κεφαλής και βάσης pulls.filter_branch=Φιλτράρισμα κλάδου pulls.no_results=Δεν βρέθηκαν αποτελέσματα. +pulls.show_all_commits=Εμφάνιση όλων των υποβολών +pulls.show_changes_since_your_last_review=Εμφάνιση αλλαγών από την τελευταία αξιολόγηση +pulls.showing_only_single_commit=Εμφάνιση μόνο αλλαγών της υποβολής %[1]s +pulls.showing_specified_commit_range=Εμφάνιση μόνο των αλλαγών μεταξύ %[1]s..%[2]s +pulls.select_commit_hold_shift_for_range=Επιλέξτε υποβολή. Κρατήστε πατημένο το shift + κάντε κλικ για να επιλέξετε ένα εύρος +pulls.review_only_possible_for_full_diff=Η αξιολόγηση είναι δυνατή μόνο κατά την προβολή της πλήρης διαφοράς +pulls.filter_changes_by_commit=Φιλτράρισμα κατά υποβολή pulls.nothing_to_compare=Αυτοί οι κλάδοι είναι όμοιοι. Δεν υπάρχει ανάγκη να δημιουργήσετε ένα pull request. pulls.nothing_to_compare_and_allow_empty_pr=Αυτοί οι κλάδοι είναι ίσοι. Αυτό το PR θα είναι κενό. pulls.has_pull_request=`Υπάρχει ήδη pull request μεταξύ αυτών των κλάδων: %[2]s#%[3]d` @@ -1627,6 +1715,8 @@ pulls.is_empty=Οι αλλαγές σε αυτόν τον κλάδο είναι pulls.required_status_check_failed=Ορισμένοι απαιτούμενοι έλεγχοι δεν ήταν επιτυχείς. pulls.required_status_check_missing=Λείπουν ορισμένοι απαιτούμενοι έλεγχοι. pulls.required_status_check_administrator=Ως διαχειριστής, μπορείτε ακόμα να συγχωνεύσετε αυτό το pull request. +pulls.blocked_by_rejection=Αυτό το Pull Request έχει αλλαγές που ζητούνται από έναν επίσημο εξεταστή. +pulls.blocked_by_official_review_requests=Αυτό το Pull Request έχει επίσημες αιτήσεις αξιολόγησης. pulls.can_auto_merge_desc=Αυτό το Pull Request μπορεί να συγχωνευθεί αυτόματα. pulls.cannot_auto_merge_desc=Αυτό το pull request δεν μπορεί να συγχωνευθεί αυτόματα λόγω συγκρούσεων. pulls.cannot_auto_merge_helper=Χειροκίνητη Συγχώνευση για την επίλυση των συγκρούσεων. @@ -1661,6 +1751,7 @@ pulls.rebase_conflict_summary=Μήνυμα Σφάλματος pulls.unrelated_histories=H Συγχώνευση Απέτυχε: Η κεφαλή και η βάση της συγχώνευσης δεν μοιράζονται μια κοινή ιστορία. Συμβουλή: Δοκιμάστε μια διαφορετική στρατηγική pulls.merge_out_of_date=Η συγχώνευση απέτυχε: Κατά τη δημιουργία της συγχώνευσης, η βάση ενημερώθηκε. Συμβουλή: Δοκιμάστε ξανά. pulls.head_out_of_date=Η συγχώνευση απέτυχε: Κατά τη δημιουργία της συγχώνευσης, το HEAD ενημερώθηκε. Συμβουλή: Δοκιμάστε ξανά. +pulls.has_merged=Αποτυχία: Το pull request έχει συγχωνευθεί, δεν είναι δυνατή η συγχώνευση ξανά ή να αλλάξει ο κλάδος προορισμού. pulls.push_rejected=Η συγχώνευση απέτυχε: Η ώθηση απορρίφθηκε. Ελέγξτε τα Άγκιστρα Git για αυτό το αποθετήριο. pulls.push_rejected_summary=Μήνυμα Πλήρους Απόρριψης pulls.push_rejected_no_message=H Συγχώνευση Aπέτυχε: Η ώθηση απορρίφθηκε, αλλά δεν υπήρχε απομακρυσμένο μήνυμα.
Ελέγξτε τα Άγκιστρα Git για αυτό το αποθετήριο @@ -1672,6 +1763,8 @@ pulls.status_checks_failure=Κάποιοι έλεγχοι απέτυχαν pulls.status_checks_error=Ορισμένοι έλεγχοι ανέφεραν σφάλματα pulls.status_checks_requested=Απαιτείται pulls.status_checks_details=Λεπτομέρειες +pulls.status_checks_hide_all=Απόκρυψη όλων των ελέγχων +pulls.status_checks_show_all=Εμφάνιση όλων των ελέγχων pulls.update_branch=Ενημέρωση κλάδου με συγχώνευση pulls.update_branch_rebase=Ενημέρωση κλάδου με rebase pulls.update_branch_success=Η ενημέρωση του κλάδου ήταν επιτυχής @@ -1680,6 +1773,11 @@ pulls.outdated_with_base_branch=Αυτός ο κλάδος δεν είναι ε pulls.close=Κλείσιμο Pull Request pulls.closed_at=`έκλεισε αυτό το pull request %[2]s` pulls.reopened_at=`άνοιξε ξανά αυτό το pull request %[2]s` +pulls.cmd_instruction_hint=`Δείτε τις οδηγίες γραμμής εντολών.` +pulls.cmd_instruction_checkout_title=Έλεγχος +pulls.cmd_instruction_checkout_desc=Από το αποθετήριο του έργου σας, ελέγξτε έναν νέο κλάδο και δοκιμάστε τις αλλαγές. +pulls.cmd_instruction_merge_title=Συγχώνευση +pulls.cmd_instruction_merge_desc=Συγχώνευση των αλλαγών και ενημέρωση στο Gitea. pulls.clear_merge_message=Εκκαθάριση μηνύματος συγχώνευσης pulls.clear_merge_message_hint=Η εκκαθάριση του μηνύματος συγχώνευσης θα αφαιρέσει μόνο το περιεχόμενο του μηνύματος υποβολής και θα διατηρήσει τα παραγόμενα git trailers όπως "Co-Authored-By …". @@ -1698,7 +1796,9 @@ pulls.auto_merge_canceled_schedule_comment=`ακύρωσε την αυτόματ pulls.delete.title=Διαγραφή αυτού του pull request; pulls.delete.text=Θέλετε πραγματικά να διαγράψετε αυτό το pull request; (Αυτό θα σβήσει οριστικά όλο το περιεχόμενο του. Εξετάστε αν θέλετε να το κλείσετε, αν σκοπεύεται να το αρχειοθετήσετε) +pulls.recently_pushed_new_branches=Ωθήσατε στο κλάδο %[1]s %[2]s +pull.deleted_branch=(διαγράφηκε):%s milestones.new=Νέο Ορόσημο milestones.closed=Έκλεισε %s @@ -1706,6 +1806,7 @@ milestones.update_ago=Ενημερώθηκε %s milestones.no_due_date=Δεν υπάρχει ημερομηνία παράδοσης milestones.open=Άνοιγμα milestones.close=Κλείσιμο +milestones.new_subheader=Τα ορόσημα μπορούν να σας βοηθήσουν να οργανώσετε τα ζητήματα και να παρακολουθείτε την πρόοδό τους. milestones.completeness=%d%% Ολοκληρώθηκε milestones.create=Δημιουργία Ορόσημου milestones.title=Τίτλος @@ -1722,11 +1823,21 @@ milestones.edit_success=Το ορόσημο "%s" ενημερώθηκε. milestones.deletion=Διαγραφή Ορόσημου milestones.deletion_desc=Η διαγραφή ενός ορόσημου το αφαιρεί από όλα τα συναφή ζητήματα. Συνέχεια; milestones.deletion_success=Το ορόσημο έχει διαγραφεί. +milestones.filter_sort.earliest_due_data=Πλησιέστερη παράδοση +milestones.filter_sort.latest_due_date=Απώτερη παράδοση milestones.filter_sort.least_complete=Λιγότερο πλήρη milestones.filter_sort.most_complete=Περισσότερο πλήρη milestones.filter_sort.most_issues=Περισσότερα ζητήματα milestones.filter_sort.least_issues=Λιγότερα ζητήματα +signing.will_sign=Αυτή η υποβολή θα υπογραφεί με το κλειδί "%s". +signing.wont_sign.error=Παρουσιάστηκε σφάλμα κατά τον έλεγχο για το αν η υποβολή μπορεί να υπογραφεί. +signing.wont_sign.never=Οι υποβολές δεν υπογράφονται ποτέ. +signing.wont_sign.always=Οι υποβολές υπογράφονται πάντα. +signing.wont_sign.parentsigned=Η υποβολή δε θα υπογραφεί καθώς η γονική υποβολή δεν έχει υπογραφεί. +signing.wont_sign.basesigned=Η συγχώνευση δε θα υπογραφεί καθώς η βασική υποβολή δεν έχει υπογραφή της βάσης. +signing.wont_sign.headsigned=Η συγχώνευση δε θα υπογραφεί καθώς δεν έχει υπογραφή η υποβολή της κεφαλής. +signing.wont_sign.not_signed_in=Δεν είστε συνδεδεμένοι. ext_wiki=Πρόσβαση στο Εξωτερικό Wiki ext_wiki.desc=Σύνδεση σε ένα εξωτερικό wiki. @@ -1866,6 +1977,7 @@ settings.mirror_settings.last_update=Τελευταία ενημέρωση settings.mirror_settings.push_mirror.none=Δεν έχουν ρυθμιστεί είδωλα ώθησης settings.mirror_settings.push_mirror.remote_url=URL Απομακρυσμένου Αποθετηρίου Git settings.mirror_settings.push_mirror.add=Προσθήκη Είδωλου Push +settings.mirror_settings.push_mirror.edit_sync_time=Επεξεργασία διαστήματος συγχρονισμού ειδώλου settings.sync_mirror=Συγχρονισμός Τώρα settings.site=Ιστοσελίδα @@ -2000,12 +2112,14 @@ settings.webhook_deletion_desc=Η αφαίρεση ενός webhook διαγρά settings.webhook_deletion_success=Το webhook έχει αφαιρεθεί. settings.webhook.test_delivery=Δοκιμή Παράδοσης settings.webhook.test_delivery_desc=Δοκιμάστε αυτό το webhook με ένα ψεύτικο συμβάν. +settings.webhook.test_delivery_desc_disabled=Για να δοκιμάσετε αυτό το webhook με μια ψεύτικη κλήση, ενεργοποιήστε το. settings.webhook.request=Αίτημα settings.webhook.response=Απάντηση settings.webhook.headers=Κεφαλίδες settings.webhook.payload=Περιεχόμενο settings.webhook.body=Σώμα settings.webhook.replay.description=Επανάληψη αυτού του webhook. +settings.webhook.replay.description_disabled=Για να επαναλάβετε αυτό το webhook, ενεργοποιήστε το. settings.webhook.delivery.success=Ένα γεγονός έχει προστεθεί στην ουρά παράδοσης. Μπορεί να χρειαστούν λίγα δευτερόλεπτα μέχρι να εμφανιστεί στο ιστορικό. settings.githooks_desc=Τα Άγκιστρα Git παρέχονται από το ίδιο το Git. Μπορείτε να επεξεργαστείτε τα αρχεία αγκίστρων παρακάτω για να ρυθμίσετε προσαρμοσμένες λειτουργίες. settings.githook_edit_desc=Αν το hook είναι ανενεργό, θα παρουσιαστεί ένα παράδειγμα. Αφήνοντας το περιεχόμενο του hook κενό θα το απενεργοποιήσετε. @@ -2165,6 +2279,7 @@ settings.dismiss_stale_approvals_desc=Όταν οι νέες υποβολές π settings.require_signed_commits=Απαιτούνται Υπογεγραμμένες Υποβολές settings.require_signed_commits_desc=Απόρριψη νέων υποβολών σε αυτόν τον κλάδο εάν είναι μη υπογεγραμμένες ή μη επαληθεύσιμες. settings.protect_branch_name_pattern=Μοτίβο Προστατευμένου Ονόματος Κλάδου +settings.protect_branch_name_pattern_desc=Μοτίβα ονόματος προστατευμένων κλάδων. Δείτε την τεκμηρίωση για σύνταξη μοτίβου. Παραδείγματα: main, release/** settings.protect_patterns=Μοτίβα settings.protect_protected_file_patterns=Μοτίβα προστατευμένων αρχείων (διαχωρισμένα με ερωτηματικό ';'): settings.protect_protected_file_patterns_desc=Τα προστατευόμενα αρχεία δεν επιτρέπεται να αλλάξουν άμεσα, ακόμη και αν ο χρήστης έχει δικαιώματα να προσθέσει, να επεξεργαστεί ή να διαγράψει αρχεία σε αυτόν τον κλάδο. Επιπλέων μοτίβα μπορούν να διαχωριστούν με ερωτηματικό (';'). Δείτε την τεκμηρίωση github.com/gobwas/glob για τη σύνταξη του μοτίβου. Πχ: .drone.yml, /docs/**/*.txt. @@ -2201,18 +2316,25 @@ settings.tags.protection.allowed.teams=Επιτρεπόμενες ομάδες settings.tags.protection.allowed.noone=Καμία settings.tags.protection.create=Προστασία Ετικέτας settings.tags.protection.none=Δεν υπάρχουν προστατευμένες ετικέτες. +settings.tags.protection.pattern.description=Μπορείτε να χρησιμοποιήσετε ένα μόνο όνομα ή ένα μοτίβο τύπου glob ή κανονική έκφραση για να ταιριάξετε πολλαπλές ετικέτες. Διαβάστε περισσότερα στον οδηγό προστατευμένων ετικετών. settings.bot_token=Διακριτικό Bot settings.chat_id=ID Συνομιλίας +settings.thread_id=ID Νήματος settings.matrix.homeserver_url=Homeserver URL settings.matrix.room_id=ID Δωματίου settings.matrix.message_type=Τύπος Μηνύματος settings.archive.button=Αρχειοθέτηση Αποθετηρίου settings.archive.header=Αρχειοθέτηση Αυτού του Αποθετηρίου +settings.archive.text=Η αρχειοθέτηση του αποθετηρίου θα το αλλάξει σε μόνο για ανάγνωση. Δε θα φαίνεται στον αρχικό πίνακα. Κανείς (ακόμα και εσείς!) δε θα μπορεί να κάνει νέες υποβολές, ή να ανοίξει ζητήματα ή pull request. settings.archive.success=Το αποθετήριο αρχειοθετήθηκε με επιτυχία. settings.archive.error=Παρουσιάστηκε σφάλμα κατά την προσπάθεια αρχειοθέτησης του αποθετηρίου. Δείτε το αρχείο καταγραφής για περισσότερες λεπτομέρειες. settings.archive.error_ismirror=Δε μπορείτε να αρχειοθετήσετε ένα είδωλο αποθετηρίου. settings.archive.branchsettings_unavailable=Οι ρυθμίσεις του κλάδου δεν είναι διαθέσιμες αν το αποθετήριο είναι αρχειοθετημένο. settings.archive.tagsettings_unavailable=Οι ρυθμίσεις της ετικέτας δεν είναι διαθέσιμες αν το αποθετήριο είναι αρχειοθετημένο. +settings.unarchive.button=Απο-Αρχειοθέτηση αποθετηρίου +settings.unarchive.header=Απο-Αρχειοθέτηση του αποθετηρίου +settings.unarchive.text=Η απο-αρχειοθέτηση του αποθετηρίου θα αποκαταστήσει την ικανότητά του να λαμβάνει υποβολές και ωθήσεις, καθώς και νέα ζητήματα και pull-requests. +settings.unarchive.success=Το αποθετήριο απο-αρχειοθετήθηκε με επιτυχία. settings.update_avatar_success=Η εικόνα του αποθετηρίου έχει ενημερωθεί. settings.lfs=LFS settings.lfs_filelist=Αρχεία LFS σε αυτό το αποθετήριο @@ -2279,6 +2401,7 @@ diff.show_more=Εμφάνιση Περισσότερων diff.load=Φόρτωση Διαφορών diff.generated=δημιουργημένο diff.vendored=εξωτερικό +diff.comment.add_line_comment=Προσθήκη σχολίου στη γραμμή diff.comment.placeholder=Αφήστε ένα σχόλιο diff.comment.markdown_info=Υποστηρίζεται στυλ με markdown. diff.comment.add_single_comment=Προσθέστε ένα σχόλιο @@ -2371,6 +2494,7 @@ branch.default_deletion_failed=Ο κλάδος "%s" είναι ο προεπιλ branch.restore=`Επαναφορά του Κλάδου "%s"` branch.download=`Λήψη του Κλάδου "%s"` branch.rename=`Μετονομασία Κλάδου "%s"` +branch.search=Αναζήτηση Κλάδου branch.included_desc=Αυτός ο κλάδος είναι μέρος του προεπιλεγμένου κλάδου branch.included=Περιλαμβάνεται branch.create_new_branch=Δημιουργία κλάδου από κλάδο: @@ -2393,6 +2517,7 @@ tag.create_success=Η ετικέτα "%s" δημιουργήθηκε. topic.manage_topics=Διαχείριση Θεμάτων topic.done=Ολοκληρώθηκε topic.count_prompt=Δεν μπορείτε να επιλέξετε περισσότερα από 25 θέματα +topic.format_prompt=Τα θέματα πρέπει να ξεκινούν με γράμμα ή αριθμό, μπορούν να περιλαμβάνουν παύλες ('-') και τελείες ('.'), μπορεί να είναι μέχρι 35 χαρακτήρες. Τα γράμματα πρέπει να είναι πεζά. find_file.go_to_file=Αναζήτηση αρχείου find_file.no_matching=Δεν ταιριάζει κανένα αρχείο @@ -2431,6 +2556,7 @@ form.create_org_not_allowed=Δεν επιτρέπεται να δημιουργ settings=Ρυθμίσεις settings.options=Οργανισμός settings.full_name=Πλήρες Όνομα +settings.email=Email Επικοινωνίας settings.website=Ιστοσελίδα settings.location=Τοποθεσία settings.permission=Δικαιώματα @@ -2444,6 +2570,7 @@ settings.visibility.private_shortname=Ιδιωτικός settings.update_settings=Ενημέρωση Ρυθμίσεων settings.update_setting_success=Οι ρυθμίσεις του οργανισμού έχουν ενημερωθεί. +settings.change_orgname_prompt=Σημείωση: Η αλλαγή του ονόματος του οργανισμού θα αλλάξει επίσης τη διεύθυνση URL του οργανισμού σας και θα απελευθερώσει το παλιό όνομα. settings.change_orgname_redirect_prompt=Το παλιό όνομα θα ανακατευθύνει μέχρι να διεκδικηθεί. settings.update_avatar_success=Η εικόνα του οργανισμού έχει ενημερωθεί. settings.delete=Διαγραφή Οργανισμού @@ -2519,15 +2646,19 @@ teams.all_repositories_helper=Η ομάδα έχει πρόσβαση σε όλ teams.all_repositories_read_permission_desc=Αυτή η ομάδα χορηγεί πρόσβαση Ανάγνωσης σε όλα τα αποθετήρια: τα μέλη μπορούν να δουν και να κλωνοποιήσουν αποθετήρια. teams.all_repositories_write_permission_desc=Αυτή η ομάδα χορηγεί πρόσβαση Εγγραφής σε όλα τα αποθετήρια: τα μέλη μπορούν να διαβάσουν και να κάνουν push σε αποθετήρια. teams.all_repositories_admin_permission_desc=Αυτή η ομάδα παρέχει πρόσβαση Διαχείρισης σε όλα τα αποθετήρια: τα μέλη μπορούν να διαβάσουν, να κάνουν push και να προσθέσουν συνεργάτες στα αποθετήρια. +teams.invite.title=Έχετε προσκληθεί να συμμετάσχετε στην ομάδα %s του οργανισμού %s. teams.invite.by=Προσκλήθηκε από %s teams.invite.description=Παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για συμμετοχή στην ομάδα. [admin] dashboard=Πίνακας Ελέγχου +identity_access=Ταυτότητα & Πρόσβαση users=Λογαριασμοί Χρήστη organizations=Οργανισμοί +assets=Στοιχεία Κώδικα repositories=Αποθετήρια hooks=Webhooks +integrations=Ενσωματώσεις authentication=Πηγές Ταυτοποίησης emails=Email Χρήστη config=Διαμόρφωση @@ -2536,6 +2667,7 @@ monitor=Παρακολούθηση first_page=Πρώτο last_page=Τελευταίο total=Σύνολο: %d +settings=Ρυθμίσεις Διαχειριστή dashboard.new_version_hint=Το Gitea %s είναι διαθέσιμο, τώρα εκτελείτε το %s. Ανατρέξτε στο blog για περισσότερες λεπτομέρειες. dashboard.statistic=Περίληψη @@ -2548,11 +2680,13 @@ dashboard.clean_unbind_oauth=Εκκαθάριση μη δεσμευμένων σ dashboard.clean_unbind_oauth_success=Όλες οι μη δεσμευμένες συνδέσεις OAuth διαγράφηκαν. dashboard.task.started=Εκκίνηση Εργασίας: %[1]s dashboard.task.process=Εργασία: %[1]s +dashboard.task.cancelled=Εργασία: %[1]ακυρώθηκε: %[3]s dashboard.task.error=Σφάλμα στην Εργασία: %[1]s: %[3]s dashboard.task.finished=Εργασία: %[1]s που εκκινήθηκε από %[2]s τελείωσε dashboard.task.unknown=Άγνωστη εργασία: %[1]s dashboard.cron.started=Εκκίνηση Προγραμματισμένης Εργασίας: %[1]s dashboard.cron.process=Προγραμματισμένη Εργασία: %[1]s +dashboard.cron.cancelled=Προγραμματισμένη εργασία: %[1]s ακυρώθηκε: %[3]s dashboard.cron.error=Σφάλμα στη Προγραμματισμένη Εργασία: %s: %[3]s dashboard.cron.finished=Προγραμματισμένη Εργασία: %[1]s τελείωσε dashboard.delete_inactive_accounts=Διαγραφή όλων των μη ενεργοποιημένων λογαριασμών @@ -2562,6 +2696,7 @@ dashboard.delete_repo_archives.started=Η διαγραφή όλων των αρ dashboard.delete_missing_repos=Διαγραφή όλων των αποθετηρίων που δεν έχουν τα αρχεία Git τους dashboard.delete_missing_repos.started=Η διαγραφή όλων των αποθετηρίων που δεν έχουν αρχεία Git τους, ξεκίνησε. dashboard.delete_generated_repository_avatars=Διαγραφή δημιουργημένων εικόνων αποθετηρίων +dashboard.sync_repo_branches=Συγχρονισμός κλάδων που λείπουν, από τα δεδομένα git στις βάσεις δεδομένων dashboard.update_mirrors=Ενημέρωση Ειδώλων dashboard.repo_health_check=Έλεγχος υγείας σε όλα τα αποθετήρια dashboard.check_repo_stats=Έλεγχος όλων των στατιστικών αποθετηρίων @@ -2613,6 +2748,9 @@ dashboard.gc_lfs=Συλλογή απορριμάτων στα μετα-αντι dashboard.stop_zombie_tasks=Διακοπή εργασιών ζόμπι dashboard.stop_endless_tasks=Διακοπή ατελείωτων εργασιών dashboard.cancel_abandoned_jobs=Ακύρωση εγκαταλελειμμένων εργασιών +dashboard.start_schedule_tasks=Έναρξη προγραμματισμένων εργασιών +dashboard.sync_branch.started=Ο Συγχρονισμός των Κλάδων ξεκίνησε +dashboard.rebuild_issue_indexer=Αναδόμηση ευρετηρίου ζητημάτων users.user_manage_panel=Διαχείριση Λογαριασμών Χρηστών users.new_account=Δημιουργία Λογαριασμού Χρήστη @@ -2621,6 +2759,9 @@ users.full_name=Πλήρες Όνομα users.activated=Ενεργοποιήθηκε users.admin=Διαχειριστής users.restricted=Περιορισμένος +users.reserved=Δεσμευμένο +users.bot=Bot +users.remote=Απομακρυσμένο users.2fa=2FA users.repos=Αποθετήρια users.created=Δημιουργήθηκε @@ -2667,6 +2808,7 @@ users.list_status_filter.is_prohibit_login=Απαγόρευση Σύνδεσης users.list_status_filter.not_prohibit_login=Επιτρέπεται η Σύνδεση users.list_status_filter.is_2fa_enabled=2FA Ενεργοποιημένο users.list_status_filter.not_2fa_enabled=2FA Απενεργοποιημένο +users.details=Λεπτομέρειες Χρήστη emails.email_manage_panel=Διαχείριση Email Χρήστη emails.primary=Κύριο @@ -2697,10 +2839,12 @@ repos.stars=Αστέρια repos.forks=Forks repos.issues=Ζητήματα repos.size=Μέγεθος +repos.lfs_size=Μέγεθος LFS packages.package_manage_panel=Διαχείριση Πακέτων packages.total_size=Συνολικό Μέγεθος: %s packages.unreferenced_size=Μέγεθος Χωρίς Αναφορά: %s +packages.cleanup=Εκκαθάριση ληγμένων δεδομένων packages.owner=Ιδιοκτήτης packages.creator=Δημιουργός packages.name=Όνομα @@ -2847,6 +2991,7 @@ config.disable_router_log=Απενεργοποίηση Καταγραφής Δρ config.run_user=Εκτέλεση Σαν Χρήστη config.run_mode=Λειτουργία Εκτέλεσης config.git_version=Έκδοση Git +config.app_data_path=Διαδρομή Δεδομένων Εφαρμογής config.repo_root_path=Ριζική Διαδρομή Αποθετηρίων config.lfs_root_path=Ριζική Διαδρομή LFS config.log_file_root_path=Διαδρομή Καταγραφών @@ -2996,8 +3141,10 @@ monitor.queue.name=Όνομα monitor.queue.type=Τύπος monitor.queue.exemplar=Τύπος Υποδείγματος monitor.queue.numberworkers=Αριθμός Εργατών +monitor.queue.activeworkers=Ενεργοί Εργάτες monitor.queue.maxnumberworkers=Μέγιστος Αριθμός Εργατών monitor.queue.numberinqueue=Πλήθος Ουράς +monitor.queue.review_add=Εξέταση / Προσθήκη Εργατών monitor.queue.settings.title=Ρυθμίσεις Δεξαμενής monitor.queue.settings.desc=Οι δεξαμενές αυξάνονται δυναμικά όταν υπάρχει φραγή της ουράς των εργατών τους. monitor.queue.settings.maxnumberworkers=Μέγιστος Αριθμός Εργατών @@ -3203,6 +3350,8 @@ pub.install=Για να εγκαταστήσετε το πακέτο μέσω τ pypi.requires=Απαιτεί Python pypi.install=Για να εγκαταστήσετε το πακέτο χρησιμοποιώντας το pip, εκτελέστε την ακόλουθη εντολή: rpm.registry=Ρυθμίστε αυτό το μητρώο από τη γραμμή εντολών: +rpm.distros.redhat=σε διανομές βασισμένες στο RedHat +rpm.distros.suse=σε διανομές με βάση το SUSE rpm.install=Για να εγκαταστήσετε το πακέτο, εκτελέστε την ακόλουθη εντολή: rubygems.install=Για να εγκαταστήσετε το πακέτο χρησιμοποιώντας το gem, εκτελέστε την ακόλουθη εντολή: rubygems.install2=ή προσθέστε το στο Gemfile: @@ -3235,6 +3384,7 @@ owner.settings.cargo.rebuild.success=Το ευρετήριο Cargo αναδομ owner.settings.cleanuprules.title=Διαχείριση Κανόνων Εκκαθάρισης owner.settings.cleanuprules.add=Προσθήκη Κανόνα Εκκαθάρισης owner.settings.cleanuprules.edit=Επεξεργασία Κανόνα Εκκαθάρισης +owner.settings.cleanuprules.none=Δεν υπάρχουν διαθέσιμοι κανόνες εκκαθάρισης. Παρακαλούμε συμβουλευτείτε την τεκμηρίωση. owner.settings.cleanuprules.preview=Προεπισκόπηση Κανόνα Εκκαθάρισης owner.settings.cleanuprules.preview.overview=%d πακέτα έχουν προγραμματιστεί να αφαιρεθούν. owner.settings.cleanuprules.preview.none=Ο κανόνας εκκαθάρισης δεν ταιριάζει με κανένα πακέτο. @@ -3279,6 +3429,7 @@ status.waiting=Αναμονή status.running=Εκτελείται status.success=Επιτυχές status.failure=Αποτυχία +status.cancelled=Ακυρώθηκε status.skipped=Παρακάμφθηκε status.blocked=Αποκλείστηκε @@ -3295,6 +3446,7 @@ runners.labels=Ετικέτες runners.last_online=Τελευταία Ώρα Σύνδεσης runners.runner_title=Εκτελεστής runners.task_list=Πρόσφατες εργασίες στον εκτελεστή +runners.task_list.no_tasks=Δεν υπάρχει καμία εργασία ακόμα. runners.task_list.run=Εκτέλεση runners.task_list.status=Κατάσταση runners.task_list.repository=Αποθετήριο @@ -3315,16 +3467,49 @@ runners.status.idle=Αδρανής runners.status.active=Ενεργό runners.status.offline=Χωρίς Σύνδεση runners.version=Έκδοση +runners.reset_registration_token=Επαναφορά διακριτικού εγγραφής runners.reset_registration_token_success=Επιτυχής επανέκδοση διακριτικού εγγραφής του εκτελεστή runs.all_workflows=Όλες Οι Ροές Εργασίας runs.commit=Υποβολή +runs.scheduled=Προγραμματισμένα +runs.pushed_by=ωθήθηκε από runs.invalid_workflow_helper=Το αρχείο ροής εργασίας δεν είναι έγκυρο. Ελέγξτε το αρχείο σας: %s +runs.no_matching_online_runner_helper=Κανένας δικτυακός δρομέας με ετικέτα: %s +runs.actor=Φορέας runs.status=Κατάσταση - +runs.actors_no_select=Όλοι οι φορείς +runs.status_no_select=Όλες οι καταστάσεις +runs.no_results=Δεν βρέθηκαν αποτελέσματα. +runs.no_workflows=Δεν υπάρχουν ροές εργασίας ακόμα. +runs.no_workflows.quick_start=Δεν ξέρετε πώς να ξεκινήσετε με τις Δράσεις Gitea; Συμβουλευτείτε τον οδηγό για γρήγορη αρχή. +runs.no_workflows.documentation=Για περισσότερες πληροφορίες σχετικά με τη Δράση Gitea, ανατρέξτε στην τεκμηρίωση. +runs.no_runs=Η ροή εργασίας δεν έχει τρέξει ακόμα. +runs.empty_commit_message=(κενό μήνυμα υποβολής) + +workflow.disable=Απενεργοποίηση Ροής Εργασιών +workflow.disable_success=Η ροή εργασίας '%s' απενεργοποιήθηκε επιτυχώς. +workflow.enable=Ενεργοποίηση Ροής Εργασίας +workflow.enable_success=Η ροή εργασίας '%s' ενεργοποιήθηκε επιτυχώς. +workflow.disabled=Η ροή εργασιών είναι απενεργοποιημένη. need_approval_desc=Πρέπει να εγκριθεί η εκτέλεση ροών εργασίας για pull request από fork. +variables=Μεταβλητές +variables.management=Διαχείριση Μεταβλητών +variables.creation=Προσθήκη Μεταβλητής +variables.none=Δεν υπάρχουν μεταβλητές ακόμα. +variables.deletion=Αφαίρεση μεταβλητής +variables.deletion.description=Η αφαίρεση μιας μεταβλητής είναι μόνιμη και δεν μπορεί να αναιρεθεί. Συνέχεια; +variables.description=Η μεταβλητές θα δίνονται σε ορισμένες δράσεις και δεν μπορούν να διαβαστούν αλλιώς. +variables.id_not_exist=Η μεταβλητή με id %d δεν υπάρχει. +variables.edit=Επεξεργασία Μεταβλητής +variables.deletion.failed=Αποτυχία αφαίρεσης της μεταβλητής. +variables.deletion.success=Η μεταβλητή έχει αφαιρεθεί. +variables.creation.failed=Αποτυχία προσθήκης μεταβλητής. +variables.creation.success=Η μεταβλητή "%s" έχει προστεθεί. +variables.update.failed=Αποτυχία επεξεργασίας μεταβλητής. +variables.update.success=Η μεταβλητή έχει τροποποιηθεί. [projects] type-1.display_name=Ατομικό Έργο @@ -3332,6 +3517,11 @@ type-2.display_name=Έργο Αποθετηρίου type-3.display_name=Έργο Οργανισμού [git.filemode] +changed_filemode=%[1]s → %[2]s ; Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", … +directory=Φάκελος +normal_file=Κανονικό αρχείο +executable_file=Εκτελέσιμο αρχείο symbolic_link=Symbolic link +submodule=Υπομονάδα diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 9af4d70171c50..96345f51f813d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1775,6 +1775,7 @@ pulls.merge_pull_request = Create merge commit pulls.rebase_merge_pull_request = Rebase then fast-forward pulls.rebase_merge_commit_pull_request = Rebase then create merge commit pulls.squash_merge_pull_request = Create squash commit +pulls.fast_forward_only_merge_pull_request = Fast-forward only pulls.merge_manually = Manually merged pulls.merge_commit_id = The merge commit ID pulls.require_signed_wont_sign = The branch requires signed commits but this merge will not be signed diff --git a/package-lock.json b/package-lock.json index 52f04c142033a..b1995a64574b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,23 +30,22 @@ "jquery": "3.7.1", "katex": "0.16.9", "license-checker-webpack-plugin": "0.2.1", - "lightningcss-loader": "2.1.0", - "mermaid": "10.7.0", + "mermaid": "10.8.0", "mini-css-extract-plugin": "2.8.0", "minimatch": "9.0.3", - "monaco-editor": "0.45.0", + "monaco-editor": "0.46.0", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.2.12", "pretty-ms": "9.0.0", "sortablejs": "1.15.2", - "swagger-ui-dist": "5.11.2", + "swagger-ui-dist": "5.11.3", "throttle-debounce": "5.0.0", "tinycolor2": "1.6.0", "tippy.js": "6.3.7", "toastify-js": "1.12.0", "tributejs": "5.1.3", "uint8-to-base64": "0.2.0", - "vue": "3.4.15", + "vue": "3.4.18", "vue-bar-graph": "2.0.0", "vue-loader": "17.4.2", "vue3-calendar-heatmap": "2.0.5", @@ -56,11 +55,11 @@ }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "4.1.0", - "@playwright/test": "1.41.1", + "@playwright/test": "1.41.2", "@stoplight/spectral-cli": "6.11.0", - "@stylistic/eslint-plugin-js": "1.5.4", + "@stylistic/eslint-plugin-js": "1.6.1", "@stylistic/stylelint-plugin": "2.0.0", - "@vitejs/plugin-vue": "5.0.3", + "@vitejs/plugin-vue": "5.0.4", "eslint": "8.56.0", "eslint-plugin-array-func": "4.0.0", "eslint-plugin-i": "2.29.1", @@ -69,8 +68,8 @@ "eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-regexp": "2.2.0", "eslint-plugin-sonarjs": "0.23.0", - "eslint-plugin-unicorn": "50.0.1", - "eslint-plugin-vitest": "0.3.21", + "eslint-plugin-unicorn": "51.0.1", + "eslint-plugin-vitest": "0.3.22", "eslint-plugin-vitest-globals": "1.4.0", "eslint-plugin-vue": "9.21.1", "eslint-plugin-vue-scoped-css": "2.7.2", @@ -82,8 +81,8 @@ "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.4", "svgo": "3.2.0", - "updates": "15.1.1", - "vite-string-plugin": "1.1.3", + "updates": "15.1.2", + "vite-string-plugin": "1.1.5", "vitest": "1.2.2" }, "engines": { @@ -1371,12 +1370,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.41.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.1.tgz", - "integrity": "sha512-9g8EWTjiQ9yFBXc6HjCWe41msLpxEX0KhmfmPl9RPLJdfzL4F0lg2BdJ91O9azFdl11y1pmpwdjBiSxvqc+btw==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz", + "integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==", "dev": true, "dependencies": { - "playwright": "1.41.1" + "playwright": "1.41.2" }, "bin": { "playwright": "cli.js" @@ -2073,9 +2072,9 @@ "dev": true }, "node_modules/@stylistic/eslint-plugin-js": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.5.4.tgz", - "integrity": "sha512-3ctWb3NvJNV1MsrZN91cYp2EGInLPSoZKphXIbIRx/zjZxKwLDr9z4LMOWtqjq14li/OgqUUcMq5pj8fgbLoTw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.6.1.tgz", + "integrity": "sha512-gHRxkbA5p8S1fnChE7Yf5NFltRZCzbCuQOcoTe93PSKBC4GqVjZmlWUSLz9pJKHvDAUTjWkfttWHIOaFYPEhRQ==", "dev": true, "dependencies": { "acorn": "^8.11.3", @@ -2261,13 +2260,13 @@ "dev": true }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz", - "integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2278,9 +2277,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz", - "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2291,13 +2290,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz", - "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/visitor-keys": "6.20.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2319,17 +2318,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz", - "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.20.0", - "@typescript-eslint/types": "6.20.0", - "@typescript-eslint/typescript-estree": "6.20.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -2344,12 +2343,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz", - "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.20.0", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -2367,9 +2366,9 @@ "dev": true }, "node_modules/@vitejs/plugin-vue": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz", - "integrity": "sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", + "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", "dev": true, "engines": { "node": "^18.0.0 || >=20.0.0" @@ -2449,9 +2448,9 @@ } }, "node_modules/@vitest/snapshot/node_modules/magic-string": { - "version": "0.30.6", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", - "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -2503,46 +2502,46 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz", - "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==", + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.18.tgz", + "integrity": "sha512-F7YK8lMK0iv6b9/Gdk15A67wM0KKZvxDxed0RR60C1z9tIJTKta+urs4j0RTN5XqHISzI3etN3mX0uHhjmoqjQ==", "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/shared": "3.4.15", + "@babel/parser": "^7.23.9", + "@vue/shared": "3.4.18", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", - "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==", + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.18.tgz", + "integrity": "sha512-24Eb8lcMfInefvQ6YlEVS18w5Q66f4+uXWVA+yb7praKbyjHRNuKVWGuinfSSjM0ZIiPi++QWukhkgznBaqpEA==", "dependencies": { - "@vue/compiler-core": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-core": "3.4.18", + "@vue/shared": "3.4.18" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz", - "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==", - "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/compiler-core": "3.4.15", - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15", + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.18.tgz", + "integrity": "sha512-rG5tqtnzwrVpMqAQ7FHtvHaV70G6LLfJIWLYZB/jZ9m/hrnZmIQh+H3ewnC5onwe/ibljm9+ZupxeElzqCkTAw==", + "dependencies": { + "@babel/parser": "^7.23.9", + "@vue/compiler-core": "3.4.18", + "@vue/compiler-dom": "3.4.18", + "@vue/compiler-ssr": "3.4.18", + "@vue/shared": "3.4.18", "estree-walker": "^2.0.2", - "magic-string": "^0.30.5", + "magic-string": "^0.30.6", "postcss": "^8.4.33", "source-map-js": "^1.0.2" } }, "node_modules/@vue/compiler-sfc/node_modules/magic-string": { - "version": "0.30.6", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", - "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -2551,57 +2550,57 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz", - "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==", + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.18.tgz", + "integrity": "sha512-hSlv20oUhPxo2UYUacHgGaxtqP0tvFo6ixxxD6JlXIkwzwoZ9eKK6PFQN4hNK/R13JlNyldwWt/fqGBKgWJ6nQ==", "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-dom": "3.4.18", + "@vue/shared": "3.4.18" } }, "node_modules/@vue/reactivity": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz", - "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==", + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.18.tgz", + "integrity": "sha512-7uda2/I0jpLiRygprDo5Jxs2HJkOVXcOMlyVlY54yRLxoycBpwGJRwJT9EdGB4adnoqJDXVT2BilUAYwI7qvmg==", "dependencies": { - "@vue/shared": "3.4.15" + "@vue/shared": "3.4.18" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz", - "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==", + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.18.tgz", + "integrity": "sha512-7mU9diCa+4e+8/wZ7Udw5pwTH10A11sZ1nldmHOUKJnzCwvZxfJqAtw31mIf4T5H2FsLCSBQT3xgioA9vIjyDQ==", "dependencies": { - "@vue/reactivity": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/reactivity": "3.4.18", + "@vue/shared": "3.4.18" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz", - "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==", + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.18.tgz", + "integrity": "sha512-2y1Mkzcw1niSfG7z3Qx+2ir9Gb4hdTkZe5p/I8x1aTIKQE0vY0tPAEUPhZm5tx6183gG3D/KwHG728UR0sIufA==", "dependencies": { - "@vue/runtime-core": "3.4.15", - "@vue/shared": "3.4.15", + "@vue/runtime-core": "3.4.18", + "@vue/shared": "3.4.18", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz", - "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==", + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.18.tgz", + "integrity": "sha512-YJd1wa7mzUN3NRqLEsrwEYWyO+PUBSROIGlCc3J/cvn7Zu6CxhNLgXa8Z4zZ5ja5/nviYO79J1InoPeXgwBTZA==", "dependencies": { - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-ssr": "3.4.18", + "@vue/shared": "3.4.18" }, "peerDependencies": { - "vue": "3.4.15" + "vue": "3.4.18" } }, "node_modules/@vue/shared": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz", - "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==" + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.18.tgz", + "integrity": "sha512-CxouGFxxaW5r1WbrSmWwck3No58rApXgRSBxrqgnY1K+jk20F6DrXJkHdH9n4HVT+/B6G2CAn213Uq3npWiy8Q==" }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", @@ -3767,30 +3766,6 @@ "cytoscape": "^3.2.0" } }, - "node_modules/cytoscape-fcose": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", - "dependencies": { - "cose-base": "^2.2.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/cose-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", - "dependencies": { - "layout-base": "^2.0.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/layout-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==" - }, "node_modules/d3": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", @@ -4372,17 +4347,6 @@ "node": ">=6" } }, - "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/diff": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", @@ -5024,9 +4988,9 @@ } }, "node_modules/eslint-plugin-unicorn": { - "version": "50.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-50.0.1.tgz", - "integrity": "sha512-KxenCZxqSYW0GWHH18okDlOQcpezcitm5aOSz6EnobyJ6BIByiPDviQRjJIUAjG/tMN11958MxaQ+qCoU6lfDA==", + "version": "51.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-51.0.1.tgz", + "integrity": "sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", @@ -5057,12 +5021,12 @@ } }, "node_modules/eslint-plugin-vitest": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.3.21.tgz", - "integrity": "sha512-oYwR1MrwaBw/OG6CKU+SJYleAc442w6CWL1RTQl5WLwy8X3sh0bgHIQk5iEtmTak3Q+XAvZglr0bIoDOjFdkcw==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.3.22.tgz", + "integrity": "sha512-atkFGQ7aVgcuSeSMDqnyevIyUpfBPMnosksgEPrKE7Y8xQlqG/5z2IQ6UDau05zXaaFv7Iz8uzqvIuKshjZ0Zw==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "^6.20.0" + "@typescript-eslint/utils": "^6.21.0" }, "engines": { "node": "^18.0.0 || >= 20.0.0" @@ -6993,224 +6957,6 @@ "node": ">=8" } }, - "node_modules/lightningcss": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.23.0.tgz", - "integrity": "sha512-SEArWKMHhqn/0QzOtclIwH5pXIYQOUEkF8DgICd/105O+GCgd7jxjNod/QPnBCSWvpRHQBGVz5fQ9uScby03zA==", - "dependencies": { - "detect-libc": "^1.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.23.0", - "lightningcss-darwin-x64": "1.23.0", - "lightningcss-freebsd-x64": "1.23.0", - "lightningcss-linux-arm-gnueabihf": "1.23.0", - "lightningcss-linux-arm64-gnu": "1.23.0", - "lightningcss-linux-arm64-musl": "1.23.0", - "lightningcss-linux-x64-gnu": "1.23.0", - "lightningcss-linux-x64-musl": "1.23.0", - "lightningcss-win32-x64-msvc": "1.23.0" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.23.0.tgz", - "integrity": "sha512-kl4Pk3Q2lnE6AJ7Qaij47KNEfY2/UXRZBT/zqGA24B8qwkgllr/j7rclKOf1axcslNXvvUdztjo4Xqh39Yq1aA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.23.0.tgz", - "integrity": "sha512-KeRFCNoYfDdcolcFXvokVw+PXCapd2yHS1Diko1z1BhRz/nQuD5XyZmxjWdhmhN/zj5sH8YvWsp0/lPLVzqKpg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.23.0.tgz", - "integrity": "sha512-xhnhf0bWPuZxcqknvMDRFFo2TInrmQRWZGB0f6YoAsZX8Y+epfjHeeOIGCfAmgF0DgZxHwYc8mIR5tQU9/+ROA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.23.0.tgz", - "integrity": "sha512-fBamf/bULvmWft9uuX+bZske236pUZEoUlaHNBjnueaCTJ/xd8eXgb0cEc7S5o0Nn6kxlauMBnqJpF70Bgq3zg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.23.0.tgz", - "integrity": "sha512-RS7sY77yVLOmZD6xW2uEHByYHhQi5JYWmgVumYY85BfNoVI3DupXSlzbw+b45A9NnVKq45+oXkiN6ouMMtTwfg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.23.0.tgz", - "integrity": "sha512-cU00LGb6GUXCwof6ACgSMKo3q7XYbsyTj0WsKHLi1nw7pV0NCq8nFTn6ZRBYLoKiV8t+jWl0Hv8KkgymmK5L5g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.23.0.tgz", - "integrity": "sha512-q4jdx5+5NfB0/qMbXbOmuC6oo7caPnFghJbIAV90cXZqgV8Am3miZhC4p+sQVdacqxfd+3nrle4C8icR3p1AYw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.23.0.tgz", - "integrity": "sha512-G9Ri3qpmF4qef2CV/80dADHKXRAQeQXpQTLx7AiQrBYQHqBjB75oxqj06FCIe5g4hNCqLPnM9fsO4CyiT1sFSQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-loader": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lightningcss-loader/-/lightningcss-loader-2.1.0.tgz", - "integrity": "sha512-mB+M/lvs/GdXT4yc8ZiNgLUAbYpPI9grDyC3ybz/Zo6s4GZv53iZnLTnkJT/Qm3Sh89dbFUm+omoHFXCfZtcXw==", - "dependencies": { - "browserslist": "^4.21.4", - "lightningcss": "^1.16.0", - "webpack-sources": "^3.2.3" - }, - "peerDependencies": { - "webpack": ">=5" - } - }, - "node_modules/lightningcss-loader/node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.23.0.tgz", - "integrity": "sha512-1rcBDJLU+obPPJM6qR5fgBUiCdZwZLafZM5f9kwjFLkb/UBNIzmae39uCSmh71nzPCTXZqHbvwu23OWnWEz+eg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -7571,16 +7317,15 @@ } }, "node_modules/mermaid": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.7.0.tgz", - "integrity": "sha512-PsvGupPCkN1vemAAjScyw4pw34p4/0dZkSrqvAB26hUvJulOWGIwt35FZWmT9wPIi4r0QLa5X0PB4YLIGn0/YQ==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.8.0.tgz", + "integrity": "sha512-9CzfSreRjdDJxX796+jW4zjEq0DVw5xVF0nWsqff8OTbrt+ml0TZ5PyYUjjUZJa2NYxYJZZXewEquxGiM8qZEA==", "dependencies": { "@braintree/sanitize-url": "^6.0.1", "@types/d3-scale": "^4.0.3", "@types/d3-scale-chromatic": "^3.0.0", - "cytoscape": "^3.23.0", + "cytoscape": "^3.28.1", "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.1.0", "d3": "^7.4.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.10", @@ -8134,9 +7879,9 @@ } }, "node_modules/monaco-editor": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.45.0.tgz", - "integrity": "sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA==" + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.46.0.tgz", + "integrity": "sha512-ADwtLIIww+9FKybWscd7OCfm9odsFYHImBRI1v9AviGce55QY8raT+9ihH8jX/E/e6QVSGM+pKj4jSUSRmALNQ==" }, "node_modules/monaco-editor-webpack-plugin": { "version": "7.1.0", @@ -8721,12 +8466,12 @@ "dev": true }, "node_modules/playwright": { - "version": "1.41.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.1.tgz", - "integrity": "sha512-gdZAWG97oUnbBdRL3GuBvX3nDDmUOuqzV/D24dytqlKt+eI5KbwusluZRGljx1YoJKZ2NRPaeWiFTeGZO7SosQ==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz", + "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==", "dev": true, "dependencies": { - "playwright-core": "1.41.1" + "playwright-core": "1.41.2" }, "bin": { "playwright": "cli.js" @@ -8739,9 +8484,9 @@ } }, "node_modules/playwright-core": { - "version": "1.41.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.1.tgz", - "integrity": "sha512-/KPO5DzXSMlxSX77wy+HihKGOunh3hqndhqeo/nMxfigiKzogn8kfL0ZBDu0L1RKgan5XHCPmn6zXd2NUJgjhg==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz", + "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -10365,9 +10110,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz", - "integrity": "sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A==" + "version": "5.11.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.3.tgz", + "integrity": "sha512-vQ+Pe73xt7vMVbX40L6nHu4sDmNCM6A+eMVJPGvKrifHQ4LO3smH0jCiiefKzsVl7OlOcVEnrZ9IFzYwElfMkA==" }, "node_modules/symbol-tree": { "version": "3.2.4", @@ -10620,12 +10365,12 @@ "integrity": "sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ==" }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -10769,9 +10514,9 @@ "dev": true }, "node_modules/ufo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", - "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz", + "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==", "dev": true }, "node_modules/uint8-to-base64": { @@ -10850,9 +10595,9 @@ } }, "node_modules/updates": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/updates/-/updates-15.1.1.tgz", - "integrity": "sha512-dMz/4251b0lV7yR58tuydCKaiWxOa18YM8fnRgtiDVzQ5ALopTZhMckv00w0nSMj6OFMFKLshTZGkX4dAebaaw==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/updates/-/updates-15.1.2.tgz", + "integrity": "sha512-+/JT4NChl82iexV9G80TY5HF3ubQ5O9UTOk3LlCo4Y4aRCYvo1h4bJE8YkP0PE7KiFRWIQq/rPmUYrY2QF8wVA==", "dev": true, "bin": { "updates": "bin/updates.js" @@ -11025,9 +10770,9 @@ } }, "node_modules/vite-string-plugin": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/vite-string-plugin/-/vite-string-plugin-1.1.3.tgz", - "integrity": "sha512-uHL8BV2tBf32T2slYpS0vRzGVrAS3iuivtGknjzyecvpSq2AiBSkyLAjEvvIZuZGDDGFHyGX+5+yc3OBPjWDlA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/vite-string-plugin/-/vite-string-plugin-1.1.5.tgz", + "integrity": "sha512-KRCIFX3PWVUuEjpi9O7EKLT9E27OqOA3RimIvVx6cziLAUxvnk2VvHQfMrP+mKkqyqqSmnnYyTig3OyDnK/zlA==", "dev": true }, "node_modules/vite/node_modules/@types/estree": { @@ -11149,9 +10894,9 @@ } }, "node_modules/vitest/node_modules/magic-string": { - "version": "0.30.6", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", - "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -11161,15 +10906,15 @@ } }, "node_modules/vue": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz", - "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==", - "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-sfc": "3.4.15", - "@vue/runtime-dom": "3.4.15", - "@vue/server-renderer": "3.4.15", - "@vue/shared": "3.4.15" + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.18.tgz", + "integrity": "sha512-0zLRYamFRe0wF4q2L3O24KQzLyLpL64ye1RUToOgOxuWZsb/FhaNRdGmeozdtVYLz6tl94OXLaK7/WQIrVCw1A==", + "dependencies": { + "@vue/compiler-dom": "3.4.18", + "@vue/compiler-sfc": "3.4.18", + "@vue/runtime-dom": "3.4.18", + "@vue/server-renderer": "3.4.18", + "@vue/shared": "3.4.18" }, "peerDependencies": { "typescript": "*" diff --git a/package.json b/package.json index 3defca1a3d905..46dfdd1055418 100644 --- a/package.json +++ b/package.json @@ -29,23 +29,22 @@ "jquery": "3.7.1", "katex": "0.16.9", "license-checker-webpack-plugin": "0.2.1", - "lightningcss-loader": "2.1.0", - "mermaid": "10.7.0", + "mermaid": "10.8.0", "mini-css-extract-plugin": "2.8.0", "minimatch": "9.0.3", - "monaco-editor": "0.45.0", + "monaco-editor": "0.46.0", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.2.12", "pretty-ms": "9.0.0", "sortablejs": "1.15.2", - "swagger-ui-dist": "5.11.2", + "swagger-ui-dist": "5.11.3", "throttle-debounce": "5.0.0", "tinycolor2": "1.6.0", "tippy.js": "6.3.7", "toastify-js": "1.12.0", "tributejs": "5.1.3", "uint8-to-base64": "0.2.0", - "vue": "3.4.15", + "vue": "3.4.18", "vue-bar-graph": "2.0.0", "vue-loader": "17.4.2", "vue3-calendar-heatmap": "2.0.5", @@ -55,11 +54,11 @@ }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "4.1.0", - "@playwright/test": "1.41.1", + "@playwright/test": "1.41.2", "@stoplight/spectral-cli": "6.11.0", - "@stylistic/eslint-plugin-js": "1.5.4", + "@stylistic/eslint-plugin-js": "1.6.1", "@stylistic/stylelint-plugin": "2.0.0", - "@vitejs/plugin-vue": "5.0.3", + "@vitejs/plugin-vue": "5.0.4", "eslint": "8.56.0", "eslint-plugin-array-func": "4.0.0", "eslint-plugin-i": "2.29.1", @@ -68,8 +67,8 @@ "eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-regexp": "2.2.0", "eslint-plugin-sonarjs": "0.23.0", - "eslint-plugin-unicorn": "50.0.1", - "eslint-plugin-vitest": "0.3.21", + "eslint-plugin-unicorn": "51.0.1", + "eslint-plugin-vitest": "0.3.22", "eslint-plugin-vitest-globals": "1.4.0", "eslint-plugin-vue": "9.21.1", "eslint-plugin-vue-scoped-css": "2.7.2", @@ -81,13 +80,11 @@ "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.4", "svgo": "3.2.0", - "updates": "15.1.1", - "vite-string-plugin": "1.1.3", + "updates": "15.1.2", + "vite-string-plugin": "1.1.5", "vitest": "1.2.2" }, "browserslist": [ - "defaults", - "not ie > 0", - "not ie_mob > 0" + "defaults" ] } diff --git a/poetry.lock b/poetry.lock index 74d202c919c4a..4897496a40bc2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -342,13 +342,13 @@ telegram = ["requests"] [[package]] name = "yamllint" -version = "1.33.0" +version = "1.34.0" description = "A linter for YAML files." optional = false python-versions = ">=3.8" files = [ - {file = "yamllint-1.33.0-py3-none-any.whl", hash = "sha256:28a19f5d68d28d8fec538a1db21bb2d84c7dc2e2ea36266da8d4d1c5a683814d"}, - {file = "yamllint-1.33.0.tar.gz", hash = "sha256:2dceab9ef2d99518a2fcf4ffc964d44250ac4459be1ba3ca315118e4a1a81f7d"}, + {file = "yamllint-1.34.0-py3-none-any.whl", hash = "sha256:33b813f6ff2ffad2e57a288281098392b85f7463ce1f3d5cd45aa848b916a806"}, + {file = "yamllint-1.34.0.tar.gz", hash = "sha256:7f0a6a41e8aab3904878da4ae34b6248b6bc74634e0d3a90f0fb2d7e723a3d4f"}, ] [package.dependencies] @@ -361,4 +361,4 @@ dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "175c87d138a47ba190a2c3f16b801f694915cc6f2367a358585df9cd1b17ff96" +content-hash = "e4ea4301a70487379fce7008493d15c005af3aada7d88fbf0bd3167147ec6502" diff --git a/pyproject.toml b/pyproject.toml index d999a1476c9d0..eb6d4b2311b5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ python = "^3.8" [tool.poetry.group.dev.dependencies] djlint = "1.34.1" -yamllint = "1.33.0" +yamllint = "1.34.0" [tool.djlint] profile="golang" diff --git a/routers/api/packages/swift/swift.go b/routers/api/packages/swift/swift.go index 427e262d0633c..6ad289e51e9ea 100644 --- a/routers/api/packages/swift/swift.go +++ b/routers/api/packages/swift/swift.go @@ -157,7 +157,7 @@ func EnumeratePackageVersions(ctx *context.Context) { } type Resource struct { - Name string `json:"id"` + Name string `json:"name"` Type string `json:"type"` Checksum string `json:"checksum"` } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index cb1803f7c652f..f3082e4fa0e89 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -811,7 +811,7 @@ func individualPermsChecker(ctx *context.APIContext) { // check for and warn against deprecated authentication options func checkDeprecatedAuthMethods(ctx *context.APIContext) { if ctx.FormString("token") != "" || ctx.FormString("access_token") != "" { - ctx.Resp.Header().Set("Warning", "token and access_token API authentication is deprecated and will be removed in gitea 1.23. Please use AuthorizationHeaderToken instead. Existing queries will continue to work but without authorization.") + ctx.Resp.Header().Set("X-Gitea-Warning", "token and access_token API authentication is deprecated and will be removed in gitea 1.23. Please use AuthorizationHeaderToken instead. Existing queries will continue to work but without authorization.") } } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 2efdccb569901..40de8853d8352 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "slices" + "strconv" "strings" "time" @@ -884,6 +885,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, + AllowFastForwardOnly: true, AllowManualMerge: true, AutodetectManualMerge: false, AllowRebaseUpdate: true, @@ -910,6 +912,9 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { if opts.AllowSquash != nil { config.AllowSquash = *opts.AllowSquash } + if opts.AllowFastForwardOnly != nil { + config.AllowFastForwardOnly = *opts.AllowFastForwardOnly + } if opts.AllowManualMerge != nil { config.AllowManualMerge = *opts.AllowManualMerge } @@ -1161,12 +1166,11 @@ func GetIssueTemplates(ctx *context.APIContext) { // "$ref": "#/responses/IssueTemplates" // "404": // "$ref": "#/responses/notFound" - ret, err := issue.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) - if err != nil { - ctx.Error(http.StatusInternalServerError, "GetTemplatesFromDefaultBranch", err) - return + ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) + if cnt := len(ret.TemplateErrors); cnt != 0 { + ctx.Resp.Header().Add("X-Gitea-Warning", "error occurs when parsing issue template: count="+strconv.Itoa(cnt)) } - ctx.JSON(http.StatusOK, ret) + ctx.JSON(http.StatusOK, ret.IssueTemplates) } // GetIssueConfig returns the issue config for a repo diff --git a/routers/api/v1/repo/repo_test.go b/routers/api/v1/repo/repo_test.go index 29e2d1f21d9ae..08ba7fabac4b3 100644 --- a/routers/api/v1/repo/repo_test.go +++ b/routers/api/v1/repo/repo_test.go @@ -35,6 +35,7 @@ func TestRepoEdit(t *testing.T) { allowRebase := false allowRebaseMerge := false allowSquashMerge := false + allowFastForwardOnlyMerge := false archived := true opts := api.EditRepoOption{ Name: &ctx.Repo.Repository.Name, @@ -50,6 +51,7 @@ func TestRepoEdit(t *testing.T) { AllowRebase: &allowRebase, AllowRebaseMerge: &allowRebaseMerge, AllowSquash: &allowSquashMerge, + AllowFastForwardOnly: &allowFastForwardOnlyMerge, Archived: &archived, } diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index fe528a483b4a2..5f6a1ec36a6a9 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -61,17 +61,17 @@ func List(ctx *context.Context) { var workflows []Workflow if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.ServerError("IsEmpty", err) return } else if !empty { commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.ServerError("GetBranchCommit", err) return } entries, err := actions.ListWorkflows(commit) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.ServerError("ListWorkflows", err) return } @@ -95,7 +95,7 @@ func List(ctx *context.Context) { workflow := Workflow{Entry: *entry} content, err := actions.GetContentFromEntry(entry) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.ServerError("GetContentFromEntry", err) return } wf, err := model.ReadWorkflow(bytes.NewReader(content)) @@ -172,7 +172,7 @@ func List(ctx *context.Context) { runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.ServerError("FindAndCount", err) return } @@ -181,7 +181,7 @@ func List(ctx *context.Context) { } if err := actions_model.RunList(runs).LoadTriggerUser(ctx); err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.ServerError("LoadTriggerUser", err) return } @@ -189,7 +189,7 @@ func List(ctx *context.Context) { actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID) if err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) + ctx.ServerError("GetActors", err) return } ctx.Data["Actors"] = repo.MakeSelfOnTop(ctx.Doer, actors) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index c8c9924a9eb77..a85f6e766652c 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -993,17 +993,17 @@ func NewIssue(ctx *context.Context) { } ctx.Data["Tags"] = tags - _, templateErrs := issue_service.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) + ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates) for k, v := range errs { - templateErrs[k] = v + ret.TemplateErrors[k] = v } if ctx.Written() { return } - if len(templateErrs) > 0 { - ctx.Flash.Warning(renderErrorOfTemplates(ctx, templateErrs), true) + if len(ret.TemplateErrors) > 0 { + ctx.Flash.Warning(renderErrorOfTemplates(ctx, ret.TemplateErrors), true) } ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(unit.TypeIssues) @@ -1046,11 +1046,11 @@ func NewIssueChooseTemplate(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.issues.new") ctx.Data["PageIsIssueList"] = true - issueTemplates, errs := issue_service.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) - ctx.Data["IssueTemplates"] = issueTemplates + ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) + ctx.Data["IssueTemplates"] = ret.IssueTemplates - if len(errs) > 0 { - ctx.Flash.Warning(renderErrorOfTemplates(ctx, errs), true) + if len(ret.TemplateErrors) > 0 { + ctx.Flash.Warning(renderErrorOfTemplates(ctx, ret.TemplateErrors), true) } if !issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo) { @@ -1862,6 +1862,8 @@ func ViewIssue(ctx *context.Context) { mergeStyle = repo_model.MergeStyleRebaseMerge } else if prConfig.AllowSquash { mergeStyle = repo_model.MergeStyleSquash + } else if prConfig.AllowFastForwardOnly { + mergeStyle = repo_model.MergeStyleFastForwardOnly } else if prConfig.AllowManualMerge { mergeStyle = repo_model.MergeStyleManuallyMerged } diff --git a/routers/web/repo/middlewares.go b/routers/web/repo/middlewares.go index ee49649654ca3..d70a53030e659 100644 --- a/routers/web/repo/middlewares.go +++ b/routers/web/repo/middlewares.go @@ -98,23 +98,15 @@ func SetWhitespaceBehavior(ctx *context.Context) { // SetShowOutdatedComments set the show outdated comments option as context variable func SetShowOutdatedComments(ctx *context.Context) { showOutdatedCommentsValue := ctx.FormString("show-outdated") - // var showOutdatedCommentsValue string - if showOutdatedCommentsValue != "true" && showOutdatedCommentsValue != "false" { // invalid or no value for this form string -> use default or stored user setting + showOutdatedCommentsValue = "true" if ctx.IsSigned { - showOutdatedCommentsValue, _ = user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowOutdatedComments, "false") - } else { - // not logged in user -> use the default value - showOutdatedCommentsValue = "false" + showOutdatedCommentsValue, _ = user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowOutdatedComments, showOutdatedCommentsValue) } - } else { + } else if ctx.IsSigned { // valid value -> update user setting if user is logged in - if ctx.IsSigned { - _ = user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowOutdatedComments, showOutdatedCommentsValue) - } + _ = user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyShowOutdatedComments, showOutdatedCommentsValue) } - - showOutdatedComments, _ := strconv.ParseBool(showOutdatedCommentsValue) - ctx.Data["ShowOutdatedComments"] = showOutdatedComments + ctx.Data["ShowOutdatedComments"], _ = strconv.ParseBool(showOutdatedCommentsValue) } diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index 19db2abd68d1c..400748b963d5d 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -294,8 +294,8 @@ func MilestoneIssuesAndPulls(ctx *context.Context) { issues(ctx, milestoneID, projectID, util.OptionalBoolNone) - ret, _ := issue.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) - ctx.Data["NewIssueChooseTemplate"] = len(ret) > 0 + ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) + ctx.Data["NewIssueChooseTemplate"] = len(ret.IssueTemplates) > 0 ctx.Data["CanWriteIssues"] = ctx.Repo.CanWriteIssuesOrPulls(false) ctx.Data["CanWritePulls"] = ctx.Repo.CanWriteIssuesOrPulls(true) diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go index a6b3bd1c8d77f..b93460d1696ae 100644 --- a/routers/web/repo/pull_review.go +++ b/routers/web/repo/pull_review.go @@ -22,6 +22,7 @@ import ( const ( tplDiffConversation base.TplName = "repo/diff/conversation" + tplConversationOutdated base.TplName = "repo/diff/conversation_outdated" tplTimelineConversation base.TplName = "repo/issue/view_content/conversation" tplNewComment base.TplName = "repo/diff/new_comment" ) @@ -161,8 +162,8 @@ func renderConversation(ctx *context.Context, comment *issues_model.Comment, ori return } if len(comments) == 0 { - // if the comments are empty (deleted, outdated, etc), it doesn't need to render anything, just return an empty body to replace "conversation-holder" on the page - ctx.Resp.WriteHeader(http.StatusOK) + // if the comments are empty (deleted, outdated, etc), it's better to tell the users that it is outdated + ctx.HTML(http.StatusOK, tplConversationOutdated) return } diff --git a/routers/web/repo/pull_review_test.go b/routers/web/repo/pull_review_test.go new file mode 100644 index 0000000000000..65019af40b168 --- /dev/null +++ b/routers/web/repo/pull_review_test.go @@ -0,0 +1,76 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "net/http/httptest" + "testing" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/contexttest" + "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/services/pull" + + "github.com/stretchr/testify/assert" +) + +func TestRenderConversation(t *testing.T) { + unittest.PrepareTestEnv(t) + + pr, _ := issues_model.GetPullRequestByID(db.DefaultContext, 2) + _ = pr.LoadIssue(db.DefaultContext) + _ = pr.Issue.LoadPoster(db.DefaultContext) + _ = pr.Issue.LoadRepo(db.DefaultContext) + + run := func(name string, cb func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder)) { + t.Run(name, func(t *testing.T) { + ctx, resp := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()}) + contexttest.LoadUser(t, ctx, pr.Issue.PosterID) + contexttest.LoadRepo(t, ctx, pr.BaseRepoID) + contexttest.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + cb(t, ctx, resp) + }) + } + + var preparedComment *issues_model.Comment + run("prepare", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) { + comment, err := pull.CreateCodeComment(ctx, pr.Issue.Poster, ctx.Repo.GitRepo, pr.Issue, 1, "content", "", false, 0, pr.HeadCommitID) + if !assert.NoError(t, err) { + return + } + comment.Invalidated = true + err = issues_model.UpdateCommentInvalidate(ctx, comment) + if !assert.NoError(t, err) { + return + } + preparedComment = comment + }) + if !assert.NotNil(t, preparedComment) { + return + } + run("diff with outdated", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) { + ctx.Data["ShowOutdatedComments"] = true + renderConversation(ctx, preparedComment, "diff") + assert.Contains(t, resp.Body.String(), `
it.Ref = git.BranchPrefix + it.Ref } - issueTemplates = append(issueTemplates, it) + ret.IssueTemplates = append(ret.IssueTemplates, it) } } } - return issueTemplates, invalidFiles + return ret } // GetTemplateConfigFromDefaultBranch returns the issue config for this repo. @@ -179,8 +181,8 @@ func GetTemplateConfigFromDefaultBranch(repo *repo.Repository, gitRepo *git.Repo } func HasTemplatesOrContactLinks(repo *repo.Repository, gitRepo *git.Repository) bool { - ret, _ := GetTemplatesFromDefaultBranch(repo, gitRepo) - if len(ret) > 0 { + ret := ParseTemplatesFromDefaultBranch(repo, gitRepo) + if len(ret.IssueTemplates) > 0 { return true } diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index 801c2476c2752..5e8e5b6af38e5 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -74,6 +74,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_mo "Release": rel, "Subject": subject, "Language": locale.Language(), + "Link": rel.HTMLURL(), } var mailBody bytes.Buffer diff --git a/services/pull/merge.go b/services/pull/merge.go index 63f0268bebece..d4c0c821d6dd6 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -267,6 +267,10 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use if err := doMergeStyleSquash(mergeCtx, message); err != nil { return "", err } + case repo_model.MergeStyleFastForwardOnly: + if err := doMergeStyleFastForwardOnly(mergeCtx); err != nil { + return "", err + } default: return "", models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle} } @@ -377,6 +381,13 @@ func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *g StdErr: ctx.errbuf.String(), Err: err, } + } else if mergeStyle == repo_model.MergeStyleFastForwardOnly && strings.Contains(ctx.errbuf.String(), "Not possible to fast-forward, aborting") { + log.Debug("MergeDivergingFastForwardOnly %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) + return models.ErrMergeDivergingFastForwardOnly{ + StdOut: ctx.outbuf.String(), + StdErr: ctx.errbuf.String(), + Err: err, + } } log.Error("git merge %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) return fmt.Errorf("git merge %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) diff --git a/services/pull/merge_ff_only.go b/services/pull/merge_ff_only.go new file mode 100644 index 0000000000000..f57c732104cc2 --- /dev/null +++ b/services/pull/merge_ff_only.go @@ -0,0 +1,21 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package pull + +import ( + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" +) + +// doMergeStyleFastForwardOnly merges the tracking into the current HEAD - which is assumed to be staging branch (equal to the pr.BaseBranch) +func doMergeStyleFastForwardOnly(ctx *mergeContext) error { + cmd := git.NewCommand(ctx, "merge", "--ff-only").AddDynamicArguments(trackingBranch) + if err := runMergeCommand(ctx, repo_model.MergeStyleFastForwardOnly, cmd); err != nil { + log.Error("%-v Unable to merge tracking into base: %v", ctx.pr, err) + return err + } + + return nil +} diff --git a/services/pull/merge_merge.go b/services/pull/merge_merge.go index 0f7664297aa81..bf56c071db07b 100644 --- a/services/pull/merge_merge.go +++ b/services/pull/merge_merge.go @@ -9,7 +9,7 @@ import ( "code.gitea.io/gitea/modules/log" ) -// doMergeStyleMerge merges the tracking into the current HEAD - which is assumed to tbe staging branch (equal to the pr.BaseBranch) +// doMergeStyleMerge merges the tracking branch into the current HEAD - which is assumed to be the staging branch (equal to the pr.BaseBranch) func doMergeStyleMerge(ctx *mergeContext, message string) error { cmd := git.NewCommand(ctx, "merge", "--no-ff", "--no-commit").AddDynamicArguments(trackingBranch) if err := runMergeCommand(ctx, repo_model.MergeStyleMerge, cmd); err != nil { diff --git a/templates/repo/branch_dropdown.tmpl b/templates/repo/branch_dropdown.tmpl index bee5363296c7a..8a5cdc7cc76f9 100644 --- a/templates/repo/branch_dropdown.tmpl +++ b/templates/repo/branch_dropdown.tmpl @@ -1,7 +1,7 @@ {{/* Attributes: * root * ContainerClasses -* (TODO: search "branch_dropdown" in the template direcotry) +* (TODO: search "branch_dropdown" in the template directory) */}} {{$defaultSelectedRefName := $.root.BranchName}} {{if and .root.IsViewTag (not .noTag)}} diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl index 7702770c406f0..4eb31e0e8e697 100644 --- a/templates/repo/commits_list.tmpl +++ b/templates/repo/commits_list.tmpl @@ -78,9 +78,12 @@ {{end}} - {{if $.FileName}} - {{svg "octicon-file-code"}} - {{end}} + + {{svg "octicon-file-code"}} + {{end}} diff --git a/templates/repo/diff/conversation_outdated.tmpl b/templates/repo/diff/conversation_outdated.tmpl new file mode 100644 index 0000000000000..c6a4bf19794a6 --- /dev/null +++ b/templates/repo/diff/conversation_outdated.tmpl @@ -0,0 +1,3 @@ +
+ {{ctx.Locale.Tr "repo.issues.review.outdated_description"}} +
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index ade0ea34cfa46..3cb7f7d0cfc23 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -367,7 +367,7 @@
{{if .OriginalAuthor}} {{else}} - {{/* Some timeline avatars need a offset to correctly allign with their speech + {{/* Some timeline avatars need a offset to correctly align with their speech bubble. The condition depends on review type and for positive reviews whether there is a comment element or not */}} diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl index 2b5776ea03a52..f1ab53eb677c4 100644 --- a/templates/repo/issue/view_content/pull.tmpl +++ b/templates/repo/issue/view_content/pull.tmpl @@ -197,7 +197,7 @@ {{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}} + {{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash $prUnit.PullRequestsConfig.AllowFastForwardOnly}} {{$hasPendingPullRequestMergeTip := ""}} {{if .HasPendingPullRequestMerge}} {{$createdPRMergeStr := TimeSinceUnix .PendingPullRequestMerge.CreatedUnix ctx.Locale}} @@ -268,6 +268,13 @@ 'mergeMessageFieldText': {{.GetCommitMessages}} + defaultSquashMergeMessage, 'hideAutoMerge': generalHideAutoMerge, }, + { + 'name': 'fast-forward-only', + 'allowed': {{and $prUnit.PullRequestsConfig.AllowFastForwardOnly (eq .Issue.PullRequest.CommitsBehind 0)}}, + 'textDoMerge': {{ctx.Locale.Tr "repo.pulls.fast_forward_only_merge_pull_request"}}, + 'hideMergeMessageTexts': true, + 'hideAutoMerge': generalHideAutoMerge, + }, { 'name': 'manually-merged', 'allowed': {{$prUnit.PullRequestsConfig.AllowManualMerge}}, diff --git a/templates/repo/issue/view_content/pull_merge_instruction.tmpl b/templates/repo/issue/view_content/pull_merge_instruction.tmpl index 3dab44710eaf0..a214f29786ba1 100644 --- a/templates/repo/issue/view_content/pull_merge_instruction.tmpl +++ b/templates/repo/issue/view_content/pull_merge_instruction.tmpl @@ -35,6 +35,10 @@
git checkout {{.PullRequest.BaseBranch}}
git merge --squash {{$localBranch}}
+
+
git checkout {{.PullRequest.BaseBranch}}
+
git merge --ff-only {{$localBranch}}
+
git checkout {{.PullRequest.BaseBranch}}
git merge {{$localBranch}}
diff --git a/templates/repo/settings/navbar.tmpl b/templates/repo/settings/navbar.tmpl index 3bef0fa4c143d..0b0ef0b6e8542 100644 --- a/templates/repo/settings/navbar.tmpl +++ b/templates/repo/settings/navbar.tmpl @@ -12,10 +12,12 @@ {{ctx.Locale.Tr "repo.settings.hooks"}}
{{end}} - {{if and (.Repository.UnitEnabled $.Context $.UnitTypeCode) (not .Repository.IsEmpty)}} - - {{ctx.Locale.Tr "repo.settings.branches"}} - + {{if .Repository.UnitEnabled $.Context $.UnitTypeCode}} + {{if not .Repository.IsEmpty}} + + {{ctx.Locale.Tr "repo.settings.branches"}} + + {{end}} {{ctx.Locale.Tr "repo.settings.tags"}} diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index dfb909e743318..f7f448fdf2fff 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -528,6 +528,12 @@
+
+
+ + +
+
@@ -545,6 +551,7 @@ + {{svg "octicon-triangle-down" 14 "dropdown icon"}}
{{if (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "merge")}} @@ -559,12 +566,16 @@ {{if (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "squash")}} {{ctx.Locale.Tr "repo.pulls.squash_merge_pull_request"}} {{end}} + {{if (eq $prUnit.PullRequestsConfig.DefaultMergeStyle "fast-forward-only")}} + {{ctx.Locale.Tr "repo.pulls.fast_forward_only_merge_pull_request"}} + {{end}}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 403f241d72787..a881afaf0ec63 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -19195,6 +19195,11 @@ "description": "EditRepoOption options when editing a repository's properties", "type": "object", "properties": { + "allow_fast_forward_only_merge": { + "description": "either `true` to allow fast-forward-only merging pull requests, or `false` to prevent fast-forward-only merging.", + "type": "boolean", + "x-go-name": "AllowFastForwardOnly" + }, "allow_manual_merge": { "description": "either `true` to allow mark pr as merged manually, or `false` to prevent it.", "type": "boolean", @@ -19251,7 +19256,7 @@ "x-go-name": "DefaultDeleteBranchAfterMerge" }, "default_merge_style": { - "description": "set to a merge style to be used by this repository: \"merge\", \"rebase\", \"rebase-merge\", or \"squash\".", + "description": "set to a merge style to be used by this repository: \"merge\", \"rebase\", \"rebase-merge\", \"squash\", or \"fast-forward-only\".", "type": "string", "x-go-name": "DefaultMergeStyle" }, @@ -20650,6 +20655,7 @@ "rebase", "rebase-merge", "squash", + "fast-forward-only", "manually-merged" ] }, @@ -22036,6 +22042,10 @@ "description": "Repository represents a repository", "type": "object", "properties": { + "allow_fast_forward_only_merge": { + "type": "boolean", + "x-go-name": "AllowFastForwardOnly" + }, "allow_merge_commits": { "type": "boolean", "x-go-name": "AllowMerge" diff --git a/tests/integration/api_issue_templates_test.go b/tests/integration/api_issue_templates_test.go new file mode 100644 index 0000000000000..6b65e6e08673b --- /dev/null +++ b/tests/integration/api_issue_templates_test.go @@ -0,0 +1,55 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "net/http" + "net/url" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + + "github.com/stretchr/testify/assert" +) + +func TestAPIIssueTemplateList(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + var issueTemplates []*api.IssueTemplate + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + + // no issue template + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates") + resp := MakeRequest(t, req, http.StatusOK) + issueTemplates = nil + DecodeJSON(t, resp, &issueTemplates) + assert.Empty(t, issueTemplates) + + // one correct issue template and some incorrect issue templates + err := createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-ok.md", repo.DefaultBranch, `---- +name: foo +about: bar +---- +`) + assert.NoError(t, err) + + err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err1.yml", repo.DefaultBranch, `name: '`) + assert.NoError(t, err) + + err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err2.yml", repo.DefaultBranch, `other: `) + assert.NoError(t, err) + + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates") + resp = MakeRequest(t, req, http.StatusOK) + issueTemplates = nil + DecodeJSON(t, resp, &issueTemplates) + assert.Len(t, issueTemplates, 1) + assert.Equal(t, "foo", issueTemplates[0].Name) + assert.Equal(t, "error occurs when parsing issue template: count=2", resp.Header().Get("X-Gitea-Warning")) + }) +} diff --git a/tests/integration/api_repo_edit_test.go b/tests/integration/api_repo_edit_test.go index c4fc2177b4ce1..7de8910ee0651 100644 --- a/tests/integration/api_repo_edit_test.go +++ b/tests/integration/api_repo_edit_test.go @@ -65,6 +65,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption allowRebase := false allowRebaseMerge := false allowSquash := false + allowFastForwardOnly := false if unit, err := repo.GetUnit(db.DefaultContext, unit_model.TypePullRequests); err == nil { config := unit.PullRequestsConfig() hasPullRequests = true @@ -73,6 +74,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption allowRebase = config.AllowRebase allowRebaseMerge = config.AllowRebaseMerge allowSquash = config.AllowSquash + allowFastForwardOnly = config.AllowFastForwardOnly } archived := repo.IsArchived return &api.EditRepoOption{ @@ -92,6 +94,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption AllowRebase: &allowRebase, AllowRebaseMerge: &allowRebaseMerge, AllowSquash: &allowSquash, + AllowFastForwardOnly: &allowFastForwardOnly, Archived: &archived, } } diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index fcd7fecd52dd5..5205df2f8e74a 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -365,6 +365,90 @@ func TestCantMergeUnrelated(t *testing.T) { }) } +func TestFastForwardOnlyMerge(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "update", "README.md", "Hello, World 2\n") + + // Use API to create a pr from update to master + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", "user1", "repo1"), &api.CreatePullRequestOption{ + Head: "update", + Base: "master", + Title: "create a pr that can be fast-forward-only merged", + }).AddTokenAuth(token) + session.MakeRequest(t, req, http.StatusCreated) + + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: "user1", + }) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ + OwnerID: user1.ID, + Name: "repo1", + }) + + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ + HeadRepoID: repo1.ID, + BaseRepoID: repo1.ID, + HeadBranch: "update", + BaseBranch: "master", + }) + + gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name)) + assert.NoError(t, err) + + err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleFastForwardOnly, "", "FAST-FORWARD-ONLY", false) + + assert.NoError(t, err) + + gitRepo.Close() + }) +} + +func TestCantFastForwardOnlyMergeDiverging(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + session := loginUser(t, "user1") + testRepoFork(t, session, "user2", "repo1", "user1", "repo1") + testEditFileToNewBranch(t, session, "user1", "repo1", "master", "diverging", "README.md", "Hello, World diverged\n") + testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World 2\n") + + // Use API to create a pr from diverging to update + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", "user1", "repo1"), &api.CreatePullRequestOption{ + Head: "diverging", + Base: "master", + Title: "create a pr from a diverging branch", + }).AddTokenAuth(token) + session.MakeRequest(t, req, http.StatusCreated) + + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: "user1", + }) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ + OwnerID: user1.ID, + Name: "repo1", + }) + + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ + HeadRepoID: repo1.ID, + BaseRepoID: repo1.ID, + HeadBranch: "diverging", + BaseBranch: "master", + }) + + gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name)) + assert.NoError(t, err) + + err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleFastForwardOnly, "", "DIVERGING", false) + + assert.Error(t, err, "Merge should return an error due to being for a diverging branch") + assert.True(t, models.IsErrMergeDivergingFastForwardOnly(err), "Merge error is not a diverging fast-forward-only error") + + gitRepo.Close() + }) +} + func TestConflictChecking(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/vitest.config.js b/vitest.config.js index 9a6cb4e5606fc..be6c0eadfa759 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -5,7 +5,7 @@ import {stringPlugin} from 'vite-string-plugin'; export default defineConfig({ test: { include: ['web_src/**/*.test.js'], - setupFiles: ['./web_src/js/test/setup.js'], + setupFiles: ['web_src/js/vitest.setup.js'], environment: 'jsdom', testTimeout: 20000, open: false, diff --git a/web_src/css/base.css b/web_src/css/base.css index 198e87c0e202f..ea32aac6f7770 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -1268,20 +1268,45 @@ img.ui.avatar, border-radius: var(--border-radius); } -.attention-icon { - vertical-align: text-top; +.attention { + color: var(--color-text) !important; } -.attention-note { - font-weight: unset; - color: var(--color-info-text); +blockquote.attention-note { + border-left-color: var(--color-blue-dark-1); +} +strong.attention-note, span.attention-note { + color: var(--color-blue-dark-1); +} + +blockquote.attention-tip { + border-left-color: var(--color-success-text); +} +strong.attention-tip, span.attention-tip { + color: var(--color-success-text); } -.attention-warning { - font-weight: unset; +blockquote.attention-important { + border-left-color: var(--color-violet-dark-1); +} +strong.attention-important, span.attention-important { + color: var(--color-violet-dark-1); +} + +blockquote.attention-warning { + border-left-color: var(--color-warning-text); +} +strong.attention-warning, span.attention-warning { color: var(--color-warning-text); } +blockquote.attention-caution { + border-left-color: var(--color-red-dark-1); +} +strong.attention-caution, span.attention-caution { + color: var(--color-red-dark-1); +} + .center:not(.popup) { text-align: center; } diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index caefa1605ccac..5eeef078a505b 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -270,7 +270,7 @@ margin-left: 0; padding: 0 15px; color: var(--color-text-light-2); - border-left: 4px solid var(--color-secondary); + border-left: 0.25em solid var(--color-secondary); } .markup blockquote > :first-child { diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 55c6ec4817187..610c3fcb5596c 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -2289,10 +2289,6 @@ padding: 1em; } -.comment-body .markup { - border-radius: 0 0 var(--border-radius) var(--border-radius); /* don't render outside box */ -} - .edit-label.modal .form .column, .new-label.modal .form .column { padding-right: 0; diff --git a/web_src/js/features/admin/users.js b/web_src/js/features/admin/users.js index c8edaab549322..7cac603b5c3b6 100644 --- a/web_src/js/features/admin/users.js +++ b/web_src/js/features/admin/users.js @@ -1,34 +1,39 @@ -import $ from 'jquery'; - export function initAdminUserListSearchForm() { const searchForm = window.config.pageData.adminUserListSearchForm; if (!searchForm) return; - const $form = $('#user-list-search-form'); - if (!$form.length) return; + const form = document.querySelector('#user-list-search-form'); + if (!form) return; - $form.find(`button[name=sort][value=${searchForm.SortType}]`).addClass('active'); + for (const button of form.querySelectorAll(`button[name=sort][value="${searchForm.SortType}"]`)) { + button.classList.add('active'); + } if (searchForm.StatusFilterMap) { for (const [k, v] of Object.entries(searchForm.StatusFilterMap)) { if (!v) continue; - $form.find(`input[name="status_filter[${k}]"][value=${v}]`).prop('checked', true); + for (const input of form.querySelectorAll(`input[name="status_filter[${k}]"][value="${v}"]`)) { + input.checked = true; + } } } - $form.find(`input[type=radio]`).on('click', () => { - $form.trigger('submit'); - return false; - }); + for (const radio of form.querySelectorAll('input[type=radio]')) { + radio.addEventListener('click', () => { + form.submit(); + }); + } - $form.find('.j-reset-status-filter').on('click', () => { - $form.find(`input[type=radio]`).each((_, e) => { - const $e = $(e); - if ($e.attr('name').startsWith('status_filter[')) { - $e.prop('checked', false); + const resetButtons = form.querySelectorAll('.j-reset-status-filter'); + for (const button of resetButtons) { + button.addEventListener('click', (e) => { + e.preventDefault(); + for (const input of form.querySelectorAll('input[type=radio]')) { + if (input.name.startsWith('status_filter[')) { + input.checked = false; + } } + form.submit(); }); - $form.trigger('submit'); - return false; - }); + } } diff --git a/web_src/js/modules/dirauto.js b/web_src/js/modules/dirauto.js index c917bf8cff2f1..cd90f8155bc10 100644 --- a/web_src/js/modules/dirauto.js +++ b/web_src/js/modules/dirauto.js @@ -1,5 +1,6 @@ -// for performance considerations, it only uses performant syntax +import {isDocumentFragmentOrElementNode} from '../utils/dom.js'; +// for performance considerations, it only uses performant syntax function attachDirAuto(el) { if (el.type !== 'hidden' && el.type !== 'checkbox' && @@ -18,7 +19,7 @@ export function initDirAuto() { const len = mutation.addedNodes.length; for (let i = 0; i < len; i++) { const addedNode = mutation.addedNodes[i]; - if (addedNode.nodeType !== Node.ELEMENT_NODE && addedNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) continue; + if (!isDocumentFragmentOrElementNode(addedNode)) continue; if (addedNode.nodeName === 'INPUT' || addedNode.nodeName === 'TEXTAREA') attachDirAuto(addedNode); const children = addedNode.querySelectorAll('input, textarea'); const len = children.length; diff --git a/web_src/js/modules/tippy.js b/web_src/js/modules/tippy.js index 7f8423e3191cf..27f371fd88659 100644 --- a/web_src/js/modules/tippy.js +++ b/web_src/js/modules/tippy.js @@ -1,4 +1,5 @@ import tippy, {followCursor} from 'tippy.js'; +import {isDocumentFragmentOrElementNode} from '../utils/dom.js'; const visibleInstances = new Set(); @@ -136,8 +137,6 @@ function attachChildrenLazyTooltip(target) { } } -const elementNodeTypes = new Set([Node.ELEMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE]); - export function initGlobalTooltips() { // use MutationObserver to detect new "data-tooltip-content" elements added to the DOM, or attributes changed const observerConnect = (observer) => observer.observe(document, { @@ -152,11 +151,10 @@ export function initGlobalTooltips() { if (mutation.type === 'childList') { // mainly for Vue components and AJAX rendered elements for (const el of mutation.addedNodes) { - if (elementNodeTypes.has(el.nodeType)) { - attachChildrenLazyTooltip(el); - if (el.hasAttribute('data-tooltip-content')) { - attachLazyTooltip(el); - } + if (!isDocumentFragmentOrElementNode(el)) continue; + attachChildrenLazyTooltip(el); + if (el.hasAttribute('data-tooltip-content')) { + attachLazyTooltip(el); } } } else if (mutation.type === 'attributes') { diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index 64a6a5affc482..4dc55a518a605 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -59,6 +59,17 @@ export function onDomReady(cb) { } } +// checks whether an element is owned by the current document, and whether it is a document fragment or element node +// if it is, it means it is a "normal" element managed by us, which can be modified safely. +export function isDocumentFragmentOrElementNode(el) { + try { + return el.ownerDocument === document && el.nodeType === Node.ELEMENT_NODE || el.nodeType === Node.DOCUMENT_FRAGMENT_NODE; + } catch { + // in case the el is not in the same origin, then the access to nodeType would fail + return false; + } +} + // autosize a textarea to fit content. Based on // https://github.com/github/textarea-autosize // --------------------------------------------------------------------- diff --git a/web_src/js/test/setup.js b/web_src/js/vitest.setup.js similarity index 100% rename from web_src/js/test/setup.js rename to web_src/js/vitest.setup.js diff --git a/web_src/js/webcomponents/GiteaOriginUrl.js b/web_src/js/webcomponents/GiteaOriginUrl.js index fca736064c0c9..5d71d95c604c1 100644 --- a/web_src/js/webcomponents/GiteaOriginUrl.js +++ b/web_src/js/webcomponents/GiteaOriginUrl.js @@ -1,17 +1,21 @@ // Convert an absolute or relative URL to an absolute URL with the current origin +export function toOriginUrl(urlStr) { + try { + // only process absolute HTTP/HTTPS URL or relative URLs ('/xxx' or '//host/xxx') + if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) { + const {origin, protocol, hostname, port} = window.location; + const url = new URL(urlStr, origin); + url.protocol = protocol; + url.hostname = hostname; + url.port = port || (protocol === 'https:' ? '443' : '80'); + return url.toString(); + } + } catch {} + return urlStr; +} + window.customElements.define('gitea-origin-url', class extends HTMLElement { connectedCallback() { - const urlStr = this.getAttribute('data-url'); - try { - // only process absolute HTTP/HTTPS URL or relative URLs ('/xxx' or '//host/xxx') - if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) { - const url = new URL(urlStr, window.origin); - url.protocol = window.location.protocol; - url.host = window.location.host; - this.textContent = url.toString(); - return; - } - } catch {} - this.textContent = urlStr; + this.textContent = toOriginUrl(this.getAttribute('data-url')); } }); diff --git a/web_src/js/webcomponents/GiteaOriginUrl.test.js b/web_src/js/webcomponents/GiteaOriginUrl.test.js new file mode 100644 index 0000000000000..f0629842b85a4 --- /dev/null +++ b/web_src/js/webcomponents/GiteaOriginUrl.test.js @@ -0,0 +1,17 @@ +import {toOriginUrl} from './GiteaOriginUrl.js'; + +test('toOriginUrl', () => { + const oldLocation = window.location; + for (const origin of ['https://example.com', 'https://example.com:3000']) { + window.location = new URL(`${origin}/`); + expect(toOriginUrl('/')).toEqual(`${origin}/`); + expect(toOriginUrl('/org/repo.git')).toEqual(`${origin}/org/repo.git`); + expect(toOriginUrl('https://another.com')).toEqual(`${origin}/`); + expect(toOriginUrl('https://another.com/')).toEqual(`${origin}/`); + expect(toOriginUrl('https://another.com/org/repo.git')).toEqual(`${origin}/org/repo.git`); + expect(toOriginUrl('https://another.com:4000')).toEqual(`${origin}/`); + expect(toOriginUrl('https://another.com:4000/')).toEqual(`${origin}/`); + expect(toOriginUrl('https://another.com:4000/org/repo.git')).toEqual(`${origin}/org/repo.git`); + } + window.location = oldLocation; +}); diff --git a/webpack.config.js b/webpack.config.js index c4b140a12bf6d..16afa0ff9c1f1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -55,12 +55,6 @@ const filterCssImport = (url, ...args) => { return true; }; -// in case lightningcss fails to load, fall back to esbuild for css minify -let LightningCssMinifyPlugin; -try { - ({LightningCssMinifyPlugin} = await import('lightningcss-loader')); -} catch {} - /** @type {import("webpack").Configuration} */ export default { mode: isProduction ? 'production' : 'development', @@ -106,12 +100,9 @@ export default { new EsbuildPlugin({ target: 'es2020', minify: true, - css: !LightningCssMinifyPlugin, + css: true, legalComments: 'none', }), - LightningCssMinifyPlugin && new LightningCssMinifyPlugin({ - sourceMap: sourceMaps === 'true', - }), ], splitChunks: { chunks: 'async',