From e45450b744c59f2c02c9bfaf992f12e2f657e3eb Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 8 Apr 2025 02:11:48 +0800 Subject: [PATCH 01/10] docs: add Chinese translations for README files (#34132) - Update README.md to include links to Traditional and Simplified Chinese translations. - Add README.zh-cn.md file containing the Simplified Chinese version of the README. - Add README.zh-tw.md file containing the Traditional Chinese version of the README. - Delete README_ZH.md file. --------- Signed-off-by: appleboy --- README.md | 2 +- README.zh-cn.md | 206 ++++++++++++++++++++++++++++++++++++++++++++++++ README.zh-tw.md | 206 ++++++++++++++++++++++++++++++++++++++++++++++++ README_ZH.md | 156 ------------------------------------ 4 files changed, 413 insertions(+), 157 deletions(-) create mode 100644 README.zh-cn.md create mode 100644 README.zh-tw.md delete mode 100644 README_ZH.md diff --git a/README.md b/README.md index 5ae65cd2ac..017ca629d0 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea) [![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin") -[View this document in Chinese](./README_ZH.md) +[繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md) ## Purpose diff --git a/README.zh-cn.md b/README.zh-cn.md new file mode 100644 index 0000000000..f34b25b945 --- /dev/null +++ b/README.zh-cn.md @@ -0,0 +1,206 @@ +# Gitea + +[![](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml/badge.svg?branch=main)](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly") +[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea") +[![](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card") +[![](https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg)](https://pkg.go.dev/code.gitea.io/gitea "GoDoc") +[![](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest "GitHub release") +[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") +[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea") +[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT") +[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea) +[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin") + +[English](./README.md) | [繁體中文](./README.zh-tw.md) + +## 目的 + +这个项目的目标是提供最简单、最快速、最无痛的方式来设置自托管的 Git 服务。 + +由于 Gitea 是用 Go 语言编写的,它可以在 Go 支持的所有平台和架构上运行,包括 Linux、macOS 和 Windows 的 x86、amd64、ARM 和 PowerPC 架构。这个项目自 2016 年 11 月从 [Gogs](https://gogs.io) [分叉](https://blog.gitea.com/welcome-to-gitea/) 而来,但已经有了很多变化。 + +在线演示可以访问 [demo.gitea.com](https://demo.gitea.com)。 + +要访问免费的 Gitea 服务(有一定数量的仓库限制),可以访问 [gitea.com](https://gitea.com/user/login)。 + +要快速部署您自己的专用 Gitea 实例,可以在 [cloud.gitea.com](https://cloud.gitea.com) 开始免费试用。 + +## 文件 + +您可以在我们的官方 [文件网站](https://docs.gitea.com/) 上找到全面的文件。 + +它包括安装、管理、使用、开发、贡献指南等,帮助您快速入门并有效地探索所有功能。 + +如果您有任何建议或想要贡献,可以访问 [文件仓库](https://gitea.com/gitea/docs) + +## 构建 + +从源代码树的根目录运行: + + TAGS="bindata" make build + +如果需要 SQLite 支持: + + TAGS="bindata sqlite sqlite_unlock_notify" make build + +`build` 目标分为两个子目标: + +- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定义。 +- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。 + +需要互联网连接来下载 go 和 npm 模块。从包含预构建前端文件的官方源代码压缩包构建时,不会触发 `frontend` 目标,因此可以在没有 Node.js 的情况下构建。 + +更多信息:https://docs.gitea.com/installation/install-from-source + +## 使用 + +构建后,默认情况下会在源代码树的根目录生成一个名为 `gitea` 的二进制文件。要运行它,请使用: + + ./gitea web + +> [!注意] +> 如果您对使用我们的 API 感兴趣,我们提供了实验性支持,并附有 [文件](https://docs.gitea.com/api)。 + +## 贡献 + +预期的工作流程是:Fork -> Patch -> Push -> Pull Request + +> [!注意] +> +> 1. **在开始进行 Pull Request 之前,您必须阅读 [贡献者指南](CONTRIBUTING.md)。** +> 2. 如果您在项目中发现了漏洞,请私下写信给 **security@gitea.io**。谢谢! + +## 翻译 + +[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com) + +翻译通过 [Crowdin](https://translate.gitea.com) 进行。如果您想翻译成新的语言,请在 Crowdin 项目中请求管理员添加新语言。 + +您也可以创建一个 issue 来添加语言,或者在 discord 的 #translation 频道上询问。如果您需要上下文或发现一些翻译问题,可以在字符串上留言或在 Discord 上询问。对于一般的翻译问题,文档中有一个部分。目前有点空,但我们希望随着问题的出现而填充它。 + +更多信息请参阅 [文件](https://docs.gitea.com/contributing/localization)。 + +## 官方和第三方项目 + +我们提供了一个官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一个名为 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一个 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。 + +我们在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 维护了一个 Gitea 相关项目的列表,您可以在那里发现更多的第三方项目,包括 SDK、插件、主题等。 + +## 通讯 + +[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea") + +如果您有任何文件未涵盖的问题,可以在我们的 [Discord 服务器](https://discord.gg/Gitea) 上与我们联系,或者在 [discourse 论坛](https://forum.gitea.com/) 上创建帖子。 + +## 作者 + +- [维护者](https://github.com/orgs/go-gitea/people) +- [贡献者](https://github.com/go-gitea/gitea/graphs/contributors) +- [翻译者](options/locale/TRANSLATORS) + +## 支持者 + +感谢所有支持者! 🙏 [[成为支持者](https://opencollective.com/gitea#backer)] + + + +## 赞助商 + +通过成为赞助商来支持这个项目。您的标志将显示在这里,并带有链接到您的网站。 [[成为赞助商](https://opencollective.com/gitea#sponsor)] + + + + + + + + + + + + +## 常见问题 + +**Gitea 怎么发音?** + +Gitea 的发音是 [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY),就像 "gi-tea" 一样,g 是硬音。 + +**为什么这个项目没有托管在 Gitea 实例上?** + +我们正在 [努力](https://github.com/go-gitea/gitea/issues/1029)。 + +**在哪里可以找到安全补丁?** + +在 [发布日志](https://github.com/go-gitea/gitea/releases) 或 [变更日志](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md) 中,搜索关键词 `SECURITY` 以找到安全补丁。 + +## 许可证 + +这个项目是根据 MIT 许可证授权的。 +请参阅 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件以获取完整的许可证文本。 + +## 进一步信息 + +
+寻找界面概述?查看这里! + +### 登录/注册页面 + +![Login](https://dl.gitea.com/screenshots/login.png) +![Register](https://dl.gitea.com/screenshots/register.png) + +### 用户仪表板 + +![Home](https://dl.gitea.com/screenshots/home.png) +![Issues](https://dl.gitea.com/screenshots/issues.png) +![Pull Requests](https://dl.gitea.com/screenshots/pull_requests.png) +![Milestones](https://dl.gitea.com/screenshots/milestones.png) + +### 用户资料 + +![Profile](https://dl.gitea.com/screenshots/user_profile.png) + +### 探索 + +![Repos](https://dl.gitea.com/screenshots/explore_repos.png) +![Users](https://dl.gitea.com/screenshots/explore_users.png) +![Orgs](https://dl.gitea.com/screenshots/explore_orgs.png) + +### 仓库 + +![Home](https://dl.gitea.com/screenshots/repo_home.png) +![Commits](https://dl.gitea.com/screenshots/repo_commits.png) +![Branches](https://dl.gitea.com/screenshots/repo_branches.png) +![Labels](https://dl.gitea.com/screenshots/repo_labels.png) +![Milestones](https://dl.gitea.com/screenshots/repo_milestones.png) +![Releases](https://dl.gitea.com/screenshots/repo_releases.png) +![Tags](https://dl.gitea.com/screenshots/repo_tags.png) + +#### 仓库问题 + +![List](https://dl.gitea.com/screenshots/repo_issues.png) +![Issue](https://dl.gitea.com/screenshots/repo_issue.png) + +#### 仓库拉取请求 + +![List](https://dl.gitea.com/screenshots/repo_pull_requests.png) +![Pull Request](https://dl.gitea.com/screenshots/repo_pull_request.png) +![File](https://dl.gitea.com/screenshots/repo_pull_request_file.png) +![Commits](https://dl.gitea.com/screenshots/repo_pull_request_commits.png) + +#### 仓库操作 + +![List](https://dl.gitea.com/screenshots/repo_actions.png) +![Details](https://dl.gitea.com/screenshots/repo_actions_run.png) + +#### 仓库活动 + +![Activity](https://dl.gitea.com/screenshots/repo_activity.png) +![Contributors](https://dl.gitea.com/screenshots/repo_contributors.png) +![Code Frequency](https://dl.gitea.com/screenshots/repo_code_frequency.png) +![Recent Commits](https://dl.gitea.com/screenshots/repo_recent_commits.png) + +### 组织 + +![Home](https://dl.gitea.com/screenshots/org_home.png) + +
diff --git a/README.zh-tw.md b/README.zh-tw.md new file mode 100644 index 0000000000..9de3f85dd5 --- /dev/null +++ b/README.zh-tw.md @@ -0,0 +1,206 @@ +# Gitea + +[![](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml/badge.svg?branch=main)](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly") +[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea") +[![](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card") +[![](https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg)](https://pkg.go.dev/code.gitea.io/gitea "GoDoc") +[![](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest "GitHub release") +[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") +[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea") +[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT") +[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea) +[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin") + +[English](./README.md) | [简体中文](./README.zh-cn.md) + +## 目的 + +這個項目的目標是提供最簡單、最快速、最無痛的方式來設置自託管的 Git 服務。 + +由於 Gitea 是用 Go 語言編寫的,它可以在 Go 支援的所有平台和架構上運行,包括 Linux、macOS 和 Windows 的 x86、amd64、ARM 和 PowerPC 架構。這個項目自 2016 年 11 月從 [Gogs](https://gogs.io) [分叉](https://blog.gitea.com/welcome-to-gitea/) 而來,但已經有了很多變化。 + +在線演示可以訪問 [demo.gitea.com](https://demo.gitea.com)。 + +要訪問免費的 Gitea 服務(有一定數量的倉庫限制),可以訪問 [gitea.com](https://gitea.com/user/login)。 + +要快速部署您自己的專用 Gitea 實例,可以在 [cloud.gitea.com](https://cloud.gitea.com) 開始免費試用。 + +## 文件 + +您可以在我們的官方 [文件網站](https://docs.gitea.com/) 上找到全面的文件。 + +它包括安裝、管理、使用、開發、貢獻指南等,幫助您快速入門並有效地探索所有功能。 + +如果您有任何建議或想要貢獻,可以訪問 [文件倉庫](https://gitea.com/gitea/docs) + +## 構建 + +從源代碼樹的根目錄運行: + + TAGS="bindata" make build + +如果需要 SQLite 支援: + + TAGS="bindata sqlite sqlite_unlock_notify" make build + +`build` 目標分為兩個子目標: + +- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定義。 +- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。 + +需要互聯網連接來下載 go 和 npm 模塊。從包含預構建前端文件的官方源代碼壓縮包構建時,不會觸發 `frontend` 目標,因此可以在沒有 Node.js 的情況下構建。 + +更多信息:https://docs.gitea.com/installation/install-from-source + +## 使用 + +構建後,默認情況下會在源代碼樹的根目錄生成一個名為 `gitea` 的二進制文件。要運行它,請使用: + + ./gitea web + +> [!注意] +> 如果您對使用我們的 API 感興趣,我們提供了實驗性支援,並附有 [文件](https://docs.gitea.com/api)。 + +## 貢獻 + +預期的工作流程是:Fork -> Patch -> Push -> Pull Request + +> [!注意] +> +> 1. **在開始進行 Pull Request 之前,您必須閱讀 [貢獻者指南](CONTRIBUTING.md)。** +> 2. 如果您在項目中發現了漏洞,請私下寫信給 **security@gitea.io**。謝謝! + +## 翻譯 + +[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com) + +翻譯通過 [Crowdin](https://translate.gitea.com) 進行。如果您想翻譯成新的語言,請在 Crowdin 項目中請求管理員添加新語言。 + +您也可以創建一個 issue 來添加語言,或者在 discord 的 #translation 頻道上詢問。如果您需要上下文或發現一些翻譯問題,可以在字符串上留言或在 Discord 上詢問。對於一般的翻譯問題,文檔中有一個部分。目前有點空,但我們希望隨著問題的出現而填充它。 + +更多信息請參閱 [文件](https://docs.gitea.com/contributing/localization)。 + +## 官方和第三方項目 + +我們提供了一個官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一個名為 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一個 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。 + +我們在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 維護了一個 Gitea 相關項目的列表,您可以在那裡發現更多的第三方項目,包括 SDK、插件、主題等。 + +## 通訊 + +[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea") + +如果您有任何文件未涵蓋的問題,可以在我們的 [Discord 服務器](https://discord.gg/Gitea) 上與我們聯繫,或者在 [discourse 論壇](https://forum.gitea.com/) 上創建帖子。 + +## 作者 + +- [維護者](https://github.com/orgs/go-gitea/people) +- [貢獻者](https://github.com/go-gitea/gitea/graphs/contributors) +- [翻譯者](options/locale/TRANSLATORS) + +## 支持者 + +感謝所有支持者! 🙏 [[成為支持者](https://opencollective.com/gitea#backer)] + + + +## 贊助商 + +通過成為贊助商來支持這個項目。您的標誌將顯示在這裡,並帶有鏈接到您的網站。 [[成為贊助商](https://opencollective.com/gitea#sponsor)] + + + + + + + + + + + + +## 常見問題 + +**Gitea 怎麼發音?** + +Gitea 的發音是 [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY),就像 "gi-tea" 一樣,g 是硬音。 + +**為什麼這個項目沒有託管在 Gitea 實例上?** + +我們正在 [努力](https://github.com/go-gitea/gitea/issues/1029)。 + +**在哪裡可以找到安全補丁?** + +在 [發佈日誌](https://github.com/go-gitea/gitea/releases) 或 [變更日誌](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md) 中,搜索關鍵詞 `SECURITY` 以找到安全補丁。 + +## 許可證 + +這個項目是根據 MIT 許可證授權的。 +請參閱 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件以獲取完整的許可證文本。 + +## 進一步信息 + +
+尋找界面概述?查看這裡! + +### 登錄/註冊頁面 + +![Login](https://dl.gitea.com/screenshots/login.png) +![Register](https://dl.gitea.com/screenshots/register.png) + +### 用戶儀表板 + +![Home](https://dl.gitea.com/screenshots/home.png) +![Issues](https://dl.gitea.com/screenshots/issues.png) +![Pull Requests](https://dl.gitea.com/screenshots/pull_requests.png) +![Milestones](https://dl.gitea.com/screenshots/milestones.png) + +### 用戶資料 + +![Profile](https://dl.gitea.com/screenshots/user_profile.png) + +### 探索 + +![Repos](https://dl.gitea.com/screenshots/explore_repos.png) +![Users](https://dl.gitea.com/screenshots/explore_users.png) +![Orgs](https://dl.gitea.com/screenshots/explore_orgs.png) + +### 倉庫 + +![Home](https://dl.gitea.com/screenshots/repo_home.png) +![Commits](https://dl.gitea.com/screenshots/repo_commits.png) +![Branches](https://dl.gitea.com/screenshots/repo_branches.png) +![Labels](https://dl.gitea.com/screenshots/repo_labels.png) +![Milestones](https://dl.gitea.com/screenshots/repo_milestones.png) +![Releases](https://dl.gitea.com/screenshots/repo_releases.png) +![Tags](https://dl.gitea.com/screenshots/repo_tags.png) + +#### 倉庫問題 + +![List](https://dl.gitea.com/screenshots/repo_issues.png) +![Issue](https://dl.gitea.com/screenshots/repo_issue.png) + +#### 倉庫拉取請求 + +![List](https://dl.gitea.com/screenshots/repo_pull_requests.png) +![Pull Request](https://dl.gitea.com/screenshots/repo_pull_request.png) +![File](https://dl.gitea.com/screenshots/repo_pull_request_file.png) +![Commits](https://dl.gitea.com/screenshots/repo_pull_request_commits.png) + +#### 倉庫操作 + +![List](https://dl.gitea.com/screenshots/repo_actions.png) +![Details](https://dl.gitea.com/screenshots/repo_actions_run.png) + +#### 倉庫活動 + +![Activity](https://dl.gitea.com/screenshots/repo_activity.png) +![Contributors](https://dl.gitea.com/screenshots/repo_contributors.png) +![Code Frequency](https://dl.gitea.com/screenshots/repo_code_frequency.png) +![Recent Commits](https://dl.gitea.com/screenshots/repo_recent_commits.png) + +### 組織 + +![Home](https://dl.gitea.com/screenshots/org_home.png) + +
diff --git a/README_ZH.md b/README_ZH.md deleted file mode 100644 index 89c34f6b63..0000000000 --- a/README_ZH.md +++ /dev/null @@ -1,156 +0,0 @@ -# Gitea - -[![](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml/badge.svg?branch=main)](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly") -[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea") -[![](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card") -[![](https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg)](https://pkg.go.dev/code.gitea.io/gitea "GoDoc") -[![](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest "GitHub release") -[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") -[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea") -[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT") -[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea) -[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin") - -[View this document in English](./README.md) - -## 目标 - -Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux、macOS 和 Windows 以及各种架构,除了 x86 和 amd64,还包括 ARM 和 PowerPC。 - -如果你想试用在线演示和报告问题,请访问 [demo.gitea.com](https://demo.gitea.com/)。 - -如果你想使用免费的 Gitea 服务(有仓库数量限制),请访问 [gitea.com](https://gitea.com/user/login)。 - -如果你想在 Gitea Cloud 上快速部署你自己独享的 Gitea 实例,请访问 [cloud.gitea.com](https://cloud.gitea.com) 开始免费试用。 - -## 文档 - -关于如何安装请访问我们的 [文档站](https://docs.gitea.com/zh-cn/category/installation),如果没有找到对应的文档,你也可以通过 [Discord - 英文](https://discord.gg/gitea) 和 QQ群 328432459 来和我们交流。 - -## 编译 - -在源代码的根目录下执行: - - TAGS="bindata" make build - -或者如果需要SQLite支持: - - TAGS="bindata sqlite sqlite_unlock_notify" make build - -编译过程会分成2个子任务: - -- `make backend`,需要 [Go Stable](https://go.dev/dl/),最低版本需求可查看 [go.mod](/go.mod)。 -- `make frontend`,需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。 - -你需要连接网络来下载 go 和 npm modules。当从 tar 格式的源文件编译时,其中包含了预编译的前端文件,因此 `make frontend` 将不会被执行。这允许编译时不需要 Node.js。 - -更多信息: https://docs.gitea.com/installation/install-from-source - -## 使用 - -编译之后,默认会在根目录下生成一个名为 `gitea` 的文件。你可以这样执行它: - - ./gitea web - -> [!注意] -> 如果你要使用API,请参见 [API 文档](https://godoc.org/code.gitea.io/sdk/gitea)。 - -## 贡献 - -贡献流程:Fork -> Patch -> Push -> Pull Request - -> [!注意] -> -> 1. **开始贡献代码之前请确保你已经看过了 [贡献者向导(英文)](CONTRIBUTING.md)**。 -> 2. 所有的安全问题,请私下发送邮件给 **security@gitea.io**。 谢谢! - -## 翻译 - -[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com) - -多语言翻译是基于Crowdin进行的。 - -从 [文档](https://docs.gitea.com/contributing/localization) 中获取更多信息。 - -## 官方和第三方项目 - -Gitea 提供官方的 [go-sdk](https://gitea.com/gitea/go-sdk),以及名为 [tea](https://gitea.com/gitea/tea) 的 CLI 工具 和 用于 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。 - -[gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 是一个 Gitea 相关项目的列表,你可以在这里找到更多的第三方项目,包括 SDK、插件、主题等等。 - -## 作者 - -- [Maintainers](https://github.com/orgs/go-gitea/people) -- [Contributors](https://github.com/go-gitea/gitea/graphs/contributors) -- [Translators](options/locale/TRANSLATORS) - -## 授权许可 - -本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件中。 - -## 更多信息 - -
-截图 - -### 登录界面 - -![登录](https://dl.gitea.com/screenshots/login.png) -![注册](https://dl.gitea.com/screenshots/register.png) - -### 用户首页 - -![首页](https://dl.gitea.com/screenshots/home.png) -![工单列表](https://dl.gitea.com/screenshots/issues.png) -![合并请求列表](https://dl.gitea.com/screenshots/pull_requests.png) -![里程碑列表](https://dl.gitea.com/screenshots/milestones.png) - -### 用户资料 - -![用户资料](https://dl.gitea.com/screenshots/user_profile.png) - -### 探索 - -![仓库列表](https://dl.gitea.com/screenshots/explore_repos.png) -![用户列表](https://dl.gitea.com/screenshots/explore_users.png) -![组织列表](https://dl.gitea.com/screenshots/explore_orgs.png) - -### 仓库 - -![首页](https://dl.gitea.com/screenshots/repo_home.png) -![提交列表](https://dl.gitea.com/screenshots/repo_commits.png) -![分支列表](https://dl.gitea.com/screenshots/repo_branches.png) -![标签列表](https://dl.gitea.com/screenshots/repo_labels.png) -![里程碑列表](https://dl.gitea.com/screenshots/repo_milestones.png) -![版本发布](https://dl.gitea.com/screenshots/repo_releases.png) -![标签列表](https://dl.gitea.com/screenshots/repo_tags.png) - -#### 仓库工单 - -![列表](https://dl.gitea.com/screenshots/repo_issues.png) -![工单](https://dl.gitea.com/screenshots/repo_issue.png) - -#### 仓库合并请求 - -![列表](https://dl.gitea.com/screenshots/repo_pull_requests.png) -![合并请求](https://dl.gitea.com/screenshots/repo_pull_request.png) -![文件](https://dl.gitea.com/screenshots/repo_pull_request_file.png) -![提交列表](https://dl.gitea.com/screenshots/repo_pull_request_commits.png) - -#### 仓库 Actions - -![列表](https://dl.gitea.com/screenshots/repo_actions.png) -![Run](https://dl.gitea.com/screenshots/repo_actions_run.png) - -#### 仓库动态 - -![动态](https://dl.gitea.com/screenshots/repo_activity.png) -![贡献者](https://dl.gitea.com/screenshots/repo_contributors.png) -![代码频率](https://dl.gitea.com/screenshots/repo_code_frequency.png) -![最近的提交](https://dl.gitea.com/screenshots/repo_recent_commits.png) - -### 组织 - -![首页](https://dl.gitea.com/screenshots/org_home.png) - -
From 8ca51abaddbf0b30c2607b4c4787b34c016a52d9 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Tue, 8 Apr 2025 00:34:12 +0000 Subject: [PATCH 02/10] [skip ci] Updated translations via Crowdin --- options/locale/locale_fr-FR.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index db70eaf66a..3c19d053da 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -1707,11 +1707,11 @@ issues.start_tracking_history=`a commencé son travail %s.` issues.tracker_auto_close=Le minuteur sera automatiquement arrêté quand le ticket sera fermé. issues.tracking_already_started=`Vous avez déjà un minuteur en cours sur un autre ticket !` issues.stop_tracking=Arrêter le minuteur -issues.stop_tracking_history=a travaillé sur %[1]s %[2]s +issues.stop_tracking_history=a travaillé %[1]s %[2]s issues.cancel_tracking=Abandonner issues.cancel_tracking_history=`a abandonné son minuteur %s.` issues.del_time=Supprimer ce minuteur du journal -issues.add_time_history=a pointé du temps de travail sur %[1]s, %[2]s +issues.add_time_history=a pointé %[1]s de travail %[2]s. issues.del_time_history=`a supprimé son temps de travail %s.` issues.add_time_manually=Temps pointé manuellement issues.add_time_hours=Heures From 07c6087878f9b7ce394104ec806069b0c35793be Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 8 Apr 2025 12:15:15 +0800 Subject: [PATCH 03/10] Fix team permission (#34128) The `team.access_mode` should be either `none` or `admin/owner`. For non-admin team, the real permissions are provided by `team_unit`. --- models/issues/issue_update.go | 3 +- models/organization/org_user.go | 2 +- models/organization/team.go | 24 ++++----------- models/perm/access/repo_permission.go | 4 +-- models/unit/unit.go | 43 ++++++++++----------------- routers/api/v1/org/team.go | 36 +++++++--------------- routers/api/v1/repo/collaborators.go | 2 +- routers/web/org/teams.go | 26 ++++++---------- services/context/org.go | 4 +-- services/org/team.go | 31 ------------------- services/org/team_test.go | 18 ----------- templates/org/team/new.tmpl | 2 +- templates/org/team/sidebar.tmpl | 4 ++- tests/integration/api_team_test.go | 16 +++++----- tests/integration/org_test.go | 4 +-- 15 files changed, 62 insertions(+), 157 deletions(-) diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go index 746a59c6fd..5e9c012c05 100644 --- a/models/issues/issue_update.go +++ b/models/issues/issue_update.go @@ -11,7 +11,6 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" - "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" @@ -612,7 +611,7 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u unittype = unit.TypePullRequests } for _, team := range teams { - if team.AccessMode >= perm.AccessModeAdmin { + if team.HasAdminAccess() { checked = append(checked, team.ID) resolved[issue.Repo.Owner.LowerName+"/"+team.LowerName] = true continue diff --git a/models/organization/org_user.go b/models/organization/org_user.go index 08d936d922..4d7527c15f 100644 --- a/models/organization/org_user.go +++ b/models/organization/org_user.go @@ -78,7 +78,7 @@ func IsOrganizationAdmin(ctx context.Context, orgID, uid int64) (bool, error) { return false, err } for _, t := range teams { - if t.AccessMode >= perm.AccessModeAdmin { + if t.HasAdminAccess() { return true, nil } } diff --git a/models/organization/team.go b/models/organization/team.go index 96666da39a..7f3a9b3829 100644 --- a/models/organization/team.go +++ b/models/organization/team.go @@ -113,7 +113,7 @@ func (t *Team) LoadUnits(ctx context.Context) (err error) { // GetUnitNames returns the team units names func (t *Team) GetUnitNames() (res []string) { - if t.AccessMode >= perm.AccessModeAdmin { + if t.HasAdminAccess() { return unit.AllUnitKeyNames() } @@ -126,7 +126,7 @@ func (t *Team) GetUnitNames() (res []string) { // GetUnitsMap returns the team units permissions func (t *Team) GetUnitsMap() map[string]string { m := make(map[string]string) - if t.AccessMode >= perm.AccessModeAdmin { + if t.HasAdminAccess() { for _, u := range unit.Units { m[u.NameKey] = t.AccessMode.ToString() } @@ -153,6 +153,10 @@ func (t *Team) IsMember(ctx context.Context, userID int64) bool { return isMember } +func (t *Team) HasAdminAccess() bool { + return t.AccessMode >= perm.AccessModeAdmin +} + // LoadMembers returns paginated members in team of organization. func (t *Team) LoadMembers(ctx context.Context) (err error) { t.Members, err = GetTeamMembers(ctx, &SearchMembersOptions{ @@ -238,22 +242,6 @@ func GetTeamByID(ctx context.Context, teamID int64) (*Team, error) { return t, nil } -// GetTeamNamesByID returns team's lower name from a list of team ids. -func GetTeamNamesByID(ctx context.Context, teamIDs []int64) ([]string, error) { - if len(teamIDs) == 0 { - return []string{}, nil - } - - var teamNames []string - err := db.GetEngine(ctx).Table("team"). - Select("lower_name"). - In("id", teamIDs). - Asc("name"). - Find(&teamNames) - - return teamNames, err -} - // IncrTeamRepoNum increases the number of repos for the given team by 1 func IncrTeamRepoNum(ctx context.Context, teamID int64) error { _, err := db.GetEngine(ctx).Incr("num_repos").ID(teamID).Update(new(Team)) diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index f42c96bbe2..9d0c9f0077 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -331,7 +331,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use // if user in an owner team for _, team := range teams { - if team.AccessMode >= perm_model.AccessModeAdmin { + if team.HasAdminAccess() { perm.AccessMode = perm_model.AccessModeOwner perm.unitsMode = nil return perm, nil @@ -399,7 +399,7 @@ func IsUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *use } for _, team := range teams { - if team.AccessMode >= perm_model.AccessModeAdmin { + if team.HasAdminAccess() { return true, nil } } diff --git a/models/unit/unit.go b/models/unit/unit.go index c816fc6c68..4ca676802f 100644 --- a/models/unit/unit.go +++ b/models/unit/unit.go @@ -20,17 +20,21 @@ type Type int // Enumerate all the unit types const ( - TypeInvalid Type = iota // 0 invalid - TypeCode // 1 code - TypeIssues // 2 issues - TypePullRequests // 3 PRs - TypeReleases // 4 Releases - TypeWiki // 5 Wiki - TypeExternalWiki // 6 ExternalWiki - TypeExternalTracker // 7 ExternalTracker - TypeProjects // 8 Projects - TypePackages // 9 Packages - TypeActions // 10 Actions + TypeInvalid Type = iota // 0 invalid + + TypeCode // 1 code + TypeIssues // 2 issues + TypePullRequests // 3 PRs + TypeReleases // 4 Releases + TypeWiki // 5 Wiki + TypeExternalWiki // 6 ExternalWiki + TypeExternalTracker // 7 ExternalTracker + TypeProjects // 8 Projects + TypePackages // 9 Packages + TypeActions // 10 Actions + + // FIXME: TEAM-UNIT-PERMISSION: the team unit "admin" permission's design is not right, when a new unit is added in the future, + // admin team won't inherit the correct admin permission for the new unit, need to have a complete fix before adding any new unit. ) // Value returns integer value for unit type (used by template) @@ -380,20 +384,3 @@ func AllUnitKeyNames() []string { } return res } - -// MinUnitAccessMode returns the minial permission of the permission map -func MinUnitAccessMode(unitsMap map[Type]perm.AccessMode) perm.AccessMode { - res := perm.AccessModeNone - for t, mode := range unitsMap { - // Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms. - if t == TypeExternalTracker || t == TypeExternalWiki { - continue - } - - // get the minial permission great than AccessModeNone except all are AccessModeNone - if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) { - res = mode - } - } - return res -} diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index f70e5dd235..71c21f2dde 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -141,26 +141,18 @@ func GetTeam(ctx *context.APIContext) { ctx.JSON(http.StatusOK, apiTeam) } -func attachTeamUnits(team *organization.Team, units []string) { +func attachTeamUnits(team *organization.Team, defaultAccessMode perm.AccessMode, units []string) { unitTypes, _ := unit_model.FindUnitTypes(units...) team.Units = make([]*organization.TeamUnit, 0, len(units)) for _, tp := range unitTypes { team.Units = append(team.Units, &organization.TeamUnit{ OrgID: team.OrgID, Type: tp, - AccessMode: team.AccessMode, + AccessMode: defaultAccessMode, }) } } -func convertUnitsMap(unitsMap map[string]string) map[unit_model.Type]perm.AccessMode { - res := make(map[unit_model.Type]perm.AccessMode, len(unitsMap)) - for unitKey, p := range unitsMap { - res[unit_model.TypeFromKey(unitKey)] = perm.ParseAccessMode(p) - } - return res -} - func attachTeamUnitsMap(team *organization.Team, unitsMap map[string]string) { team.Units = make([]*organization.TeamUnit, 0, len(unitsMap)) for unitKey, p := range unitsMap { @@ -214,24 +206,22 @@ func CreateTeam(ctx *context.APIContext) { // "422": // "$ref": "#/responses/validationError" form := web.GetForm(ctx).(*api.CreateTeamOption) - p := perm.ParseAccessMode(form.Permission) - if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 { - p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap)) - } + teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin) team := &organization.Team{ OrgID: ctx.Org.Organization.ID, Name: form.Name, Description: form.Description, IncludesAllRepositories: form.IncludesAllRepositories, CanCreateOrgRepo: form.CanCreateOrgRepo, - AccessMode: p, + AccessMode: teamPermission, } if team.AccessMode < perm.AccessModeAdmin { if len(form.UnitsMap) > 0 { attachTeamUnitsMap(team, form.UnitsMap) } else if len(form.Units) > 0 { - attachTeamUnits(team, form.Units) + unitPerm := perm.ParseAccessMode(form.Permission, perm.AccessModeRead, perm.AccessModeWrite) + attachTeamUnits(team, unitPerm, form.Units) } else { ctx.APIErrorInternal(errors.New("units permission should not be empty")) return @@ -304,15 +294,10 @@ func EditTeam(ctx *context.APIContext) { isAuthChanged := false isIncludeAllChanged := false if !team.IsOwnerTeam() && len(form.Permission) != 0 { - // Validate permission level. - p := perm.ParseAccessMode(form.Permission) - if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 { - p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap)) - } - - if team.AccessMode != p { + teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin) + if team.AccessMode != teamPermission { isAuthChanged = true - team.AccessMode = p + team.AccessMode = teamPermission } if form.IncludesAllRepositories != nil { @@ -325,7 +310,8 @@ func EditTeam(ctx *context.APIContext) { if len(form.UnitsMap) > 0 { attachTeamUnitsMap(team, form.UnitsMap) } else if len(form.Units) > 0 { - attachTeamUnits(team, form.Units) + unitPerm := perm.ParseAccessMode(form.Permission, perm.AccessModeRead, perm.AccessModeWrite) + attachTeamUnits(team, unitPerm, form.Units) } } else { attachAdminTeamUnits(team) diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index a54225f0fd..d1652c1d51 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -181,7 +181,7 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) { p := perm.AccessModeWrite if form.Permission != nil { - p = perm.ParseAccessMode(*form.Permission) + p = perm.ParseAccessMode(*form.Permission, perm.AccessModeRead, perm.AccessModeWrite, perm.AccessModeAdmin) } if err := repo_service.AddOrUpdateCollaborator(ctx, ctx.Repo.Repository, collaborator, p); err != nil { diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index aeea3708b2..b1b0dd2c49 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -284,6 +284,8 @@ func NewTeam(ctx *context.Context) { ctx.HTML(http.StatusOK, tplTeamNew) } +// FIXME: TEAM-UNIT-PERMISSION: this design is not right, when a new unit is added in the future, +// admin team won't inherit the correct admin permission for the new unit. func getUnitPerms(forms url.Values, teamPermission perm.AccessMode) map[unit_model.Type]perm.AccessMode { unitPerms := make(map[unit_model.Type]perm.AccessMode) for _, ut := range unit_model.AllRepoUnitTypes { @@ -314,19 +316,14 @@ func getUnitPerms(forms url.Values, teamPermission perm.AccessMode) map[unit_mod func NewTeamPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.CreateTeamForm) includesAllRepositories := form.RepoAccess == "all" - p := perm.ParseAccessMode(form.Permission) - unitPerms := getUnitPerms(ctx.Req.Form, p) - if p < perm.AccessModeAdmin { - // if p is less than admin accessmode, then it should be general accessmode, - // so we should calculate the minial accessmode from units accessmodes. - p = unit_model.MinUnitAccessMode(unitPerms) - } + teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin) + unitPerms := getUnitPerms(ctx.Req.Form, teamPermission) t := &org_model.Team{ OrgID: ctx.Org.Organization.ID, Name: form.TeamName, Description: form.Description, - AccessMode: p, + AccessMode: teamPermission, IncludesAllRepositories: includesAllRepositories, CanCreateOrgRepo: form.CanCreateOrgRepo, } @@ -485,13 +482,8 @@ func EditTeam(ctx *context.Context) { func EditTeamPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.CreateTeamForm) t := ctx.Org.Team - newAccessMode := perm.ParseAccessMode(form.Permission) - unitPerms := getUnitPerms(ctx.Req.Form, newAccessMode) - if newAccessMode < perm.AccessModeAdmin { - // if newAccessMode is less than admin accessmode, then it should be general accessmode, - // so we should calculate the minial accessmode from units accessmodes. - newAccessMode = unit_model.MinUnitAccessMode(unitPerms) - } + teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin) + unitPerms := getUnitPerms(ctx.Req.Form, teamPermission) isAuthChanged := false isIncludeAllChanged := false includesAllRepositories := form.RepoAccess == "all" @@ -503,9 +495,9 @@ func EditTeamPost(ctx *context.Context) { if !t.IsOwnerTeam() { t.Name = form.TeamName - if t.AccessMode != newAccessMode { + if t.AccessMode != teamPermission { isAuthChanged = true - t.AccessMode = newAccessMode + t.AccessMode = teamPermission } if t.IncludesAllRepositories != includesAllRepositories { diff --git a/services/context/org.go b/services/context/org.go index 992a48afa0..c8b6ed09b7 100644 --- a/services/context/org.go +++ b/services/context/org.go @@ -182,7 +182,7 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) { return } for _, team := range teams { - if team.IncludesAllRepositories && team.AccessMode >= perm.AccessModeAdmin { + if team.IncludesAllRepositories && team.HasAdminAccess() { shouldSeeAllTeams = true break } @@ -228,7 +228,7 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) { return } - ctx.Org.IsTeamAdmin = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.AccessMode >= perm.AccessModeAdmin + ctx.Org.IsTeamAdmin = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.HasAdminAccess() ctx.Data["IsTeamAdmin"] = ctx.Org.IsTeamAdmin if opts.RequireTeamAdmin && !ctx.Org.IsTeamAdmin { ctx.NotFound(err) diff --git a/services/org/team.go b/services/org/team.go index ee3bd898ea..6890dafd90 100644 --- a/services/org/team.go +++ b/services/org/team.go @@ -259,37 +259,6 @@ func AddTeamMember(ctx context.Context, team *organization.Team, user *user_mode } team.NumMembers++ - - // Give access to team repositories. - // update exist access if mode become bigger - subQuery := builder.Select("repo_id").From("team_repo"). - Where(builder.Eq{"team_id": team.ID}) - - if _, err := sess.Where("user_id=?", user.ID). - In("repo_id", subQuery). - And("mode < ?", team.AccessMode). - SetExpr("mode", team.AccessMode). - Update(new(access_model.Access)); err != nil { - return fmt.Errorf("update user accesses: %w", err) - } - - // for not exist access - var repoIDs []int64 - accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": user.ID}) - if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil { - return fmt.Errorf("select id accesses: %w", err) - } - - accesses := make([]*access_model.Access, 0, 100) - for i, repoID := range repoIDs { - accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: user.ID, Mode: team.AccessMode}) - if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 { - if err = db.Insert(ctx, accesses); err != nil { - return fmt.Errorf("insert new user accesses: %w", err) - } - accesses = accesses[:0] - } - } return nil }) if err != nil { diff --git a/services/org/team_test.go b/services/org/team_test.go index a7070fadb0..831fe1669f 100644 --- a/services/org/team_test.go +++ b/services/org/team_test.go @@ -166,24 +166,6 @@ func TestRemoveTeamMember(t *testing.T) { assert.True(t, organization.IsErrLastOrgOwner(err)) } -func TestRepository_RecalculateAccesses3(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - team5 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}) - user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}) - - has, err := db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: user29.ID, RepoID: 23}) - assert.NoError(t, err) - assert.False(t, has) - - // adding user29 to team5 should add an explicit access row for repo 23 - // even though repo 23 is public - assert.NoError(t, AddTeamMember(db.DefaultContext, team5, user29)) - - has, err = db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: user29.ID, RepoID: 23}) - assert.NoError(t, err) - assert.True(t, has) -} - func TestIncludesAllRepositoriesTeams(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) diff --git a/templates/org/team/new.tmpl b/templates/org/team/new.tmpl index b67c18dd7d..67529ddfba 100644 --- a/templates/org/team/new.tmpl +++ b/templates/org/team/new.tmpl @@ -56,7 +56,7 @@
- + {{ctx.Locale.Tr "org.teams.general_access_helper"}}
diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl index c4acd8da24..8390bf0acd 100644 --- a/templates/org/team/sidebar.tmpl +++ b/templates/org/team/sidebar.tmpl @@ -42,10 +42,12 @@
  • {{ctx.Locale.Tr "org.teams.can_create_org_repo"}}
  • {{end}} - {{if (eq .Team.AccessMode 2)}} + {{/* the AccessMode should be either none or admin/owner, the real permissions are provided by each team unit */}} + {{if false}}{{/*(eq .Team.AccessMode 2)*/}}

    {{ctx.Locale.Tr "org.settings.permission"}}

    {{ctx.Locale.Tr "org.teams.write_permission_desc"}} {{else if (eq .Team.AccessMode 3)}} + {{/* FIXME: here might not right, see "FIXME: TEAM-UNIT-PERMISSION", new units might not have correct admin permission*/}}

    {{ctx.Locale.Tr "org.settings.permission"}}

    {{ctx.Locale.Tr "org.teams.admin_permission_desc"}} {{else}} diff --git a/tests/integration/api_team_test.go b/tests/integration/api_team_test.go index a2a2f388fd..e54203ed50 100644 --- a/tests/integration/api_team_test.go +++ b/tests/integration/api_team_test.go @@ -78,9 +78,9 @@ func TestAPITeam(t *testing.T) { apiTeam = api.Team{} DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "CreateTeam1", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - teamToCreate.Permission, teamToCreate.Units, nil) + "none", teamToCreate.Units, nil) checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - teamToCreate.Permission, teamToCreate.Units, nil) + "none", teamToCreate.Units, nil) teamID := apiTeam.ID // Edit team. @@ -149,9 +149,9 @@ func TestAPITeam(t *testing.T) { apiTeam = api.Team{} DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "CreateTeam2", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - "read", nil, teamToCreate.UnitsMap) + "none", nil, teamToCreate.UnitsMap) checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - "read", nil, teamToCreate.UnitsMap) + "none", nil, teamToCreate.UnitsMap) teamID = apiTeam.ID // Edit team. @@ -171,9 +171,9 @@ func TestAPITeam(t *testing.T) { apiTeam = api.Team{} DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "EditTeam2", &apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, - "read", nil, teamToEdit.UnitsMap) + "none", nil, teamToEdit.UnitsMap) checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, - "read", nil, teamToEdit.UnitsMap) + "none", nil, teamToEdit.UnitsMap) // Edit team Description only editDescription = "second team" @@ -184,9 +184,9 @@ func TestAPITeam(t *testing.T) { apiTeam = api.Team{} DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "EditTeam2_DescOnly", &apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, - "read", nil, teamToEdit.UnitsMap) + "none", nil, teamToEdit.UnitsMap) checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, - "read", nil, teamToEdit.UnitsMap) + "none", nil, teamToEdit.UnitsMap) // Read team. teamRead = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) diff --git a/tests/integration/org_test.go b/tests/integration/org_test.go index b1376fe1bf..9a93455858 100644 --- a/tests/integration/org_test.go +++ b/tests/integration/org_test.go @@ -177,9 +177,9 @@ func TestOrgRestrictedUser(t *testing.T) { resp := adminSession.MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "CreateTeam_codereader", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - teamToCreate.Permission, teamToCreate.Units, nil) + "none", teamToCreate.Units, nil) checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - teamToCreate.Permission, teamToCreate.Units, nil) + "none", teamToCreate.Units, nil) // teamID := apiTeam.ID // Now we need to add the restricted user to the team From 90b509aafb5c0696cb1b459e9035ab3fc859f26a Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 8 Apr 2025 06:42:32 +0200 Subject: [PATCH 04/10] Update JS and PY deps (#34143) - Update selected dependencies. - Ran `make svg && git add --all`. - Tested mermaid and swagger. - Mark `fileicon` assets as generated so they don't spam the diff. - Webpack is not upgraded because it has some regression. --- .gitattributes | 1 + options/fileicon/material-icon-rules.json | 195 +++++- options/fileicon/material-icon-svgs.json | 44 +- package-lock.json | 783 +++++++++------------- package.json | 36 +- poetry.lock | 28 +- pyproject.toml | 2 +- 7 files changed, 585 insertions(+), 504 deletions(-) diff --git a/.gitattributes b/.gitattributes index 52695f70c2..e218bbe25d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,6 +4,7 @@ /assets/*.json linguist-generated /public/assets/img/svg/*.svg linguist-generated /templates/swagger/v1_json.tmpl linguist-generated +/options/fileicon/** linguist-generated /vendor/** -text -eol linguist-vendored /web_src/js/vendor/** -text -eol linguist-vendored Dockerfile.* linguist-language=Dockerfile diff --git a/options/fileicon/material-icon-rules.json b/options/fileicon/material-icon-rules.json index fd058c82dc..d097399252 100644 --- a/options/fileicon/material-icon-rules.json +++ b/options/fileicon/material-icon-rules.json @@ -1112,6 +1112,18 @@ ".routers": "folder-routes", "_routers": "folder-routes", "__routers__": "folder-routes", + "navigation": "folder-routes", + ".navigation": "folder-routes", + "_navigation": "folder-routes", + "__navigation__": "folder-routes", + "navigations": "folder-routes", + ".navigations": "folder-routes", + "_navigations": "folder-routes", + "__navigations__": "folder-routes", + "routing": "folder-routes", + ".routing": "folder-routes", + "_routing": "folder-routes", + "__routing__": "folder-routes", "ci": "folder-ci", ".ci": "folder-ci", "_ci": "folder-ci", @@ -2092,6 +2104,34 @@ ".DS_Store": "folder-macos", "_DS_Store": "folder-macos", "__DS_Store__": "folder-macos", + "iPhone": "folder-macos", + ".iPhone": "folder-macos", + "_iPhone": "folder-macos", + "__iPhone__": "folder-macos", + "iPad": "folder-macos", + ".iPad": "folder-macos", + "_iPad": "folder-macos", + "__iPad__": "folder-macos", + "iPod": "folder-macos", + ".iPod": "folder-macos", + "_iPod": "folder-macos", + "__iPod__": "folder-macos", + "macbook": "folder-macos", + ".macbook": "folder-macos", + "_macbook": "folder-macos", + "__macbook__": "folder-macos", + "macbook-air": "folder-macos", + ".macbook-air": "folder-macos", + "_macbook-air": "folder-macos", + "__macbook-air__": "folder-macos", + "macosx": "folder-macos", + ".macosx": "folder-macos", + "_macosx": "folder-macos", + "__macosx__": "folder-macos", + "apple": "folder-macos", + ".apple": "folder-macos", + "_apple": "folder-macos", + "__apple__": "folder-macos", "error": "folder-error", ".error": "folder-error", "_error": "folder-error", @@ -2428,6 +2468,34 @@ ".firebase": "folder-firebase", "_firebase": "folder-firebase", "__firebase__": "folder-firebase", + "firestore": "folder-firestore", + ".firestore": "folder-firestore", + "_firestore": "folder-firestore", + "__firestore__": "folder-firestore", + "cloud-firestore": "folder-firestore", + ".cloud-firestore": "folder-firestore", + "_cloud-firestore": "folder-firestore", + "__cloud-firestore__": "folder-firestore", + "firebase-firestore": "folder-firestore", + ".firebase-firestore": "folder-firestore", + "_firebase-firestore": "folder-firestore", + "__firebase-firestore__": "folder-firestore", + "cloud-functions": "folder-cloud-functions", + ".cloud-functions": "folder-cloud-functions", + "_cloud-functions": "folder-cloud-functions", + "__cloud-functions__": "folder-cloud-functions", + "cloudfunctions": "folder-cloud-functions", + ".cloudfunctions": "folder-cloud-functions", + "_cloudfunctions": "folder-cloud-functions", + "__cloudfunctions__": "folder-cloud-functions", + "firebase-cloud-functions": "folder-cloud-functions", + ".firebase-cloud-functions": "folder-cloud-functions", + "_firebase-cloud-functions": "folder-cloud-functions", + "__firebase-cloud-functions__": "folder-cloud-functions", + "firebase-cloudfunctions": "folder-cloud-functions", + ".firebase-cloudfunctions": "folder-cloud-functions", + "_firebase-cloudfunctions": "folder-cloud-functions", + "__firebase-cloudfunctions__": "folder-cloud-functions", "svelte": "folder-svelte", ".svelte": "folder-svelte", "_svelte": "folder-svelte", @@ -2975,6 +3043,14 @@ ".zeabur": "folder-zeabur", "_zeabur": "folder-zeabur", "__zeabur__": "folder-zeabur", + "kusto": "folder-kusto", + ".kusto": "folder-kusto", + "_kusto": "folder-kusto", + "__kusto__": "folder-kusto", + "kql": "folder-kusto", + ".kql": "folder-kusto", + "_kql": "folder-kusto", + "__kql__": "folder-kusto", "meta-inf": "folder-config", ".meta-inf": "folder-config", "_meta-inf": "folder-config", @@ -2990,7 +3066,19 @@ "ds_store": "folder-macos", ".ds_store": "folder-macos", "_ds_store": "folder-macos", - "__ds_store__": "folder-macos" + "__ds_store__": "folder-macos", + "iphone": "folder-macos", + ".iphone": "folder-macos", + "_iphone": "folder-macos", + "__iphone__": "folder-macos", + "ipad": "folder-macos", + ".ipad": "folder-macos", + "_ipad": "folder-macos", + "__ipad__": "folder-macos", + "ipod": "folder-macos", + ".ipod": "folder-macos", + "_ipod": "folder-macos", + "__ipod__": "folder-macos" }, "folderNamesExpanded": { "rust": "folder-rust-open", @@ -4105,6 +4193,18 @@ ".routers": "folder-routes-open", "_routers": "folder-routes-open", "__routers__": "folder-routes-open", + "navigation": "folder-routes-open", + ".navigation": "folder-routes-open", + "_navigation": "folder-routes-open", + "__navigation__": "folder-routes-open", + "navigations": "folder-routes-open", + ".navigations": "folder-routes-open", + "_navigations": "folder-routes-open", + "__navigations__": "folder-routes-open", + "routing": "folder-routes-open", + ".routing": "folder-routes-open", + "_routing": "folder-routes-open", + "__routing__": "folder-routes-open", "ci": "folder-ci-open", ".ci": "folder-ci-open", "_ci": "folder-ci-open", @@ -5085,6 +5185,34 @@ ".DS_Store": "folder-macos-open", "_DS_Store": "folder-macos-open", "__DS_Store__": "folder-macos-open", + "iPhone": "folder-macos-open", + ".iPhone": "folder-macos-open", + "_iPhone": "folder-macos-open", + "__iPhone__": "folder-macos-open", + "iPad": "folder-macos-open", + ".iPad": "folder-macos-open", + "_iPad": "folder-macos-open", + "__iPad__": "folder-macos-open", + "iPod": "folder-macos-open", + ".iPod": "folder-macos-open", + "_iPod": "folder-macos-open", + "__iPod__": "folder-macos-open", + "macbook": "folder-macos-open", + ".macbook": "folder-macos-open", + "_macbook": "folder-macos-open", + "__macbook__": "folder-macos-open", + "macbook-air": "folder-macos-open", + ".macbook-air": "folder-macos-open", + "_macbook-air": "folder-macos-open", + "__macbook-air__": "folder-macos-open", + "macosx": "folder-macos-open", + ".macosx": "folder-macos-open", + "_macosx": "folder-macos-open", + "__macosx__": "folder-macos-open", + "apple": "folder-macos-open", + ".apple": "folder-macos-open", + "_apple": "folder-macos-open", + "__apple__": "folder-macos-open", "error": "folder-error-open", ".error": "folder-error-open", "_error": "folder-error-open", @@ -5421,6 +5549,34 @@ ".firebase": "folder-firebase-open", "_firebase": "folder-firebase-open", "__firebase__": "folder-firebase-open", + "firestore": "folder-firestore-open", + ".firestore": "folder-firestore-open", + "_firestore": "folder-firestore-open", + "__firestore__": "folder-firestore-open", + "cloud-firestore": "folder-firestore-open", + ".cloud-firestore": "folder-firestore-open", + "_cloud-firestore": "folder-firestore-open", + "__cloud-firestore__": "folder-firestore-open", + "firebase-firestore": "folder-firestore-open", + ".firebase-firestore": "folder-firestore-open", + "_firebase-firestore": "folder-firestore-open", + "__firebase-firestore__": "folder-firestore-open", + "cloud-functions": "folder-cloud-functions-open", + ".cloud-functions": "folder-cloud-functions-open", + "_cloud-functions": "folder-cloud-functions-open", + "__cloud-functions__": "folder-cloud-functions-open", + "cloudfunctions": "folder-cloud-functions-open", + ".cloudfunctions": "folder-cloud-functions-open", + "_cloudfunctions": "folder-cloud-functions-open", + "__cloudfunctions__": "folder-cloud-functions-open", + "firebase-cloud-functions": "folder-cloud-functions-open", + ".firebase-cloud-functions": "folder-cloud-functions-open", + "_firebase-cloud-functions": "folder-cloud-functions-open", + "__firebase-cloud-functions__": "folder-cloud-functions-open", + "firebase-cloudfunctions": "folder-cloud-functions-open", + ".firebase-cloudfunctions": "folder-cloud-functions-open", + "_firebase-cloudfunctions": "folder-cloud-functions-open", + "__firebase-cloudfunctions__": "folder-cloud-functions-open", "svelte": "folder-svelte-open", ".svelte": "folder-svelte-open", "_svelte": "folder-svelte-open", @@ -5967,7 +6123,15 @@ "zeabur": "folder-zeabur-open", ".zeabur": "folder-zeabur-open", "_zeabur": "folder-zeabur-open", - "__zeabur__": "folder-zeabur-open" + "__zeabur__": "folder-zeabur-open", + "kusto": "folder-kusto-open", + ".kusto": "folder-kusto-open", + "_kusto": "folder-kusto-open", + "__kusto__": "folder-kusto-open", + "kql": "folder-kusto-open", + ".kql": "folder-kusto-open", + "_kql": "folder-kusto-open", + "__kql__": "folder-kusto-open" }, "rootFolderNames": {}, "rootFolderNamesExpanded": {}, @@ -6032,8 +6196,6 @@ "ico": "image", "tif": "image", "tiff": "image", - "psd": "image", - "psb": "image", "ami": "image", "apx": "image", "avif": "image", @@ -6128,6 +6290,10 @@ "routing.tsx": "routing", "routing.js": "routing", "routing.jsx": "routing", + "route.ts": "routing", + "route.tsx": "routing", + "route.js": "routing", + "route.jsx": "routing", "routes.ts": "routing", "routes.tsx": "routing", "routes.js": "routing", @@ -6288,6 +6454,7 @@ "py": "python", "pyc": "python-misc", "whl": "python-misc", + "egg": "python-misc", "url": "url", "sh": "console", "ksh": "console", @@ -6780,6 +6947,11 @@ "gltf": "3d", "glb": "3d", "svg": "svg", + "ai": "adobe-illustrator", + "ait": "adobe-illustrator", + "psd": "adobe-photoshop", + "psb": "adobe-photoshop", + "psdt": "adobe-photoshop", "svelte": "svelte", "svelte.js": "svelte_js", "svelte.ts": "svelte_ts", @@ -7031,6 +7203,7 @@ "dfxp": "subtitles", "vtt": "subtitles", "sub": "subtitles", + "ass": "subtitles", "beancount": "beancount", "bean": "beancount", "epub": "epub", @@ -7272,6 +7445,10 @@ "router.jsx": "routing", "router.ts": "routing", "router.tsx": "routing", + "route.js": "routing", + "route.jsx": "routing", + "route.ts": "routing", + "route.tsx": "routing", "routes.js": "routing", "routes.jsx": "routing", "routes.ts": "routing", @@ -7310,6 +7487,11 @@ ".pylintrc": "python-misc", "pyproject.toml": "python-misc", "py.typed": "python-misc", + ".coveragerc": "python-misc", + ".coverage": "python-misc", + ".scrapy": "python-misc", + "celerybeat-schedule": "python-misc", + "celerybeat.pid": "python-misc", "ruff.toml": "ruff", ".ruff.toml": "ruff", "uv.toml": "uv", @@ -9317,6 +9499,11 @@ "drone.yml": "drone_light", ".wakatime-project": "wakatime_light", "hcl": "hcl_light", + "ai": "adobe-illustrator_light", + "ait": "adobe-illustrator_light", + "psd": "adobe-photoshop_light", + "psb": "adobe-photoshop_light", + "psdt": "adobe-photoshop_light", "iuml": "uml_light", "pu": "uml_light", "puml": "uml_light", diff --git a/options/fileicon/material-icon-svgs.json b/options/fileicon/material-icon-svgs.json index dbda90665b..50bc2d2b8a 100644 --- a/options/fileicon/material-icon-svgs.json +++ b/options/fileicon/material-icon-svgs.json @@ -4,6 +4,10 @@ "abc": "", "actionscript": "", "ada": "", + "adobe-illustrator": "", + "adobe-illustrator_light": "", + "adobe-photoshop": "", + "adobe-photoshop_light": "", "adobe-swc": "", "adonis": "", "advpl-include.clone": "", @@ -163,8 +167,8 @@ "fastlane": "", "favicon": "", "figma": "", - "file": "", - "firebase": "", + "file": "", + "firebase": "", "flash": "", "flow": "", "folder-admin-open": "", @@ -223,6 +227,8 @@ "folder-client": "", "folder-cline-open": "", "folder-cline": "", + "folder-cloud-functions-open": "", + "folder-cloud-functions": "", "folder-cloudflare-open": "", "folder-cloudflare": "", "folder-cluster-open": "", @@ -273,8 +279,8 @@ "folder-delta": "", "folder-desktop-open": "", "folder-desktop": "", - "folder-development-open.clone": "", - "folder-development.clone": "", + "folder-development-open.clone": "", + "folder-development.clone": "", "folder-directive-open": "", "folder-directive": "", "folder-dist-open": "", @@ -309,8 +315,10 @@ "folder-fastlane": "", "folder-favicon-open": "", "folder-favicon": "", - "folder-firebase-open": "", - "folder-firebase": "", + "folder-firebase-open": "", + "folder-firebase": "", + "folder-firestore-open": "", + "folder-firestore": "", "folder-flow-open": "", "folder-flow": "", "folder-flutter-open": "", @@ -391,6 +399,8 @@ "folder-keys": "", "folder-kubernetes-open": "", "folder-kubernetes": "", + "folder-kusto-open": "", + "folder-kusto": "", "folder-layout-open": "", "folder-layout": "", "folder-lefthook-open": "", @@ -443,10 +453,10 @@ "folder-next": "", "folder-ngrx-actions-open.clone": "", "folder-ngrx-actions.clone": "", - "folder-ngrx-effects-open.clone": "", - "folder-ngrx-effects.clone": "", - "folder-ngrx-entities-open.clone": "", - "folder-ngrx-entities.clone": "", + "folder-ngrx-effects-open.clone": "", + "folder-ngrx-effects.clone": "", + "folder-ngrx-entities-open.clone": "", + "folder-ngrx-entities.clone": "", "folder-ngrx-reducer-open.clone": "", "folder-ngrx-reducer.clone": "", "folder-ngrx-selectors-open.clone": "", @@ -461,7 +471,7 @@ "folder-nuxt": "", "folder-obsidian-open": "", "folder-obsidian": "", - "folder-open": "", + "folder-open": "", "folder-other-open": "", "folder-other": "", "folder-packages-open": "", @@ -500,8 +510,8 @@ "folder-queue": "", "folder-react-components-open": "", "folder-react-components": "", - "folder-redux-actions-open.clone": "", - "folder-redux-actions.clone": "", + "folder-redux-actions-open.clone": "", + "folder-redux-actions.clone": "", "folder-redux-reducer-open": "", "folder-redux-reducer": "", "folder-redux-selector-open.clone": "", @@ -518,8 +528,8 @@ "folder-review": "", "folder-robot-open": "", "folder-robot": "", - "folder-root-open": "", - "folder-root": "", + "folder-root-open": "", + "folder-root": "", "folder-routes-open": "", "folder-routes": "", "folder-rules-open": "", @@ -644,7 +654,7 @@ "folder-yarn": "", "folder-zeabur-open": "", "folder-zeabur": "", - "folder": "", + "folder": "", "font": "", "forth": "", "fortran": "", @@ -738,7 +748,7 @@ "knip": "", "kotlin": "", "kubernetes": "", - "kusto": "", + "kusto": "", "label": "", "laravel": "", "lefthook": "", diff --git a/package-lock.json b/package-lock.json index c347cf5fd8..e516dcd91a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,12 +16,12 @@ "@primer/octicons": "19.15.1", "@silverwind/vue3-calendar-heatmap": "2.0.6", "add-asset-webpack-plugin": "3.0.0", - "ansi_up": "6.0.2", + "ansi_up": "6.0.5", "asciinema-player": "3.9.0", "chart.js": "4.4.8", "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.2.0", - "clippie": "4.1.5", + "clippie": "4.1.6", "cropperjs": "1.6.2", "css-loader": "7.1.2", "dayjs": "1.11.13", @@ -35,7 +35,7 @@ "jquery": "3.7.1", "katex": "0.16.21", "license-checker-webpack-plugin": "0.2.1", - "mermaid": "11.5.0", + "mermaid": "11.6.0", "mini-css-extract-plugin": "2.9.2", "minimatch": "10.0.1", "monaco-editor": "0.52.2", @@ -46,14 +46,14 @@ "postcss-loader": "8.1.1", "postcss-nesting": "13.0.1", "sortablejs": "1.15.6", - "swagger-ui-dist": "5.20.1", + "swagger-ui-dist": "5.20.7", "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", "tippy.js": "6.3.7", "toastify-js": "1.12.0", "tributejs": "5.1.3", - "typescript": "5.8.2", + "typescript": "5.8.3", "uint8-to-base64": "0.2.0", "vanilla-colorful": "0.7.2", "vue": "3.5.13", @@ -80,15 +80,15 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.3", - "@typescript-eslint/eslint-plugin": "8.26.1", - "@typescript-eslint/parser": "8.26.1", - "@vitejs/plugin-vue": "5.2.1", - "@vitest/eslint-plugin": "1.1.37", + "@typescript-eslint/eslint-plugin": "8.29.1", + "@typescript-eslint/parser": "8.29.1", + "@vitejs/plugin-vue": "5.2.3", + "@vitest/eslint-plugin": "1.1.39", "eslint": "8.57.0", - "eslint-import-resolver-typescript": "3.9.0", + "eslint-import-resolver-typescript": "4.3.2", "eslint-plugin-array-func": "4.0.0", "eslint-plugin-github": "5.0.2", - "eslint-plugin-import-x": "4.7.2", + "eslint-plugin-import-x": "4.10.2", "eslint-plugin-no-jquery": "3.1.1", "eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-playwright": "2.2.0", @@ -97,23 +97,23 @@ "eslint-plugin-unicorn": "56.0.1", "eslint-plugin-vue": "10.0.0", "eslint-plugin-vue-scoped-css": "2.9.0", - "eslint-plugin-wc": "2.2.1", + "eslint-plugin-wc": "3.0.0", "happy-dom": "17.4.4", "markdownlint-cli": "0.44.0", - "material-icon-theme": "5.20.0", + "material-icon-theme": "5.21.1", "nolyfill": "1.0.44", "postcss-html": "1.8.0", - "stylelint": "16.16.0", - "stylelint-config-recommended": "15.0.0", + "stylelint": "16.18.0", + "stylelint-config-recommended": "16.0.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.11", - "stylelint-define-config": "16.15.0", + "stylelint-define-config": "16.17.0", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.3.2", - "type-fest": "4.37.0", + "type-fest": "4.39.1", "updates": "16.4.2", "vite-string-plugin": "1.4.4", - "vitest": "3.0.8", + "vitest": "3.1.1", "vue-tsc": "2.2.8" }, "engines": { @@ -521,9 +521,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", - "integrity": "sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.0.tgz", + "integrity": "sha512-H+N/FqT07NmLmt6OFFtDfwe8PNygprzBikrEMyQfgqSmT0vzE515Pz7R8izwB9q/zsH/MA64AKoul3sA6/CzVg==", "dev": true, "license": "MIT", "optional": true, @@ -533,9 +533,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.0.tgz", + "integrity": "sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==", "dev": true, "license": "MIT", "optional": true, @@ -1540,24 +1540,24 @@ } }, "node_modules/@mermaid-js/parser": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.3.0.tgz", - "integrity": "sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.4.0.tgz", + "integrity": "sha512-wla8XOWvQAwuqy+gxiZqY+c7FokraOTHRWMsbB4AgRx9Sy7zKslNyejy7E+a77qHfey5GXw/ik3IXv/NHMJgaA==", "license": "MIT", "dependencies": { - "langium": "3.0.0" + "langium": "3.3.1" } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.7.tgz", - "integrity": "sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.8.tgz", + "integrity": "sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.3.1", - "@emnapi/runtime": "^1.3.1", + "@emnapi/core": "^1.4.0", + "@emnapi/runtime": "^1.4.0", "@tybys/wasm-util": "^0.9.0" } }, @@ -1596,16 +1596,6 @@ "node": ">= 8" } }, - "node_modules/@nolyfill/is-core-module": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", - "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.4.0" - } - }, "node_modules/@nolyfill/shared": { "version": "1.0.44", "resolved": "https://registry.npmjs.org/@nolyfill/shared/-/shared-1.0.44.tgz", @@ -1613,163 +1603,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@oxc-resolver/binding-darwin-arm64": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-5.0.0.tgz", - "integrity": "sha512-zwHAf+owoxSWTDD4dFuwW+FkpaDzbaL30H5Ltocb+RmLyg4WKuteusRLKh5Y8b/cyu7UzhxM0haIqQjyqA1iuA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oxc-resolver/binding-darwin-x64": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-5.0.0.tgz", - "integrity": "sha512-1lS3aBNVjVQKBvZdHm13+8tSjvu2Tl1Cv4FnUyMYxqx6+rsom2YaOylS5LhDUwfZu0zAgpLMwK6kGpF/UPncNg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oxc-resolver/binding-freebsd-x64": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-5.0.0.tgz", - "integrity": "sha512-q9sRd68wC1/AJ0eu6ClhxlklVfe8gH4wrUkSyEbIYTZ8zY5yjsLY3fpqqsaCvWJUx65nW+XtnAxCGCi5AXr1Mw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-5.0.0.tgz", - "integrity": "sha512-catYavWsvqViYnCveQjhrK6yVYDEPFvIOgGLxnz5r2dcgrjpmquzREoyss0L2QG/J5HTTbwqwZ1kk+g56hE/1A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-5.0.0.tgz", - "integrity": "sha512-l/0pWoQM5kVmJLg4frQ1mKZOXgi0ex/hzvFt8E4WK2ifXr5JgKFUokxsb/oat7f5YzdJJh5r9p+qS/t3dA26Aw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm64-musl": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-5.0.0.tgz", - "integrity": "sha512-bx0oz/oaAW4FGYqpIIxJCnmgb906YfMhTEWCJvYkxjpEI8VKLJEL3PQevYiqDq36SA0yRLJ/sQK2fqry8AFBfA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-x64-gnu": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-5.0.0.tgz", - "integrity": "sha512-4PH++qbSIhlRsFYdN1P9neDov4OGhTGo5nbQ1D7AL6gWFLo3gdZTc00FM2y8JjeTcPWEXkViZuwpuc0w5i6qHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-x64-musl": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-5.0.0.tgz", - "integrity": "sha512-mLfQFpX3/5y9oWi0b+9FbWDkL2hM0Y29653beCHiHxAdGyVgb2DsJbK74WkMTwtSz9by8vyBh8jGPZcg1yLZbQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-wasm32-wasi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-5.0.0.tgz", - "integrity": "sha512-uEhsAZSo65qsRi6+IfBTEUUFbjg7T2yruJeLYpFfEATpm3ory5Mgo5vx3L0c2/Cz1OUZXBgp3A8x6VMUB2jT2A==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-5.0.0.tgz", - "integrity": "sha512-8DbSso9Jp1ns8AYuZFXdRfAcdJrzZwkFm/RjPuvAPTENsm685dosBF8G6gTHQlHvULnk6o3sa9ygZaTGC/UoEw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@oxc-resolver/binding-win32-x64-msvc": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-5.0.0.tgz", - "integrity": "sha512-ylppfPEg63NuRXOPNsXFlgyl37JrtRn0QMO26X3K3Ytp5HtLrMreQMGVtgr30e1l2YmAWqhvmKlCryOqzGPD/g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3314,17 +3147,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz", - "integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", + "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/type-utils": "8.26.1", - "@typescript-eslint/utils": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/type-utils": "8.29.1", + "@typescript-eslint/utils": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -3344,16 +3177,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.1.tgz", - "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", + "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/typescript-estree": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4" }, "engines": { @@ -3369,14 +3202,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz", - "integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", + "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1" + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3387,14 +3220,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz", - "integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", + "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.26.1", - "@typescript-eslint/utils": "8.26.1", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/utils": "8.29.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -3411,9 +3244,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz", - "integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", + "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", "dev": true, "license": "MIT", "engines": { @@ -3425,14 +3258,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz", - "integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", + "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3468,16 +3301,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz", - "integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", + "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/typescript-estree": "8.26.1" + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3492,13 +3325,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz", - "integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", + "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/types": "8.29.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -3516,10 +3349,10 @@ "dev": true, "license": "ISC" }, - "node_modules/@unrs/rspack-resolver-binding-darwin-arm64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-darwin-arm64/-/rspack-resolver-binding-darwin-arm64-1.1.0.tgz", - "integrity": "sha512-otdWnJrycP8Ow0rbiKKjhrW7PPeHPoIglYjBruqh75fEwQGV2EmA9oZMgD4YA6g+/hGzD0mXI26YnWL0G0SkTA==", + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.4.1.tgz", + "integrity": "sha512-8Tv+Bsd0BjGwfEedIyor4inw8atppRxM5BdUnIt+3mAm/QXUm7Dw74CHnXpfZKXkp07EXJGiA8hStqCINAWhdw==", "cpu": [ "arm64" ], @@ -3530,10 +3363,10 @@ "darwin" ] }, - "node_modules/@unrs/rspack-resolver-binding-darwin-x64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-darwin-x64/-/rspack-resolver-binding-darwin-x64-1.1.0.tgz", - "integrity": "sha512-MqHyrtIw2ra0KZlniDITROq6rEiMsBnaJtQDYLNDv/y+pvPSXdB3VveZCwSldXJ9TrST2b3NnIbmehljjsMVhg==", + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.4.1.tgz", + "integrity": "sha512-X8c3PhWziEMKAzZz+YAYWfwawi5AEgzy/hmfizAB4C70gMHLKmInJcp1270yYAOs7z07YVFI220pp50z24Jk3A==", "cpu": [ "x64" ], @@ -3544,10 +3377,10 @@ "darwin" ] }, - "node_modules/@unrs/rspack-resolver-binding-freebsd-x64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-freebsd-x64/-/rspack-resolver-binding-freebsd-x64-1.1.0.tgz", - "integrity": "sha512-bopyOqmtWn8np1d4iN90PE1tYHopdWwei7mK8/8mf4qhc99f7WRNXtWa1MoL5sjN3DWef3jvr0+MGnMS9MTfJA==", + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.4.1.tgz", + "integrity": "sha512-UUr/nREy1UdtxXQnmLaaTXFGOcGxPwNIzeJdb3KXai3TKtC1UgNOB9s8KOA4TaxOUBR/qVgL5BvBwmUjD5yuVA==", "cpu": [ "x64" ], @@ -3558,10 +3391,10 @@ "freebsd" ] }, - "node_modules/@unrs/rspack-resolver-binding-linux-arm-gnueabihf": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm-gnueabihf/-/rspack-resolver-binding-linux-arm-gnueabihf-1.1.0.tgz", - "integrity": "sha512-XldXRkQurDBXCiCuIaWcqOX6UtvjFW8O3CH/kFEZxNJISOAt+ztgyRQRxYhf+T1p18R4boripKmWKEE0uBCiYw==", + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.4.1.tgz", + "integrity": "sha512-e3pII53dEeS8inkX6A1ad2UXE0nuoWCqik4kOxaDnls0uJUq0ntdj5d9IYd+bv5TDwf9DSge/xPOvCmRYH+Tsw==", "cpu": [ "arm" ], @@ -3572,10 +3405,24 @@ "linux" ] }, - "node_modules/@unrs/rspack-resolver-binding-linux-arm64-gnu": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm64-gnu/-/rspack-resolver-binding-linux-arm64-gnu-1.1.0.tgz", - "integrity": "sha512-8zubI4MY3whPfLNHEiJ0TeSC5eSmNVWTEGAeMGALCUQtVD9TyZTd6wGwWrQVRN7ESIapWUSChkPLr+Bi13d9sQ==", + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.4.1.tgz", + "integrity": "sha512-e/AKKd9gR+HNmVyDEPI/PIz2t0DrA3cyonHNhHVjrkxe8pMCiYiqhtn1+h+yIpHUtUlM6Y1FNIdivFa+r7wrEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.4.1.tgz", + "integrity": "sha512-vtIu34luF1jRktlHtiwm2mjuE8oJCsFiFr8hT5+tFQdqFKjPhbJXn83LswKsOhy0GxAEevpXDI4xxEwkjuXIPA==", "cpu": [ "arm64" ], @@ -3586,10 +3433,10 @@ "linux" ] }, - "node_modules/@unrs/rspack-resolver-binding-linux-arm64-musl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm64-musl/-/rspack-resolver-binding-linux-arm64-musl-1.1.0.tgz", - "integrity": "sha512-+GAyOhl8KPqJsILpHTB/mMc4hfOwI4INed8VAZnSvdaL0ec3Sz/6UXEeTtucW1fWhwaP3lVlpjv2xuRhOCjehA==", + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.4.1.tgz", + "integrity": "sha512-H3PaOuGyhFXiyJd+09uPhGl4gocmhyi1BRzvsP8Lv5AQO3p3/ZY7WjV4t2NkBksm9tMjf3YbOVHyPWi2eWsNYw==", "cpu": [ "arm64" ], @@ -3600,10 +3447,38 @@ "linux" ] }, - "node_modules/@unrs/rspack-resolver-binding-linux-x64-gnu": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-x64-gnu/-/rspack-resolver-binding-linux-x64-gnu-1.1.0.tgz", - "integrity": "sha512-0zoy6UwRFoto5boJKGjgDpA+4kv+G1kysgrAe0KVefJXOnDNJlfgcV7mOV2O9J+FqtIQsXvzmOJxDB9e1Hhbzw==", + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.4.1.tgz", + "integrity": "sha512-4+GmJcaaFntCi1S01YByqp8wLMjV/FyQyHVGm0vedIhL1Vfx7uHkz/sZmKsidRwokBGuxi92GFmSzqT2O8KcNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.4.1.tgz", + "integrity": "sha512-6RDQVCmtFYTlhy89D5ixTqo9bTQqFhvNN0Ey1wJs5r+01Dq15gPHRXv2jF2bQATtMrOfYwv+R2ZR9ew1N1N3YQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.4.1.tgz", + "integrity": "sha512-XpU9uzIkD86+19NjCXxlVPISMUrVXsXo5htxtuG+uJ59p5JauSRZsIxQxzzfKzkxEjdvANPM/lS1HFoX6A6QeA==", "cpu": [ "x64" ], @@ -3614,10 +3489,10 @@ "linux" ] }, - "node_modules/@unrs/rspack-resolver-binding-linux-x64-musl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-x64-musl/-/rspack-resolver-binding-linux-x64-musl-1.1.0.tgz", - "integrity": "sha512-XjC+aZKi+X+ma5e6yaVTvojK0v0kxfikbP1dB0klx80NjCW9KRrldiZxAo7ME8Tb4a7Fz0J6PDOVzd9tFYwkQQ==", + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.4.1.tgz", + "integrity": "sha512-3CDjG/spbTKCSHl66QP2ekHSD+H34i7utuDIM5gzoNBcZ1gTO0Op09Wx5cikXnhORRf9+HyDWzm37vU1PLSM1A==", "cpu": [ "x64" ], @@ -3628,10 +3503,10 @@ "linux" ] }, - "node_modules/@unrs/rspack-resolver-binding-wasm32-wasi": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-wasm32-wasi/-/rspack-resolver-binding-wasm32-wasi-1.1.0.tgz", - "integrity": "sha512-eVBK4z9VN3vahAh2+bQBl+vs9JgWEF1xeccilDcerGNkmlFHB1My5++sbeZzou+DExkioVAdfK+uVyVnHS4k7Q==", + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.4.1.tgz", + "integrity": "sha512-50tYhvbCTnuzMn7vmP8IV2UKF7ITo1oihygEYq9wW2DUb/Y+QMqBHJUSCABRngATjZ4shOK6f2+s0gQX6ElENQ==", "cpu": [ "wasm32" ], @@ -3639,16 +3514,16 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.7" + "@napi-rs/wasm-runtime": "^0.2.8" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@unrs/rspack-resolver-binding-win32-arm64-msvc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-win32-arm64-msvc/-/rspack-resolver-binding-win32-arm64-msvc-1.1.0.tgz", - "integrity": "sha512-ktm/CnSKOt/Wwdi2SBECLPJ+gL5oaw8LDHG4IfOQO5oT6qlIP0DaOUPrTf8g/WTlLnSp4TryDBM0B/gGla3LEw==", + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.4.1.tgz", + "integrity": "sha512-KyJiIne/AqV4IW0wyQO34wSMuJwy3VxVQOfIXIPyQ/Up6y/zi2P/WwXb78gHsLiGRUqCA9LOoCX+6dQZde0g1g==", "cpu": [ "arm64" ], @@ -3659,10 +3534,24 @@ "win32" ] }, - "node_modules/@unrs/rspack-resolver-binding-win32-x64-msvc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-win32-x64-msvc/-/rspack-resolver-binding-win32-x64-msvc-1.1.0.tgz", - "integrity": "sha512-cdMid8RdR6XaQ5uAudzdu9Ydl3HbYjiwpsh+X01APmTZG2ph7OeaRTozW4F8ScUHkPHfrYedv9McICbHxgBvXQ==", + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.4.1.tgz", + "integrity": "sha512-y2NUD7pygrBolN2NoXUrwVqBpKPhF8DiSNE5oB5/iFO49r2DpoYqdj5HPb3F42fPBH5qNqj6Zg63+xCEzAD2hw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.4.1.tgz", + "integrity": "sha512-hVXaObGI2lGFmrtT77KSbPQ3I+zk9IU500wobjk0+oX59vg/0VqAzABNtt3YSQYgXTC2a/LYxekLfND/wlt0yQ==", "cpu": [ "x64" ], @@ -3674,9 +3563,9 @@ ] }, "node_modules/@vitejs/plugin-vue": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", - "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz", + "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==", "dev": true, "license": "MIT", "engines": { @@ -3688,9 +3577,9 @@ } }, "node_modules/@vitest/eslint-plugin": { - "version": "1.1.37", - "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.37.tgz", - "integrity": "sha512-cnlBV8zr0oaBu1Vk6ruqWzpPzFCtwY0yuwUQpNIeFOUl3UhXVwNUoOYfWkZzeToGuNBaXvIsr6/RgHrXiHXqXA==", + "version": "1.1.39", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.39.tgz", + "integrity": "sha512-l5/MUFCYI8nxwr62JHlWwXfeQNS8E7xy71lSLGQ3CrjGjBdWLs1Rtee+BvYwy2m4YVPwYqUwdcAIOaZOwPUpfg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3709,14 +3598,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.8.tgz", - "integrity": "sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.1.tgz", + "integrity": "sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.8", - "@vitest/utils": "3.0.8", + "@vitest/spy": "3.1.1", + "@vitest/utils": "3.1.1", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -3725,13 +3614,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.8.tgz", - "integrity": "sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.1.tgz", + "integrity": "sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.8", + "@vitest/spy": "3.1.1", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -3752,9 +3641,9 @@ } }, "node_modules/@vitest/mocker/node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, "license": "MIT" }, @@ -3779,9 +3668,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", - "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.1.tgz", + "integrity": "sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==", "dev": true, "license": "MIT", "dependencies": { @@ -3792,13 +3681,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.8.tgz", - "integrity": "sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.1.tgz", + "integrity": "sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.0.8", + "@vitest/utils": "3.1.1", "pathe": "^2.0.3" }, "funding": { @@ -3806,13 +3695,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.8.tgz", - "integrity": "sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.1.tgz", + "integrity": "sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.8", + "@vitest/pretty-format": "3.1.1", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -3831,9 +3720,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.8.tgz", - "integrity": "sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.1.tgz", + "integrity": "sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3844,13 +3733,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.8.tgz", - "integrity": "sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.1.tgz", + "integrity": "sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.8", + "@vitest/pretty-format": "3.1.1", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -4378,9 +4267,9 @@ "license": "MIT" }, "node_modules/ansi_up": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.2.tgz", - "integrity": "sha512-3G3vKvl1ilEp7J1u6BmULpMA0xVoW/f4Ekqhl8RTrJrhEBkonKn5k3bUc5Xt+qDayA6iDX0jyUh3AbZjB/l0tw==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.5.tgz", + "integrity": "sha512-bo4K8S5usgFivfgvgQozTC2EfusPf76o7w0LUVdAOkpISvVmQqtwCdF5c6okokrgIN13KhFIVB/0BhnNXueQeA==", "license": "MIT", "engines": { "node": "*" @@ -5103,9 +4992,9 @@ } }, "node_modules/clippie": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/clippie/-/clippie-4.1.5.tgz", - "integrity": "sha512-ZNGL7+p8HZWM2G8fecb3N7izoagXGktTg7nSYxzBID4OAtOBU7SUFvI9EL/0IZpy9VkU8AY6Zp8AvaH4/Xj7pA==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/clippie/-/clippie-4.1.6.tgz", + "integrity": "sha512-1M3xZRNWcVwRkh3i2XcVYFjVtfC6FCLmIpk5s54uT3+jkBa25KRE3dB0Fgkt/YLoeR7AABWkVTlb0WziQYGgaw==", "license": "BSD-2-Clause" }, "node_modules/cliui": { @@ -6586,25 +6475,24 @@ } }, "node_modules/eslint-import-resolver-typescript": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.9.0.tgz", - "integrity": "sha512-EUcFmaz0zAa6P2C9jAb5XDymRld8S6TURvWcIW7y+czOW+K8hrjgQgbhBsNE0J/dDZ6HLfcn70LqnIil9W+ICw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.3.2.tgz", + "integrity": "sha512-T2LqBXj87ndEC9t1LrDiPkzalSFzD4rrXr6BTzGdgMx1jdQM4T972guQvg7Ih+LNO51GURXI/qMHS5GF3h1ilw==", "dev": true, "license": "ISC", "dependencies": { - "@nolyfill/is-core-module": "1.0.39", - "debug": "^4.3.7", + "debug": "^4.4.0", "get-tsconfig": "^4.10.0", - "is-bun-module": "^1.0.2", - "oxc-resolver": "^5.0.0", + "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", - "tinyglobby": "^0.2.12" + "tinyglobby": "^0.2.12", + "unrs-resolver": "^1.4.1" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^16.17.0 || >=18.6.0" }, "funding": { - "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + "url": "https://opencollective.com/eslint-import-resolver-typescript" }, "peerDependencies": { "eslint": "*", @@ -6797,24 +6685,25 @@ } }, "node_modules/eslint-plugin-import-x": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.7.2.tgz", - "integrity": "sha512-+GpGWKbQMK3izrwU4XoRGdAJHwvxuboiNusqU25nNXlRsmnxj8B5niQRuFK1aHEvcbIKE6D9ZfwjsLmBQbnJmw==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.10.2.tgz", + "integrity": "sha512-jO3Y6+zBUyTX5MVbbLSzoz6fe65t+WEBaXStRLM4EBhZWbuSwAH3cLwARtM0Yp4zRtZGp9sL2zzK7G9JkHR8LA==", "dev": true, "license": "MIT", "dependencies": { + "@pkgr/core": "^0.2.1", "@types/doctrine": "^0.0.9", - "@typescript-eslint/utils": "^8.26.1", + "@typescript-eslint/utils": "^8.29.0", "debug": "^4.4.0", "doctrine": "^3.0.0", "eslint-import-resolver-node": "^0.3.9", "get-tsconfig": "^4.10.0", "is-glob": "^4.0.3", - "minimatch": "^10.0.1", - "rspack-resolver": "^1.1.0", + "minimatch": "^9.0.3 || ^10.0.1", "semver": "^7.7.1", "stable-hash": "^0.0.5", - "tslib": "^2.8.1" + "tslib": "^2.8.1", + "unrs-resolver": "^1.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6823,6 +6712,19 @@ "eslint": "^8.57.0 || ^9.0.0" } }, + "node_modules/eslint-plugin-import-x/node_modules/@pkgr/core": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.1.tgz", + "integrity": "sha512-VzgHzGblFmUeBmmrk55zPyrQIArQN4vujc9shWytaPdB3P7qhi0cpaiKIr7tlCmFv2lYUwnLospIqjL9ZSAhhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -7176,14 +7078,14 @@ } }, "node_modules/eslint-plugin-wc": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.2.1.tgz", - "integrity": "sha512-KstLqGmyQz088DvFlDYHg0sHih+w2QeulreCi1D1ftr357klO2zqHdG/bbnNMmuQdVFDuNkopNIyNhmG0XCT/g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-3.0.0.tgz", + "integrity": "sha512-TVbwa4ytaKoUGCCAG14+FPDF3v4nGoPcEOd4kjN2MLZ2NTo1lHUs7HsVaiwC0ReKQVivJ7+v6d7u0GYu1c6GJQ==", "dev": true, "license": "MIT", "dependencies": { "is-valid-element-name": "^1.0.0", - "js-levenshtein-esm": "^1.2.0" + "js-levenshtein-esm": "^2.0.0" }, "peerDependencies": { "eslint": ">=8.40.0" @@ -8275,13 +8177,13 @@ } }, "node_modules/is-bun-module": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.3.0.tgz", - "integrity": "sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.6.3" + "semver": "^7.7.1" } }, "node_modules/is-core-module": { @@ -8520,9 +8422,9 @@ "license": "MIT" }, "node_modules/js-levenshtein-esm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz", - "integrity": "sha512-fzreKVq1eD7eGcQr7MtRpQH94f8gIfhdrc7yeih38xh684TNMK9v5aAu2wxfIRMk/GpAJRrzcirMAPIaSDaByQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-2.0.0.tgz", + "integrity": "sha512-1n4LEPOL4wRXY8rOQcuA7Iuaphe5xCMayvufCzlLAi+hRsnBRDbSS6XPuV58CBVJxj5D9ApFLyjQ7KzFToyHBw==", "dev": true, "license": "MIT" }, @@ -8751,9 +8653,9 @@ "license": "MIT" }, "node_modules/langium": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.0.0.tgz", - "integrity": "sha512-+Ez9EoiByeoTu/2BXmEaZ06iPNXM6thWJp02KfBO/raSMyCJ4jw7AkWWa+zBCTm0+Tw1Fj9FOxdqSskyN5nAwg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", + "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", "license": "MIT", "dependencies": { "chevrotain": "~11.0.3", @@ -9244,9 +9146,9 @@ } }, "node_modules/material-icon-theme": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/material-icon-theme/-/material-icon-theme-5.20.0.tgz", - "integrity": "sha512-EAz5I2O7Hq6G8Rv0JdO6NXL+jK/mvDppcVUVbsUMpSqSmFczNdaR5WJ3lOiRz4HNBlEN2i2sVSfuqI5iNQfGLg==", + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/material-icon-theme/-/material-icon-theme-5.21.1.tgz", + "integrity": "sha512-7gWH20MC3rvf1fBXwTpeMEAJqfuhXaciY2IN/hb74hR0XU/TnicoggblFoWtZvt0HrCzxyqX5P+u60BHWXEEHw==", "dev": true, "license": "MIT", "dependencies": { @@ -9316,14 +9218,14 @@ } }, "node_modules/mermaid": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.5.0.tgz", - "integrity": "sha512-IYhyukID3zzDj1EihKiN1lp+PXNImoJ3Iyz73qeDAgnus4BNGsJV1n471P4PyeGxPVONerZxignwGxGTSwZnlg==", + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.6.0.tgz", + "integrity": "sha512-PE8hGUy1LDlWIHWBP05SFdqUHGmRcCcK4IzpOKPE35eOw+G9zZgcnMpyunJVUEOgb//KBORPjysKndw8bFLuRg==", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.0.4", "@iconify/utils": "^2.1.33", - "@mermaid-js/parser": "^0.3.0", + "@mermaid-js/parser": "^0.4.0", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", @@ -10327,29 +10229,6 @@ "node": ">= 0.8.0" } }, - "node_modules/oxc-resolver": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-5.0.0.tgz", - "integrity": "sha512-66fopyAqCN8Mx4tzNiBXWbk8asCSuxUWN62gwTc3yfRs7JfWhX/eVJCf+fUrfbNOdQVOWn+o8pAKllp76ysMXA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - }, - "optionalDependencies": { - "@oxc-resolver/binding-darwin-arm64": "5.0.0", - "@oxc-resolver/binding-darwin-x64": "5.0.0", - "@oxc-resolver/binding-freebsd-x64": "5.0.0", - "@oxc-resolver/binding-linux-arm-gnueabihf": "5.0.0", - "@oxc-resolver/binding-linux-arm64-gnu": "5.0.0", - "@oxc-resolver/binding-linux-arm64-musl": "5.0.0", - "@oxc-resolver/binding-linux-x64-gnu": "5.0.0", - "@oxc-resolver/binding-linux-x64-musl": "5.0.0", - "@oxc-resolver/binding-wasm32-wasi": "5.0.0", - "@oxc-resolver/binding-win32-arm64-msvc": "5.0.0", - "@oxc-resolver/binding-win32-x64-msvc": "5.0.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -11652,29 +11531,6 @@ "points-on-path": "^0.2.1" } }, - "node_modules/rspack-resolver": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/rspack-resolver/-/rspack-resolver-1.1.0.tgz", - "integrity": "sha512-pJfTX5KuwbJc4agd2AQ9sMwrBxMAGkLt4/HHw5+L06WuzxjsEjg3oDKdbfn43QGq0Stw8wQ7VpZjWA/T03L0Pg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/JounQin" - }, - "optionalDependencies": { - "@unrs/rspack-resolver-binding-darwin-arm64": "1.1.0", - "@unrs/rspack-resolver-binding-darwin-x64": "1.1.0", - "@unrs/rspack-resolver-binding-freebsd-x64": "1.1.0", - "@unrs/rspack-resolver-binding-linux-arm-gnueabihf": "1.1.0", - "@unrs/rspack-resolver-binding-linux-arm64-gnu": "1.1.0", - "@unrs/rspack-resolver-binding-linux-arm64-musl": "1.1.0", - "@unrs/rspack-resolver-binding-linux-x64-gnu": "1.1.0", - "@unrs/rspack-resolver-binding-linux-x64-musl": "1.1.0", - "@unrs/rspack-resolver-binding-wasm32-wasi": "1.1.0", - "@unrs/rspack-resolver-binding-win32-arm64-msvc": "1.1.0", - "@unrs/rspack-resolver-binding-win32-x64-msvc": "1.1.0" - } - }, "node_modules/run-con": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz", @@ -12258,9 +12114,9 @@ "license": "ISC" }, "node_modules/stylelint": { - "version": "16.16.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.16.0.tgz", - "integrity": "sha512-40X5UOb/0CEFnZVEHyN260HlSSUxPES+arrUphOumGWgXERHfwCD0kNBVILgQSij8iliYVwlc0V7M5bcLP9vPg==", + "version": "16.18.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.18.0.tgz", + "integrity": "sha512-OXb68qzesv7J70BSbFwfK3yTVLEVXiQ/ro6wUE4UrSbKCMjLLA02S8Qq3LC01DxKyVjk7z8xh35aB4JzO3/sNA==", "dev": true, "funding": [ { @@ -12321,9 +12177,9 @@ } }, "node_modules/stylelint-config-recommended": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-15.0.0.tgz", - "integrity": "sha512-9LejMFsat7L+NXttdHdTq94byn25TD+82bzGRiV1Pgasl99pWnwipXS5DguTpp3nP1XjvLXVnEJIuYBfsRjRkA==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-16.0.0.tgz", + "integrity": "sha512-4RSmPjQegF34wNcK1e1O3Uz91HN8P1aFdFzio90wNK9mjgAI19u5vsU868cVZboKzCaa5XbpvtTzAAGQAxpcXA==", "dev": true, "funding": [ { @@ -12340,7 +12196,7 @@ "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^16.13.0" + "stylelint": "^16.16.0" } }, "node_modules/stylelint-declaration-block-no-ignored-properties": { @@ -12370,9 +12226,9 @@ } }, "node_modules/stylelint-define-config": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/stylelint-define-config/-/stylelint-define-config-16.15.0.tgz", - "integrity": "sha512-nzHX9ZpI/k4A7izGYPS79xLAf2HyGvYkk/UXMgsQ7ZQEvkOZpQt4Aca4Qn5DYqNmWnqNlW5E3wK+qUmdR3vdxg==", + "version": "16.17.0", + "resolved": "https://registry.npmjs.org/stylelint-define-config/-/stylelint-define-config-16.17.0.tgz", + "integrity": "sha512-HToy83mn05K4g72GjXfyw3PhFV9QZKVJWt6m+w7+XNqAOsegiBI+sYXq8sg0R+cedsi2KVyfmEEpKO550yMmrQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12777,9 +12633,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.1.tgz", - "integrity": "sha512-qBPCis2w8nP4US7SvUxdJD3OwKcqiWeZmjN2VWhq2v+ESZEXOP/7n4DeiOiiZcGYTKMHAHUUrroHaTsjUWTEGw==", + "version": "5.20.7", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.7.tgz", + "integrity": "sha512-gLpb1wrWinUwMFKfSvDYsIlCyGQSryftzi6uWc9Qo98zO3mFT6oHOqmDUu5OoahvepuS6HGTe/3MsGUCVtpLig==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -13181,9 +13037,9 @@ } }, "node_modules/type-fest": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.37.0.tgz", - "integrity": "sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==", + "version": "4.39.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.1.tgz", + "integrity": "sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -13194,9 +13050,9 @@ } }, "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -13247,6 +13103,33 @@ "node": ">= 10.0.0" } }, + "node_modules/unrs-resolver": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.4.1.tgz", + "integrity": "sha512-MhPB3wBI5BR8TGieTb08XuYlE8oFVEXdSAgat3psdlRyejl8ojQ8iqPcjh094qCZ1r+TnkxzP6BeCd/umfHckQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/JounQin" + }, + "optionalDependencies": { + "@unrs/resolver-binding-darwin-arm64": "1.4.1", + "@unrs/resolver-binding-darwin-x64": "1.4.1", + "@unrs/resolver-binding-freebsd-x64": "1.4.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.4.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.4.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.4.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.4.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.4.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.4.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.4.1", + "@unrs/resolver-binding-linux-x64-musl": "1.4.1", + "@unrs/resolver-binding-wasm32-wasi": "1.4.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.4.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.4.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.4.1" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -13436,9 +13319,9 @@ } }, "node_modules/vite-node": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.8.tgz", - "integrity": "sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.1.tgz", + "integrity": "sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==", "dev": true, "license": "MIT", "dependencies": { @@ -13527,31 +13410,31 @@ } }, "node_modules/vitest": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.8.tgz", - "integrity": "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.1.tgz", + "integrity": "sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.0.8", - "@vitest/mocker": "3.0.8", - "@vitest/pretty-format": "^3.0.8", - "@vitest/runner": "3.0.8", - "@vitest/snapshot": "3.0.8", - "@vitest/spy": "3.0.8", - "@vitest/utils": "3.0.8", + "@vitest/expect": "3.1.1", + "@vitest/mocker": "3.1.1", + "@vitest/pretty-format": "^3.1.1", + "@vitest/runner": "3.1.1", + "@vitest/snapshot": "3.1.1", + "@vitest/spy": "3.1.1", + "@vitest/utils": "3.1.1", "chai": "^5.2.0", "debug": "^4.4.0", - "expect-type": "^1.1.0", + "expect-type": "^1.2.0", "magic-string": "^0.30.17", "pathe": "^2.0.3", - "std-env": "^3.8.0", + "std-env": "^3.8.1", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.8", + "vite-node": "3.1.1", "why-is-node-running": "^2.3.0" }, "bin": { @@ -13567,8 +13450,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.0.8", - "@vitest/ui": "3.0.8", + "@vitest/browser": "3.1.1", + "@vitest/ui": "3.1.1", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index ef1a132994..040d6b24fd 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,12 @@ "@primer/octicons": "19.15.1", "@silverwind/vue3-calendar-heatmap": "2.0.6", "add-asset-webpack-plugin": "3.0.0", - "ansi_up": "6.0.2", + "ansi_up": "6.0.5", "asciinema-player": "3.9.0", "chart.js": "4.4.8", "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.2.0", - "clippie": "4.1.5", + "clippie": "4.1.6", "cropperjs": "1.6.2", "css-loader": "7.1.2", "dayjs": "1.11.13", @@ -34,7 +34,7 @@ "jquery": "3.7.1", "katex": "0.16.21", "license-checker-webpack-plugin": "0.2.1", - "mermaid": "11.5.0", + "mermaid": "11.6.0", "mini-css-extract-plugin": "2.9.2", "minimatch": "10.0.1", "monaco-editor": "0.52.2", @@ -45,14 +45,14 @@ "postcss-loader": "8.1.1", "postcss-nesting": "13.0.1", "sortablejs": "1.15.6", - "swagger-ui-dist": "5.20.1", + "swagger-ui-dist": "5.20.7", "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", "tippy.js": "6.3.7", "toastify-js": "1.12.0", "tributejs": "5.1.3", - "typescript": "5.8.2", + "typescript": "5.8.3", "uint8-to-base64": "0.2.0", "vanilla-colorful": "0.7.2", "vue": "3.5.13", @@ -79,15 +79,15 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.3", - "@typescript-eslint/eslint-plugin": "8.26.1", - "@typescript-eslint/parser": "8.26.1", - "@vitejs/plugin-vue": "5.2.1", - "@vitest/eslint-plugin": "1.1.37", + "@typescript-eslint/eslint-plugin": "8.29.1", + "@typescript-eslint/parser": "8.29.1", + "@vitejs/plugin-vue": "5.2.3", + "@vitest/eslint-plugin": "1.1.39", "eslint": "8.57.0", - "eslint-import-resolver-typescript": "3.9.0", + "eslint-import-resolver-typescript": "4.3.2", "eslint-plugin-array-func": "4.0.0", "eslint-plugin-github": "5.0.2", - "eslint-plugin-import-x": "4.7.2", + "eslint-plugin-import-x": "4.10.2", "eslint-plugin-no-jquery": "3.1.1", "eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-playwright": "2.2.0", @@ -96,23 +96,23 @@ "eslint-plugin-unicorn": "56.0.1", "eslint-plugin-vue": "10.0.0", "eslint-plugin-vue-scoped-css": "2.9.0", - "eslint-plugin-wc": "2.2.1", + "eslint-plugin-wc": "3.0.0", "happy-dom": "17.4.4", "markdownlint-cli": "0.44.0", - "material-icon-theme": "5.20.0", + "material-icon-theme": "5.21.1", "nolyfill": "1.0.44", "postcss-html": "1.8.0", - "stylelint": "16.16.0", - "stylelint-config-recommended": "15.0.0", + "stylelint": "16.18.0", + "stylelint-config-recommended": "16.0.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.11", - "stylelint-define-config": "16.15.0", + "stylelint-define-config": "16.17.0", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.3.2", - "type-fest": "4.37.0", + "type-fest": "4.39.1", "updates": "16.4.2", "vite-string-plugin": "1.4.4", - "vitest": "3.0.8", + "vitest": "3.1.1", "vue-tsc": "2.2.8" }, "browserslist": [ diff --git a/poetry.lock b/poetry.lock index ca7ae78cb8..b532bf0a3a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "click" @@ -119,18 +119,18 @@ six = ">=1.13.0" [[package]] name = "json5" -version = "0.10.0" +version = "0.12.0" description = "A Python implementation of the JSON5 data format." optional = false python-versions = ">=3.8.0" groups = ["dev"] files = [ - {file = "json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa"}, - {file = "json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559"}, + {file = "json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db"}, + {file = "json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a"}, ] [package.extras] -dev = ["build (==1.2.2.post1)", "coverage (==7.5.3)", "mypy (==1.13.0)", "pip (==24.3.1)", "pylint (==3.2.3)", "ruff (==0.7.3)", "twine (==5.1.1)", "uv (==0.5.1)"] +dev = ["build (==1.2.2.post1)", "coverage (==7.5.4) ; python_version < \"3.9\"", "coverage (==7.8.0) ; python_version >= \"3.9\"", "mypy (==1.14.1) ; python_version < \"3.9\"", "mypy (==1.15.0) ; python_version >= \"3.9\"", "pip (==25.0.1)", "pylint (==3.2.7) ; python_version < \"3.9\"", "pylint (==3.3.6) ; python_version >= \"3.9\"", "ruff (==0.11.2)", "twine (==6.1.0)", "uv (==0.6.11)"] [[package]] name = "pathspec" @@ -330,7 +330,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -390,27 +390,27 @@ telegram = ["requests"] [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.1" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69"}, + {file = "typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff"}, ] [[package]] name = "yamllint" -version = "1.36.1" +version = "1.37.0" description = "A linter for YAML files." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "yamllint-1.36.1-py3-none-any.whl", hash = "sha256:3e2ccd47ea12449837adf6b2c56fd9e31172ce42bc1470380806be461f25df66"}, - {file = "yamllint-1.36.1.tar.gz", hash = "sha256:a287689daaafc301a80549b2d0170452ebfdcabd826e3fe3b4c66e322d4851fa"}, + {file = "yamllint-1.37.0-py3-none-any.whl", hash = "sha256:c03ab4e79ab4af964c8eb16ac9746880fc76a3bb0ffb14925b9a55220ae7dda0"}, + {file = "yamllint-1.37.0.tar.gz", hash = "sha256:ead81921d4d87216b2528b7a055664708f9fb8267beb0c427cb706ac6ab93580"}, ] [package.dependencies] @@ -423,4 +423,4 @@ dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "d48a461813418f7b803a7f94713d93076a009a96554e0f4c707be0cc2a95b563" +content-hash = "d8ed1d7f88ff4be57be2c599b9d906dee25d0f6f6e46fdc9051c892c4e812a1c" diff --git a/pyproject.toml b/pyproject.toml index a2aedfe148..4eed5f0e74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ python = "^3.10" [tool.poetry.group.dev.dependencies] djlint = "1.36.4" -yamllint = "1.36.1" +yamllint = "1.37.0" [tool.djlint] profile="golang" From a100ac3306000885b01abcc08d1672efa880daec Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 7 Apr 2025 22:12:54 -0700 Subject: [PATCH 05/10] Rework create/fork/adopt/generate repository to make sure resources will be cleanup once failed (#31035) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #28144 To make the resources will be cleanup once failed. All repository operations now follow a consistent pattern: - 1. Create a database record for the repository with the status being_migrated. - 2. Register a deferred cleanup function to delete the repository and its related data if the operation fails. - 3. Perform the actual Git and database operations step by step. - 4. Upon successful completion, update the repository’s status to ready. The adopt operation is a special case — if it fails, the repository on disk should not be deleted. --- models/git/branch.go | 5 + models/repo/release.go | 5 + modules/repository/init.go | 27 ---- services/repository/adopt.go | 90 ++++++------ services/repository/adopt_test.go | 31 ++++- services/repository/create.go | 211 +++++++++++++++-------------- services/repository/create_test.go | 57 ++++++++ services/repository/delete.go | 21 ++- services/repository/fork.go | 180 ++++++++++++------------ services/repository/fork_test.go | 43 ++++++ services/repository/generate.go | 52 ------- services/repository/migrate.go | 10 +- services/repository/template.go | 169 +++++++++++++++-------- tests/integration/repo_test.go | 51 +++++++ 14 files changed, 562 insertions(+), 390 deletions(-) create mode 100644 services/repository/create_test.go diff --git a/models/git/branch.go b/models/git/branch.go index 9ac6c45578..beeb7c0689 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -235,6 +235,11 @@ func GetDeletedBranchByID(ctx context.Context, repoID, branchID int64) (*Branch, return &branch, nil } +func DeleteRepoBranches(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Where("repo_id=?", repoID).Delete(new(Branch)) + return err +} + func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64) error { return db.WithTx(ctx, func(ctx context.Context) error { branches := make([]*Branch, 0, len(branchIDs)) diff --git a/models/repo/release.go b/models/repo/release.go index 1c2e4a48e3..663d310bc0 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -558,3 +558,8 @@ func FindTagsByCommitIDs(ctx context.Context, repoID int64, commitIDs ...string) } return res, nil } + +func DeleteRepoReleases(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(new(Release)) + return err +} diff --git a/modules/repository/init.go b/modules/repository/init.go index ace21254ba..e6331966ba 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -11,11 +11,7 @@ import ( "strings" issues_model "code.gitea.io/gitea/models/issues" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/label" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -121,29 +117,6 @@ func LoadRepoConfig() error { return nil } -func CheckInitRepository(ctx context.Context, repo *repo_model.Repository) (err error) { - // Somehow the directory could exist. - isExist, err := gitrepo.IsRepositoryExist(ctx, repo) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) - return err - } - if isExist { - return repo_model.ErrRepoFilesAlreadyExist{ - Uname: repo.OwnerName, - Name: repo.Name, - } - } - - // Init git bare new repository. - if err = git.InitRepository(ctx, repo.RepoPath(), true, repo.ObjectFormatName); err != nil { - return fmt.Errorf("git.InitRepository: %w", err) - } else if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil { - return fmt.Errorf("createDelegateHooks: %w", err) - } - return nil -} - // InitializeLabels adds a label set to a repository using a template func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error { list, err := LoadTemplateLabelsByDisplayName(labelTemplate) diff --git a/services/repository/adopt.go b/services/repository/adopt.go index b7321156d9..6953970e28 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -16,7 +16,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" @@ -28,6 +27,18 @@ import ( "github.com/gobwas/glob" ) +func deleteFailedAdoptRepository(repoID int64) error { + return db.WithTx(db.DefaultContext, func(ctx context.Context) error { + if err := deleteDBRepository(ctx, repoID); err != nil { + return fmt.Errorf("deleteDBRepository: %w", err) + } + if err := git_model.DeleteRepoBranches(ctx, repoID); err != nil { + return fmt.Errorf("deleteRepoBranches: %w", err) + } + return repo_model.DeleteRepoReleases(ctx, repoID) + }) +} + // AdoptRepository adopts pre-existing repository files for the user/organization. func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { if !doer.IsAdmin && !u.CanCreateRepo() { @@ -48,58 +59,51 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR IsPrivate: opts.IsPrivate, IsFsckEnabled: !opts.IsMirror, CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, - Status: opts.Status, + Status: repo_model.RepositoryBeingMigrated, IsEmpty: !opts.AutoInit, } - if err := db.WithTx(ctx, func(ctx context.Context) error { - isExist, err := gitrepo.IsRepositoryExist(ctx, repo) + // 1 - create the repository database operations first + err := db.WithTx(ctx, func(ctx context.Context) error { + return createRepositoryInDB(ctx, doer, u, repo, false) + }) + if err != nil { + return nil, err + } + + // last - clean up if something goes wrong + // WARNING: Don't override all later err with local variables + defer func() { if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) - return err - } - if !isExist { - return repo_model.ErrRepoNotExist{ - OwnerName: u.Name, - Name: repo.Name, + // we can not use the ctx because it maybe canceled or timeout + if errDel := deleteFailedAdoptRepository(repo.ID); errDel != nil { + log.Error("Failed to delete repository %s that could not be adopted: %v", repo.FullName(), errDel) } } + }() - if err := CreateRepositoryByExample(ctx, doer, u, repo, true, false); err != nil { - return err - } - - // Re-fetch the repository from database before updating it (else it would - // override changes that were done earlier with sql) - if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil { - return fmt.Errorf("getRepositoryByID: %w", err) - } - return nil - }); err != nil { - return nil, err + // Re-fetch the repository from database before updating it (else it would + // override changes that were done earlier with sql) + if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil { + return nil, fmt.Errorf("getRepositoryByID: %w", err) } - if err := func() error { - if err := adoptRepository(ctx, repo, opts.DefaultBranch); err != nil { - return fmt.Errorf("adoptRepository: %w", err) - } - - if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { - return fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil { - log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) - return fmt.Errorf("CreateRepository(git update-server-info): %w", err) - } - return nil - }(); err != nil { - if errDel := DeleteRepository(ctx, doer, repo, false /* no notify */); errDel != nil { - log.Error("Failed to delete repository %s that could not be adopted: %v", repo.FullName(), errDel) - } - return nil, err + // 2 - adopt the repository from disk + if err = adoptRepository(ctx, repo, opts.DefaultBranch); err != nil { + return nil, fmt.Errorf("adoptRepository: %w", err) } + + // 3 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, repo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + + // 4 - update repository status + repo.Status = repo_model.RepositoryReady + if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { + return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) + } + notify_service.AdoptRepository(ctx, doer, u, repo) return repo, nil diff --git a/services/repository/adopt_test.go b/services/repository/adopt_test.go index 294185ea1f..6e1dc417b3 100644 --- a/services/repository/adopt_test.go +++ b/services/repository/adopt_test.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) @@ -89,10 +90,36 @@ func TestListUnadoptedRepositories_ListOptions(t *testing.T) { func TestAdoptRepository(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.NoError(t, unittest.SyncDirs(filepath.Join(setting.RepoRootPath, "user2", "repo1.git"), filepath.Join(setting.RepoRootPath, "user2", "test-adopt.git"))) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - _, err := AdoptRepository(db.DefaultContext, user2, user2, CreateRepoOptions{Name: "test-adopt"}) + + // a successful adopt + destDir := filepath.Join(setting.RepoRootPath, user2.Name, "test-adopt.git") + assert.NoError(t, unittest.SyncDirs(filepath.Join(setting.RepoRootPath, user2.Name, "repo1.git"), destDir)) + + adoptedRepo, err := AdoptRepository(db.DefaultContext, user2, user2, CreateRepoOptions{Name: "test-adopt"}) assert.NoError(t, err) repoTestAdopt := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "test-adopt"}) assert.Equal(t, "sha1", repoTestAdopt.ObjectFormatName) + + // just delete the adopted repo's db records + err = deleteFailedAdoptRepository(adoptedRepo.ID) + assert.NoError(t, err) + + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: "test-adopt"}) + + // a failed adopt because some mock data + // remove the hooks directory and create a file so that we cannot create the hooks successfully + _ = os.RemoveAll(filepath.Join(destDir, "hooks", "update.d")) + assert.NoError(t, os.WriteFile(filepath.Join(destDir, "hooks", "update.d"), []byte("tests"), os.ModePerm)) + + adoptedRepo, err = AdoptRepository(db.DefaultContext, user2, user2, CreateRepoOptions{Name: "test-adopt"}) + assert.Error(t, err) + assert.Nil(t, adoptedRepo) + + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: "test-adopt"}) + + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, "test-adopt")) + assert.NoError(t, err) + assert.True(t, exist) // the repository should be still in the disk } diff --git a/services/repository/create.go b/services/repository/create.go index af4e897151..22dfce91f0 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" + system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" @@ -140,8 +141,11 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir // InitRepository initializes README and .gitignore if needed. func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Repository, opts CreateRepoOptions) (err error) { - if err = repo_module.CheckInitRepository(ctx, repo); err != nil { - return err + // Init git bare new repository. + if err = git.InitRepository(ctx, repo.RepoPath(), true, repo.ObjectFormatName); err != nil { + return fmt.Errorf("git.InitRepository: %w", err) + } else if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil { + return fmt.Errorf("createDelegateHooks: %w", err) } // Initialize repository according to user's choice. @@ -244,100 +248,93 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt ObjectFormatName: opts.ObjectFormatName, } - var rollbackRepo *repo_model.Repository - - if err := db.WithTx(ctx, func(ctx context.Context) error { - if err := CreateRepositoryByExample(ctx, doer, u, repo, false, false); err != nil { - return err - } - - // No need for init mirror. - if opts.IsMirror { - return nil - } - - isExist, err := gitrepo.IsRepositoryExist(ctx, repo) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) - return err - } - if isExist { - // repo already exists - We have two or three options. - // 1. We fail stating that the directory exists - // 2. We create the db repository to go with this data and adopt the git repo - // 3. We delete it and start afresh - // - // Previously Gitea would just delete and start afresh - this was naughty. - // So we will now fail and delegate to other functionality to adopt or delete - log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName()) - return repo_model.ErrRepoFilesAlreadyExist{ - Uname: u.Name, - Name: repo.Name, - } - } - - if err = initRepository(ctx, doer, repo, opts); err != nil { - if err2 := gitrepo.DeleteRepository(ctx, repo); err2 != nil { - log.Error("initRepository: %v", err) - return fmt.Errorf( - "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) - } - return fmt.Errorf("initRepository: %w", err) - } - - // Initialize Issue Labels if selected - if len(opts.IssueLabels) > 0 { - if err = repo_module.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { - rollbackRepo = repo - rollbackRepo.OwnerID = u.ID - return fmt.Errorf("InitializeLabels: %w", err) - } - } - - if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { - return fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil { - log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) - rollbackRepo = repo - rollbackRepo.OwnerID = u.ID - return fmt.Errorf("CreateRepository(git update-server-info): %w", err) - } - - // update licenses - var licenses []string - if len(opts.License) > 0 { - licenses = append(licenses, opts.License) - - stdout, _, err := git.NewCommand("rev-parse", "HEAD").RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) - if err != nil { - log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err) - rollbackRepo = repo - rollbackRepo.OwnerID = u.ID - return fmt.Errorf("CreateRepository(git rev-parse HEAD): %w", err) - } - if err := repo_model.UpdateRepoLicenses(ctx, repo, stdout, licenses); err != nil { - return err - } - } - return nil - }); err != nil { - if rollbackRepo != nil { - if errDelete := DeleteRepositoryDirectly(ctx, doer, rollbackRepo.ID); errDelete != nil { - log.Error("Rollback deleteRepository: %v", errDelete) - } - } + needsUpdateStatus := opts.Status != repo_model.RepositoryReady + // 1 - create the repository database operations first + err := db.WithTx(ctx, func(ctx context.Context) error { + return createRepositoryInDB(ctx, doer, u, repo, false) + }) + if err != nil { return nil, err } + // last - clean up if something goes wrong + // WARNING: Don't override all later err with local variables + defer func() { + if err != nil { + // we can not use the ctx because it maybe canceled or timeout + cleanupRepository(doer, repo.ID) + } + }() + + // No need for init mirror. + if opts.IsMirror { + return repo, nil + } + + // 2 - check whether the repository with the same storage exists + var isExist bool + isExist, err = gitrepo.IsRepositoryExist(ctx, repo) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) + return nil, err + } + if isExist { + log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName()) + // Don't return directly, we need err in defer to cleanupRepository + err = repo_model.ErrRepoFilesAlreadyExist{ + Uname: repo.OwnerName, + Name: repo.Name, + } + return nil, err + } + + // 3 - init git repository in storage + if err = initRepository(ctx, doer, repo, opts); err != nil { + return nil, fmt.Errorf("initRepository: %w", err) + } + + // 4 - Initialize Issue Labels if selected + if len(opts.IssueLabels) > 0 { + if err = repo_module.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { + return nil, fmt.Errorf("InitializeLabels: %w", err) + } + } + + // 5 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, repo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + + // 6 - update licenses + var licenses []string + if len(opts.License) > 0 { + licenses = append(licenses, opts.License) + + var stdout string + stdout, _, err = git.NewCommand("rev-parse", "HEAD").RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) + if err != nil { + log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err) + return nil, fmt.Errorf("CreateRepository(git rev-parse HEAD): %w", err) + } + if err = repo_model.UpdateRepoLicenses(ctx, repo, stdout, licenses); err != nil { + return nil, err + } + } + + // 7 - update repository status to be ready + if needsUpdateStatus { + repo.Status = repo_model.RepositoryReady + if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { + return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) + } + } + return repo, nil } -// CreateRepositoryByExample creates a repository for the user/organization. -func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt, isFork bool) (err error) { +// createRepositoryInDB creates a repository for the user/organization. +func createRepositoryInDB(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, isFork bool) (err error) { if err = repo_model.IsUsableRepoName(repo.Name); err != nil { return err } @@ -352,19 +349,6 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re } } - isExist, err := gitrepo.IsRepositoryExist(ctx, repo) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) - return err - } - if !overwriteOrAdopt && isExist { - log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName()) - return repo_model.ErrRepoFilesAlreadyExist{ - Uname: u.Name, - Name: repo.Name, - } - } - if err = db.Insert(ctx, repo); err != nil { return err } @@ -473,3 +457,26 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re return nil } + +func cleanupRepository(doer *user_model.User, repoID int64) { + if errDelete := DeleteRepositoryDirectly(db.DefaultContext, doer, repoID); errDelete != nil { + log.Error("cleanupRepository failed: %v", errDelete) + // add system notice + if err := system_model.CreateRepositoryNotice("DeleteRepositoryDirectly failed when cleanup repository: %v", errDelete); err != nil { + log.Error("CreateRepositoryNotice: %v", err) + } + } +} + +func updateGitRepoAfterCreate(ctx context.Context, repo *repo_model.Repository) error { + if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { + return fmt.Errorf("checkDaemonExportOK: %w", err) + } + + if stdout, _, err := git.NewCommand("update-server-info"). + RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil { + log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) + return fmt.Errorf("CreateRepository(git update-server-info): %w", err) + } + return nil +} diff --git a/services/repository/create_test.go b/services/repository/create_test.go new file mode 100644 index 0000000000..9ecf404e8b --- /dev/null +++ b/services/repository/create_test.go @@ -0,0 +1,57 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "os" + "testing" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/util" + + "github.com/stretchr/testify/assert" +) + +func TestCreateRepositoryDirectly(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // a successful creating repository + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + createdRepo, err := CreateRepositoryDirectly(git.DefaultContext, user2, user2, CreateRepoOptions{ + Name: "created-repo", + }) + assert.NoError(t, err) + assert.NotNil(t, createdRepo) + + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, createdRepo.Name)) + assert.NoError(t, err) + assert.True(t, exist) + + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: createdRepo.Name}) + + err = DeleteRepositoryDirectly(db.DefaultContext, user2, createdRepo.ID) + assert.NoError(t, err) + + // a failed creating because some mock data + // create the repository directory so that the creation will fail after database record created. + assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, createdRepo.Name), os.ModePerm)) + + createdRepo2, err := CreateRepositoryDirectly(db.DefaultContext, user2, user2, CreateRepoOptions{ + Name: "created-repo", + }) + assert.Nil(t, createdRepo2) + assert.Error(t, err) + + // assert the cleanup is successful + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: createdRepo.Name}) + + exist, err = util.IsExist(repo_model.RepoPath(user2.Name, createdRepo.Name)) + assert.NoError(t, err) + assert.False(t, exist) +} diff --git a/services/repository/delete.go b/services/repository/delete.go index ff74a20817..cf960af8cf 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -32,6 +32,19 @@ import ( "xorm.io/builder" ) +func deleteDBRepository(ctx context.Context, repoID int64) error { + if cnt, err := db.GetEngine(ctx).ID(repoID).Delete(&repo_model.Repository{}); err != nil { + return err + } else if cnt != 1 { + return repo_model.ErrRepoNotExist{ + ID: repoID, + OwnerName: "", + Name: "", + } + } + return nil +} + // DeleteRepository deletes a repository for a user or organization. // make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock) func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID int64, ignoreOrgTeams ...bool) error { @@ -82,14 +95,8 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID } needRewriteKeysFile := deleted > 0 - if cnt, err := sess.ID(repoID).Delete(&repo_model.Repository{}); err != nil { + if err := deleteDBRepository(ctx, repoID); err != nil { return err - } else if cnt != 1 { - return repo_model.ErrRepoNotExist{ - ID: repoID, - OwnerName: "", - Name: "", - } } if org != nil && org.IsOrganization() { diff --git a/services/repository/fork.go b/services/repository/fork.go index 5b1ba7a418..daf6913510 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -100,114 +100,106 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork IsFork: true, ForkID: opts.BaseRepo.ID, ObjectFormatName: opts.BaseRepo.ObjectFormatName, + Status: repo_model.RepositoryBeingMigrated, } - oldRepoPath := opts.BaseRepo.RepoPath() - - needsRollback := false - rollbackFn := func() { - if !needsRollback { - return - } - - if exists, _ := gitrepo.IsRepositoryExist(ctx, repo); !exists { - return - } - - // As the transaction will be failed and hence database changes will be destroyed we only need - // to delete the related repository on the filesystem - if errDelete := gitrepo.DeleteRepository(ctx, repo); errDelete != nil { - log.Error("Failed to remove fork repo") - } - } - - needsRollbackInPanic := true - defer func() { - panicErr := recover() - if panicErr == nil { - return - } - - if needsRollbackInPanic { - rollbackFn() - } - panic(panicErr) - }() - - err = db.WithTx(ctx, func(txCtx context.Context) error { - if err = CreateRepositoryByExample(txCtx, doer, owner, repo, false, true); err != nil { + // 1 - Create the repository in the database + err = db.WithTx(ctx, func(ctx context.Context) error { + if err = createRepositoryInDB(ctx, doer, owner, repo, true); err != nil { return err } - - if err = repo_model.IncrementRepoForkNum(txCtx, opts.BaseRepo.ID); err != nil { + if err = repo_model.IncrementRepoForkNum(ctx, opts.BaseRepo.ID); err != nil { return err } // copy lfs files failure should not be ignored - if err = git_model.CopyLFS(txCtx, repo, opts.BaseRepo); err != nil { - return err - } - - needsRollback = true - - cloneCmd := git.NewCommand("clone", "--bare") - if opts.SingleBranch != "" { - cloneCmd.AddArguments("--single-branch", "--branch").AddDynamicArguments(opts.SingleBranch) - } - if stdout, _, err := cloneCmd.AddDynamicArguments(oldRepoPath, repo.RepoPath()). - RunStdBytes(txCtx, &git.RunOpts{Timeout: 10 * time.Minute}); err != nil { - log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err) - return fmt.Errorf("git clone: %w", err) - } - - if err := repo_module.CheckDaemonExportOK(txCtx, repo); err != nil { - return fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(txCtx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil { - log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err) - return fmt.Errorf("git update-server-info: %w", err) - } - - if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil { - return fmt.Errorf("createDelegateHooks: %w", err) - } - - gitRepo, err := gitrepo.OpenRepository(txCtx, repo) - if err != nil { - return fmt.Errorf("OpenRepository: %w", err) - } - defer gitRepo.Close() - - _, err = repo_module.SyncRepoBranchesWithRepo(txCtx, repo, gitRepo, doer.ID) - return err + return git_model.CopyLFS(ctx, repo, opts.BaseRepo) }) - needsRollbackInPanic = false if err != nil { - rollbackFn() return nil, err } - // even if below operations failed, it could be ignored. And they will be retried - if err := repo_module.UpdateRepoSize(ctx, repo); err != nil { - log.Error("Failed to update size for repository: %v", err) - } - if err := repo_model.CopyLanguageStat(ctx, opts.BaseRepo, repo); err != nil { - log.Error("Copy language stat from oldRepo failed: %v", err) - } - if err := repo_model.CopyLicense(ctx, opts.BaseRepo, repo); err != nil { - return nil, err - } - - gitRepo, err := gitrepo.OpenRepository(ctx, repo) - if err != nil { - log.Error("Open created git repository failed: %v", err) - } else { - defer gitRepo.Close() - if err := repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { - log.Error("Sync releases from git tags failed: %v", err) + // last - clean up if something goes wrong + // WARNING: Don't override all later err with local variables + defer func() { + if err != nil { + // we can not use the ctx because it maybe canceled or timeout + cleanupRepository(doer, repo.ID) } + }() + + // 2 - check whether the repository with the same storage exists + var isExist bool + isExist, err = gitrepo.IsRepositoryExist(ctx, repo) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) + return nil, err + } + if isExist { + log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName()) + // Don't return directly, we need err in defer to cleanupRepository + err = repo_model.ErrRepoFilesAlreadyExist{ + Uname: repo.OwnerName, + Name: repo.Name, + } + return nil, err + } + + // 3 - Clone the repository + cloneCmd := git.NewCommand("clone", "--bare") + if opts.SingleBranch != "" { + cloneCmd.AddArguments("--single-branch", "--branch").AddDynamicArguments(opts.SingleBranch) + } + var stdout []byte + if stdout, _, err = cloneCmd.AddDynamicArguments(opts.BaseRepo.RepoPath(), repo.RepoPath()). + RunStdBytes(ctx, &git.RunOpts{Timeout: 10 * time.Minute}); err != nil { + log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err) + return nil, fmt.Errorf("git clone: %w", err) + } + + // 4 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, repo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + + // 5 - Create hooks + if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil { + return nil, fmt.Errorf("createDelegateHooks: %w", err) + } + + // 6 - Sync the repository branches and tags + var gitRepo *git.Repository + gitRepo, err = gitrepo.OpenRepository(ctx, repo) + if err != nil { + return nil, fmt.Errorf("OpenRepository: %w", err) + } + defer gitRepo.Close() + + if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doer.ID); err != nil { + return nil, fmt.Errorf("SyncRepoBranchesWithRepo: %w", err) + } + if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { + return nil, fmt.Errorf("Sync releases from git tags failed: %v", err) + } + + // 7 - Update the repository + // even if below operations failed, it could be ignored. And they will be retried + if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { + log.Error("Failed to update size for repository: %v", err) + err = nil + } + if err = repo_model.CopyLanguageStat(ctx, opts.BaseRepo, repo); err != nil { + log.Error("Copy language stat from oldRepo failed: %v", err) + err = nil + } + if err = repo_model.CopyLicense(ctx, opts.BaseRepo, repo); err != nil { + return nil, err + } + + // 8 - update repository status to be ready + repo.Status = repo_model.RepositoryReady + if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { + return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) } notify_service.ForkRepository(ctx, doer, opts.BaseRepo, repo) diff --git a/services/repository/fork_test.go b/services/repository/fork_test.go index 452798b25b..9edc0aa39f 100644 --- a/services/repository/fork_test.go +++ b/services/repository/fork_test.go @@ -4,13 +4,16 @@ package repository import ( + "os" "testing" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) @@ -46,3 +49,43 @@ func TestForkRepository(t *testing.T) { assert.Nil(t, fork2) assert.True(t, repo_model.IsErrReachLimitOfRepo(err)) } + +func TestForkRepositoryCleanup(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // a successful fork + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) + + fork, err := ForkRepository(git.DefaultContext, user2, user2, ForkRepoOptions{ + BaseRepo: repo10, + Name: "test", + }) + assert.NoError(t, err) + assert.NotNil(t, fork) + + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, "test")) + assert.NoError(t, err) + assert.True(t, exist) + + err = DeleteRepositoryDirectly(db.DefaultContext, user2, fork.ID) + assert.NoError(t, err) + + // a failed creating because some mock data + // create the repository directory so that the creation will fail after database record created. + assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, "test"), os.ModePerm)) + + fork2, err := ForkRepository(db.DefaultContext, user2, user2, ForkRepoOptions{ + BaseRepo: repo10, + Name: "test", + }) + assert.Nil(t, fork2) + assert.Error(t, err) + + // assert the cleanup is successful + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: "test"}) + + exist, err = util.IsExist(repo_model.RepoPath(user2.Name, "test")) + assert.NoError(t, err) + assert.False(t, exist) +} diff --git a/services/repository/generate.go b/services/repository/generate.go index 9d2bbb1f7f..1c4d3b7b63 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -17,7 +17,6 @@ import ( git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" @@ -328,57 +327,6 @@ func (gro GenerateRepoOptions) IsValid() bool { gro.IssueLabels || gro.ProtectedBranch // or other items as they are added } -// generateRepository generates a repository from a template -func generateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) { - generateRepo := &repo_model.Repository{ - OwnerID: owner.ID, - Owner: owner, - OwnerName: owner.Name, - Name: opts.Name, - LowerName: strings.ToLower(opts.Name), - Description: opts.Description, - DefaultBranch: opts.DefaultBranch, - IsPrivate: opts.Private, - IsEmpty: !opts.GitContent || templateRepo.IsEmpty, - IsFsckEnabled: templateRepo.IsFsckEnabled, - TemplateID: templateRepo.ID, - TrustModel: templateRepo.TrustModel, - ObjectFormatName: templateRepo.ObjectFormatName, - } - - if err = CreateRepositoryByExample(ctx, doer, owner, generateRepo, false, false); err != nil { - return nil, err - } - - isExist, err := gitrepo.IsRepositoryExist(ctx, generateRepo) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", generateRepo.FullName(), err) - return nil, err - } - if isExist { - return nil, repo_model.ErrRepoFilesAlreadyExist{ - Uname: generateRepo.OwnerName, - Name: generateRepo.Name, - } - } - - if err = repo_module.CheckInitRepository(ctx, generateRepo); err != nil { - return generateRepo, err - } - - if err = repo_module.CheckDaemonExportOK(ctx, generateRepo); err != nil { - return generateRepo, fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(ctx, &git.RunOpts{Dir: generateRepo.RepoPath()}); err != nil { - log.Error("GenerateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", generateRepo, stdout, err) - return generateRepo, fmt.Errorf("error in GenerateRepository(git update-server-info): %w", err) - } - - return generateRepo, nil -} - var fileNameSanitizeRegexp = regexp.MustCompile(`(?i)\.\.|[<>:\"/\\|?*\x{0000}-\x{001F}]|^(con|prn|aux|nul|com\d|lpt\d)$`) // Sanitize user input to valid OS filenames diff --git a/services/repository/migrate.go b/services/repository/migrate.go index 2a17e9acd9..003be1a9ab 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -118,14 +118,8 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, repo.Owner = u } - if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { - return repo, fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(ctx, &git.RunOpts{Dir: repoPath}); err != nil { - log.Error("MigrateRepositoryGitData(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) - return repo, fmt.Errorf("error in MigrateRepositoryGitData(git update-server-info): %w", err) + if err := updateGitRepoAfterCreate(ctx, repo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) } gitRepo, err := git.OpenRepository(ctx, repoPath) diff --git a/services/repository/template.go b/services/repository/template.go index 36a680c8e2..b3f34e4858 100644 --- a/services/repository/template.go +++ b/services/repository/template.go @@ -5,12 +5,17 @@ package repository import ( "context" + "fmt" + "strings" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/log" notify_service "code.gitea.io/gitea/services/notify" ) @@ -69,66 +74,120 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ } } - var generateRepo *repo_model.Repository - if err = db.WithTx(ctx, func(ctx context.Context) error { - generateRepo, err = generateRepository(ctx, doer, owner, templateRepo, opts) - if err != nil { - return err - } + generateRepo := &repo_model.Repository{ + OwnerID: owner.ID, + Owner: owner, + OwnerName: owner.Name, + Name: opts.Name, + LowerName: strings.ToLower(opts.Name), + Description: opts.Description, + DefaultBranch: opts.DefaultBranch, + IsPrivate: opts.Private, + IsEmpty: !opts.GitContent || templateRepo.IsEmpty, + IsFsckEnabled: templateRepo.IsFsckEnabled, + TemplateID: templateRepo.ID, + TrustModel: templateRepo.TrustModel, + ObjectFormatName: templateRepo.ObjectFormatName, + Status: repo_model.RepositoryBeingMigrated, + } - // Git Content - if opts.GitContent && !templateRepo.IsEmpty { - if err = GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Topics - if opts.Topics { - if err = repo_model.GenerateTopics(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Git Hooks - if opts.GitHooks { - if err = GenerateGitHooks(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Webhooks - if opts.Webhooks { - if err = GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Avatar - if opts.Avatar && len(templateRepo.Avatar) > 0 { - if err = generateAvatar(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Issue Labels - if opts.IssueLabels { - if err = GenerateIssueLabels(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - if opts.ProtectedBranch { - if err = GenerateProtectedBranch(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - return nil + // 1 - Create the repository in the database + if err := db.WithTx(ctx, func(ctx context.Context) error { + return createRepositoryInDB(ctx, doer, owner, generateRepo, false) }); err != nil { return nil, err } + // last - clean up the repository if something goes wrong + defer func() { + if err != nil { + // we can not use the ctx because it maybe canceled or timeout + cleanupRepository(doer, generateRepo.ID) + } + }() + + // 2 - check whether the repository with the same storage exists + isExist, err := gitrepo.IsRepositoryExist(ctx, generateRepo) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", generateRepo.FullName(), err) + return nil, err + } + if isExist { + // Don't return directly, we need err in defer to cleanupRepository + err = repo_model.ErrRepoFilesAlreadyExist{ + Uname: generateRepo.OwnerName, + Name: generateRepo.Name, + } + return nil, err + } + + // 3 -Init git bare new repository. + if err = git.InitRepository(ctx, generateRepo.RepoPath(), true, generateRepo.ObjectFormatName); err != nil { + return nil, fmt.Errorf("git.InitRepository: %w", err) + } else if err = gitrepo.CreateDelegateHooks(ctx, generateRepo); err != nil { + return nil, fmt.Errorf("createDelegateHooks: %w", err) + } + + // 4 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, generateRepo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + + // 5 - generate the repository contents according to the template + // Git Content + if opts.GitContent && !templateRepo.IsEmpty { + if err = GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Topics + if opts.Topics { + if err = repo_model.GenerateTopics(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Git Hooks + if opts.GitHooks { + if err = GenerateGitHooks(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Webhooks + if opts.Webhooks { + if err = GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Avatar + if opts.Avatar && len(templateRepo.Avatar) > 0 { + if err = generateAvatar(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Issue Labels + if opts.IssueLabels { + if err = GenerateIssueLabels(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + if opts.ProtectedBranch { + if err = GenerateProtectedBranch(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // 6 - update repository status to be ready + generateRepo.Status = repo_model.RepositoryReady + if err = repo_model.UpdateRepositoryCols(ctx, generateRepo, "status"); err != nil { + return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) + } + notify_service.CreateRepository(ctx, doer, owner, generateRepo) return generateRepo, nil diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index da5aaf3da0..c04d09af08 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -6,15 +6,23 @@ package integration import ( "fmt" "net/http" + "os" "path" "strconv" "strings" "testing" "time" + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/util" + repo_service "code.gitea.io/gitea/services/repository" "code.gitea.io/gitea/tests" "github.com/PuerkitoBio/goquery" @@ -493,3 +501,46 @@ func testViewCommit(t *testing.T) { resp := MakeRequest(t, req, http.StatusNotFound) assert.True(t, test.IsNormalPageCompleted(resp.Body.String()), "non-existing commit should render 404 page") } + +// TestGenerateRepository the test cannot succeed when moved as a unit test +func TestGenerateRepository(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // a successful generate from template + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo44 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44}) + + generatedRepo, err := repo_service.GenerateRepository(git.DefaultContext, user2, user2, repo44, repo_service.GenerateRepoOptions{ + Name: "generated-from-template-44", + GitContent: true, + }) + assert.NoError(t, err) + assert.NotNil(t, generatedRepo) + + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, generatedRepo.Name)) + assert.NoError(t, err) + assert.True(t, exist) + + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name}) + + err = repo_service.DeleteRepositoryDirectly(db.DefaultContext, user2, generatedRepo.ID) + assert.NoError(t, err) + + // a failed creating because some mock data + // create the repository directory so that the creation will fail after database record created. + assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, "generated-from-template-44"), os.ModePerm)) + + generatedRepo2, err := repo_service.GenerateRepository(db.DefaultContext, user2, user2, repo44, repo_service.GenerateRepoOptions{ + Name: "generated-from-template-44", + GitContent: true, + }) + assert.Nil(t, generatedRepo2) + assert.Error(t, err) + + // assert the cleanup is successful + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name}) + + exist, err = util.IsExist(repo_model.RepoPath(user2.Name, generatedRepo.Name)) + assert.NoError(t, err) + assert.False(t, exist) +} From fd7c364ca62ea5545bd21d1b54af021c4cbc29a4 Mon Sep 17 00:00:00 2001 From: DrMaxNix Date: Tue, 8 Apr 2025 06:45:31 +0000 Subject: [PATCH 06/10] Check user/org repo limit instead of doer (#34147) This PR tries to finally fix the bug mentioned in #30011 and #15504, where the user repo limit is checked when creating a repo in an organization. Fix #30011 --------- Co-authored-by: wxiaoguang Co-authored-by: TheFox0x7 --- models/organization/org.go | 6 ----- models/repo/update.go | 16 ++++++------- models/user/user.go | 22 ++++++++--------- models/user/user_test.go | 35 ++++++++++++++++++++++++++++ routers/web/repo/fork.go | 17 +++++--------- routers/web/repo/repo.go | 16 +++++-------- routers/web/repo/setting/setting.go | 3 ++- services/repository/adopt.go | 16 ++++++------- services/repository/create.go | 14 +++++------ services/repository/fork.go | 2 +- services/repository/template.go | 2 +- services/repository/transfer.go | 4 ++-- services/repository/transfer_test.go | 4 ++-- templates/repo/pulls/fork.tmpl | 2 +- templates/repo/settings/options.tmpl | 4 ++-- 15 files changed, 92 insertions(+), 71 deletions(-) diff --git a/models/organization/org.go b/models/organization/org.go index 3e55a36758..dc889ea17f 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -178,12 +178,6 @@ func (org *Organization) HomeLink() string { return org.AsUser().HomeLink() } -// CanCreateRepo returns if user login can create a repository -// NOTE: functions calling this assume a failure due to repository count limit; if new checks are added, those functions should be revised -func (org *Organization) CanCreateRepo() bool { - return org.AsUser().CanCreateRepo() -} - // FindOrgMembersOpts represensts find org members conditions type FindOrgMembersOpts struct { db.ListOptions diff --git a/models/repo/update.go b/models/repo/update.go index fce357a1ac..15c8c48d5b 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -111,31 +111,31 @@ func (err ErrRepoFilesAlreadyExist) Unwrap() error { return util.ErrAlreadyExist } -// CheckCreateRepository check if could created a repository -func CheckCreateRepository(ctx context.Context, doer, u *user_model.User, name string, overwriteOrAdopt bool) error { - if !doer.CanCreateRepo() { - return ErrReachLimitOfRepo{u.MaxRepoCreation} +// CheckCreateRepository check if doer could create a repository in new owner +func CheckCreateRepository(ctx context.Context, doer, owner *user_model.User, name string, overwriteOrAdopt bool) error { + if !doer.CanCreateRepoIn(owner) { + return ErrReachLimitOfRepo{owner.MaxRepoCreation} } if err := IsUsableRepoName(name); err != nil { return err } - has, err := IsRepositoryModelOrDirExist(ctx, u, name) + has, err := IsRepositoryModelOrDirExist(ctx, owner, name) if err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { - return ErrRepoAlreadyExist{u.Name, name} + return ErrRepoAlreadyExist{owner.Name, name} } - repoPath := RepoPath(u.Name, name) + repoPath := RepoPath(owner.Name, name) isExist, err := util.IsExist(repoPath) if err != nil { log.Error("Unable to check if %s exists. Error: %v", repoPath, err) return err } if !overwriteOrAdopt && isExist { - return ErrRepoFilesAlreadyExist{u.Name, name} + return ErrRepoFilesAlreadyExist{owner.Name, name} } return nil } diff --git a/models/user/user.go b/models/user/user.go index 3b268a6f41..5989be74f0 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -247,19 +247,20 @@ func (u *User) MaxCreationLimit() int { return u.MaxRepoCreation } -// CanCreateRepo returns if user login can create a repository -// NOTE: functions calling this assume a failure due to repository count limit; if new checks are added, those functions should be revised -func (u *User) CanCreateRepo() bool { +// CanCreateRepoIn checks whether the doer(u) can create a repository in the owner +// NOTE: functions calling this assume a failure due to repository count limit; it ONLY checks the repo number LIMIT, if new checks are added, those functions should be revised +func (u *User) CanCreateRepoIn(owner *User) bool { if u.IsAdmin { return true } - if u.MaxRepoCreation <= -1 { - if setting.Repository.MaxCreationLimit <= -1 { + const noLimit = -1 + if owner.MaxRepoCreation == noLimit { + if setting.Repository.MaxCreationLimit == noLimit { return true } - return u.NumRepos < setting.Repository.MaxCreationLimit + return owner.NumRepos < setting.Repository.MaxCreationLimit } - return u.NumRepos < u.MaxRepoCreation + return owner.NumRepos < owner.MaxRepoCreation } // CanCreateOrganization returns true if user can create organisation. @@ -272,13 +273,12 @@ func (u *User) CanEditGitHook() bool { return !setting.DisableGitHooks && (u.IsAdmin || u.AllowGitHook) } -// CanForkRepo returns if user login can fork a repository -// It checks especially that the user can create repos, and potentially more -func (u *User) CanForkRepo() bool { +// CanForkRepoIn ONLY checks repository count limit +func (u *User) CanForkRepoIn(owner *User) bool { if setting.Repository.AllowForkWithoutMaximumLimit { return true } - return u.CanCreateRepo() + return u.CanCreateRepoIn(owner) } // CanImportLocal returns true if user can migrate repository by local path. diff --git a/models/user/user_test.go b/models/user/user_test.go index 2d5b6a405c..90e8bf13a8 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" @@ -616,3 +617,37 @@ func TestGetInactiveUsers(t *testing.T) { assert.NoError(t, err) assert.Empty(t, users) } + +func TestCanCreateRepo(t *testing.T) { + defer test.MockVariableValue(&setting.Repository.MaxCreationLimit)() + const noLimit = -1 + doerNormal := &user_model.User{} + doerAdmin := &user_model.User{IsAdmin: true} + t.Run("NoGlobalLimit", func(t *testing.T) { + setting.Repository.MaxCreationLimit = noLimit + + assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit})) + assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0})) + assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100})) + + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit})) + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0})) + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100})) + }) + + t.Run("GlobalLimit50", func(t *testing.T) { + setting.Repository.MaxCreationLimit = 50 + + assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit})) + assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: noLimit})) // limited by global limit + assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0})) + assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100})) + assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: 100})) + + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit})) + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: noLimit})) + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0})) + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100})) + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: 100})) + }) +} diff --git a/routers/web/repo/fork.go b/routers/web/repo/fork.go index 36e64bfee3..79f033659b 100644 --- a/routers/web/repo/fork.go +++ b/routers/web/repo/fork.go @@ -91,12 +91,17 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository { ctx.Data["CanForkToUser"] = canForkToUser ctx.Data["Orgs"] = orgs + // TODO: this message should only be shown for the "current doer" when it is selected, just like the "new repo" page. + // msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", ctx.Doer.MaxCreationLimit()) + if canForkToUser { ctx.Data["ContextUser"] = ctx.Doer + ctx.Data["CanForkRepoInNewOwner"] = true } else if len(orgs) > 0 { ctx.Data["ContextUser"] = orgs[0] + ctx.Data["CanForkRepoInNewOwner"] = true } else { - ctx.Data["CanForkRepo"] = false + ctx.Data["CanForkRepoInNewOwner"] = false ctx.Flash.Error(ctx.Tr("repo.fork_no_valid_owners"), true) return nil } @@ -120,15 +125,6 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository { // Fork render repository fork page func Fork(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("new_fork") - - if ctx.Doer.CanForkRepo() { - ctx.Data["CanForkRepo"] = true - } else { - maxCreationLimit := ctx.Doer.MaxCreationLimit() - msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) - ctx.Flash.Error(msg, true) - } - getForkRepository(ctx) if ctx.Written() { return @@ -141,7 +137,6 @@ func Fork(ctx *context.Context) { func ForkPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.CreateRepoForm) ctx.Data["Title"] = ctx.Tr("new_fork") - ctx.Data["CanForkRepo"] = true ctxUser := checkContextUser(ctx, form.UID) if ctx.Written() { diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index e260ea36dd..ee112b83f2 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -87,17 +87,13 @@ func checkContextUser(ctx *context.Context, uid int64) *user_model.User { return nil } - if !ctx.Doer.IsAdmin { - orgsAvailable := []*organization.Organization{} - for i := 0; i < len(orgs); i++ { - if orgs[i].CanCreateRepo() { - orgsAvailable = append(orgsAvailable, orgs[i]) - } + var orgsAvailable []*organization.Organization + for i := 0; i < len(orgs); i++ { + if ctx.Doer.CanCreateRepoIn(orgs[i].AsUser()) { + orgsAvailable = append(orgsAvailable, orgs[i]) } - ctx.Data["Orgs"] = orgsAvailable - } else { - ctx.Data["Orgs"] = orgs } + ctx.Data["Orgs"] = orgsAvailable // Not equal means current user is an organization. if uid == ctx.Doer.ID || uid == 0 { @@ -154,7 +150,7 @@ func createCommon(ctx *context.Context) { ctx.Data["Licenses"] = repo_module.Licenses ctx.Data["Readmes"] = repo_module.Readmes ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate - ctx.Data["CanCreateRepoInDoer"] = ctx.Doer.CanCreateRepo() + ctx.Data["CanCreateRepoInDoer"] = ctx.Doer.CanCreateRepoIn(ctx.Doer) ctx.Data["MaxCreationLimitOfDoer"] = ctx.Doer.MaxCreationLimit() ctx.Data["SupportedObjectFormats"] = git.DefaultFeatures().SupportedObjectFormats ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 380fec9d4a..5552a8726c 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -59,6 +59,7 @@ func SettingsCtxData(ctx *context.Context) { ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush ctx.Data["DefaultMirrorInterval"] = setting.Mirror.DefaultInterval ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval + ctx.Data["CanConvertFork"] = ctx.Repo.Repository.IsFork && ctx.Doer.CanCreateRepoIn(ctx.Repo.Repository.Owner) signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath()) ctx.Data["SigningKeyAvailable"] = len(signing) > 0 @@ -786,7 +787,7 @@ func handleSettingsPostConvertFork(ctx *context.Context) { return } - if !ctx.Repo.Owner.CanCreateRepo() { + if !ctx.Doer.CanForkRepoIn(ctx.Repo.Owner) { maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit() msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) ctx.Flash.Error(msg) diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 6953970e28..6d5505c42c 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -40,17 +40,17 @@ func deleteFailedAdoptRepository(repoID int64) error { } // AdoptRepository adopts pre-existing repository files for the user/organization. -func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { - if !doer.IsAdmin && !u.CanCreateRepo() { +func AdoptRepository(ctx context.Context, doer, owner *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { + if !doer.CanCreateRepoIn(owner) { return nil, repo_model.ErrReachLimitOfRepo{ - Limit: u.MaxRepoCreation, + Limit: owner.MaxRepoCreation, } } repo := &repo_model.Repository{ - OwnerID: u.ID, - Owner: u, - OwnerName: u.Name, + OwnerID: owner.ID, + Owner: owner, + OwnerName: owner.Name, Name: opts.Name, LowerName: strings.ToLower(opts.Name), Description: opts.Description, @@ -65,7 +65,7 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR // 1 - create the repository database operations first err := db.WithTx(ctx, func(ctx context.Context) error { - return createRepositoryInDB(ctx, doer, u, repo, false) + return createRepositoryInDB(ctx, doer, owner, repo, false) }) if err != nil { return nil, err @@ -104,7 +104,7 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) } - notify_service.AdoptRepository(ctx, doer, u, repo) + notify_service.AdoptRepository(ctx, doer, owner, repo) return repo, nil } diff --git a/services/repository/create.go b/services/repository/create.go index 22dfce91f0..050544ce11 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -204,10 +204,10 @@ func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Re } // CreateRepositoryDirectly creates a repository for the user/organization. -func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { - if !doer.IsAdmin && !u.CanCreateRepo() { +func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { + if !doer.CanCreateRepoIn(owner) { return nil, repo_model.ErrReachLimitOfRepo{ - Limit: u.MaxRepoCreation, + Limit: owner.MaxRepoCreation, } } @@ -227,9 +227,9 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt } repo := &repo_model.Repository{ - OwnerID: u.ID, - Owner: u, - OwnerName: u.Name, + OwnerID: owner.ID, + Owner: owner, + OwnerName: owner.Name, Name: opts.Name, LowerName: strings.ToLower(opts.Name), Description: opts.Description, @@ -252,7 +252,7 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt // 1 - create the repository database operations first err := db.WithTx(ctx, func(ctx context.Context) error { - return createRepositoryInDB(ctx, doer, u, repo, false) + return createRepositoryInDB(ctx, doer, owner, repo, false) }) if err != nil { return nil, err diff --git a/services/repository/fork.go b/services/repository/fork.go index daf6913510..1794cc18ab 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -65,7 +65,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork } // Fork is prohibited, if user has reached maximum limit of repositories - if !owner.CanForkRepo() { + if !doer.CanForkRepoIn(owner) { return nil, repo_model.ErrReachLimitOfRepo{ Limit: owner.MaxRepoCreation, } diff --git a/services/repository/template.go b/services/repository/template.go index b3f34e4858..95f585cead 100644 --- a/services/repository/template.go +++ b/services/repository/template.go @@ -68,7 +68,7 @@ func GenerateProtectedBranch(ctx context.Context, templateRepo, generateRepo *re // GenerateRepository generates a repository from a template func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) { - if !doer.IsAdmin && !owner.CanCreateRepo() { + if !doer.CanCreateRepoIn(owner) { return nil, repo_model.ErrReachLimitOfRepo{ Limit: owner.MaxRepoCreation, } diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 4e44b90df2..86917ad285 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -61,7 +61,7 @@ func AcceptTransferOwnership(ctx context.Context, repo *repo_model.Repository, d return err } - if !doer.IsAdmin && !repoTransfer.Recipient.CanCreateRepo() { + if !doer.CanCreateRepoIn(repoTransfer.Recipient) { limit := util.Iif(repoTransfer.Recipient.MaxRepoCreation >= 0, repoTransfer.Recipient.MaxRepoCreation, setting.Repository.MaxCreationLimit) return LimitReachedError{Limit: limit} } @@ -416,7 +416,7 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use return err } - if !doer.IsAdmin && !newOwner.CanCreateRepo() { + if !doer.CanForkRepoIn(newOwner) { limit := util.Iif(newOwner.MaxRepoCreation >= 0, newOwner.MaxRepoCreation, setting.Repository.MaxCreationLimit) return LimitReachedError{Limit: limit} } diff --git a/services/repository/transfer_test.go b/services/repository/transfer_test.go index bf71c7ca2e..80a073e9f9 100644 --- a/services/repository/transfer_test.go +++ b/services/repository/transfer_test.go @@ -144,7 +144,7 @@ func TestRepositoryTransferRejection(t *testing.T) { require.NotNil(t, transfer) require.NoError(t, transfer.LoadRecipient(db.DefaultContext)) - require.True(t, transfer.Recipient.CanCreateRepo()) // admin is not subject to limits + require.True(t, doerAdmin.CanCreateRepoIn(transfer.Recipient)) // admin is not subject to limits // Administrator should not be affected by the limits so transfer should be successful assert.NoError(t, AcceptTransferOwnership(db.DefaultContext, repo, doerAdmin)) @@ -158,7 +158,7 @@ func TestRepositoryTransferRejection(t *testing.T) { require.NotNil(t, transfer) require.NoError(t, transfer.LoadRecipient(db.DefaultContext)) - require.False(t, transfer.Recipient.CanCreateRepo()) // regular user is subject to limits + require.False(t, doer.CanCreateRepoIn(transfer.Recipient)) // regular user is subject to limits // Cannot accept because of the limit err = AcceptTransferOwnership(db.DefaultContext, repo, doer) diff --git a/templates/repo/pulls/fork.tmpl b/templates/repo/pulls/fork.tmpl index 9a6c44077f..adf9e250c1 100644 --- a/templates/repo/pulls/fork.tmpl +++ b/templates/repo/pulls/fork.tmpl @@ -75,7 +75,7 @@
    -
    diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 202be3fce7..4d61604612 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -802,7 +802,7 @@
    {{end}} - {{if and .Repository.IsFork .Repository.Owner.CanCreateRepo}} + {{if .CanConvertFork}}
    {{ctx.Locale.Tr "repo.settings.convert_fork"}}
    @@ -916,7 +916,7 @@
    {{end}} - {{if and .Repository.IsFork .Repository.Owner.CanCreateRepo}} + {{if .CanConvertFork}}