From d9b50e4fd126e60fe0735bafbcb12d0e8f6dd0c2 Mon Sep 17 00:00:00 2001 From: Wim Date: Sun, 5 Jun 2022 21:55:08 +0200 Subject: [PATCH 1/5] Update MAINTAINERS (#19896) Added myself as maintainer [List of PRs](https://github.com/go-gitea/gitea/pulls?q=is%3Apr+author%3A42wim+is%3Aclosed) --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3563b51f24b7c..907cbb5c41f40 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -47,3 +47,4 @@ Leon Hofmeister (@delvh) Gusted (@singuliere) silentcode (@silentcodeg) +Wim (@42wim) From 0a8c0306004382263ba86610ba4111c569b99f14 Mon Sep 17 00:00:00 2001 From: Wim Date: Mon, 6 Jun 2022 00:10:13 +0000 Subject: [PATCH 2/5] [skip ci] Updated translations via Crowdin --- options/locale/locale_cs-CZ.ini | 1 - options/locale/locale_de-DE.ini | 1 - options/locale/locale_el-GR.ini | 1 - options/locale/locale_es-ES.ini | 1 - options/locale/locale_fa-IR.ini | 1 - options/locale/locale_fr-FR.ini | 1 - options/locale/locale_it-IT.ini | 1 - options/locale/locale_ja-JP.ini | 1 - options/locale/locale_lv-LV.ini | 1 - options/locale/locale_nl-NL.ini | 1 - options/locale/locale_pl-PL.ini | 1 - options/locale/locale_pt-BR.ini | 1 - options/locale/locale_pt-PT.ini | 1 - options/locale/locale_ru-RU.ini | 1 - options/locale/locale_si-LK.ini | 1 - options/locale/locale_sv-SE.ini | 1 - options/locale/locale_tr-TR.ini | 1 - options/locale/locale_uk-UA.ini | 1 - options/locale/locale_zh-CN.ini | 1 - options/locale/locale_zh-TW.ini | 1 - 20 files changed, 20 deletions(-) diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 7e84c449aa894..6966410803e57 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -902,7 +902,6 @@ need_auth=Ověření migrate_options=Možnosti migrace migrate_service=Migrační služba migrate_options_mirror_helper=Tento repozitář bude zrcadlem -migrate_options_mirror_disabled=Administrátor vašeho webu zakázal nová zrcadla. migrate_options_lfs=Migrovat LFS soubory migrate_options_lfs_endpoint.label=Koncový bod LFS migrate_options_lfs_endpoint.description=Migrace se pokusí použít váš vzdálený Git pro určení LFS serveru. Můžete také zadat vlastní koncový bod, pokud jsou data LFS repozitáře uložena někde jinde. diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 85602158996a2..70ff7c0e5f45d 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -916,7 +916,6 @@ need_auth=Authentifizierung migrate_options=Migrationsoptionen migrate_service=Migrationsdienst migrate_options_mirror_helper=Dieses Repository wird ein Mirror sein -migrate_options_mirror_disabled=Dein Administrator hat neue Mirrors deaktiviert. migrate_options_lfs=LFS-Dateien migrieren migrate_options_lfs_endpoint.label=LFS-Endpunkt migrate_options_lfs_endpoint.description=Migration wird versuchen, über den entfernten Git-Server den LFS-Server zu bestimmen. Du kannst auch einen eigenen Endpunkt angeben, wenn die LFS-Dateien woanders gespeichert werden. diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index f611c67e6198f..c9ff2c271885e 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -930,7 +930,6 @@ need_auth=Εξουσιοδότηση migrate_options=Επιλογές Μεταφοράς migrate_service=Υπηρεσία Μεταφοράς migrate_options_mirror_helper=Αυτό το αποθετήριο θα είναι ένα είδωλο -migrate_options_mirror_disabled=Ο διαχειριστής έχει απενεργοποιήσει τα νέα είδωλα. migrate_options_lfs=Μεταφορά αρχείων LFS migrate_options_lfs_endpoint.label=LFS Endpoint migrate_options_lfs_endpoint.description=Η μεταφορά θα προσπαθήσει να χρησιμοποιήσει το Git remote για να καθορίσει τον διακομιστή LFS. Μπορείτε επίσης να καθορίσετε ένα δικό σας endpoint αν τα δεδομένα LFS του αποθετηρίου αποθηκεύονται κάπου αλλού. diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 6bdaeb3fb5163..9eb525ca33356 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -920,7 +920,6 @@ need_auth=Autorización migrate_options=Opciones de migración migrate_service=Servicio de Migración migrate_options_mirror_helper=Este repositorio será uno replicado -migrate_options_mirror_disabled=El administrador de tu sitio ha desactivado nuevos repositorios replicados. migrate_options_lfs=Migrar archivos LFS migrate_options_lfs_endpoint.label=Punto final de LFS migrate_options_lfs_endpoint.description=Migración intentará usar su mando Git para determinar el servidor LFS. También puede especificar un punto final personalizado si los datos LFS del repositorio se almacenan en otro lugar. diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index da0a83b75903e..283987720e493 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -867,7 +867,6 @@ need_auth=دسترسی migrate_options=تنظیمات مهاجرت migrate_service=سرویس مهاجرت migrate_options_mirror_helper=این مخزن یک آینه خواهد بود -migrate_options_mirror_disabled=سرپرست سایت آینه‌های جدید را غیرفعال کرده است. migrate_options_lfs=مهاجرت فایلهای LFS migrate_options_lfs_endpoint.label=نشانهای پایانی LFS migrate_options_lfs_endpoint.description=Migration سعی خواهد کرد از کنترل از راه دور Git شما برای تعیین سرور LFS استفاده کند. همچنین اگر داده های LFS مخزن در جای دیگری ذخیره شده باشد، می توانید یک نقطه پایانی سفارشی را مشخص کنید. diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index df8e148035be6..4a40f50460097 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -819,7 +819,6 @@ need_auth=Autorisation migrate_options=Options de migration migrate_service=Service de migration migrate_options_mirror_helper=Ce dépôt sera un miroir -migrate_options_mirror_disabled=L’administrateur du site a désactivé les nouveaux miroirs. migrate_options_lfs=Migrer les fichiers LFS migrate_options_lfs_endpoint.label=Point d'accès LFS migrate_options_lfs_endpoint.description=La migration va tenter d'utiliser votre dépôt Git distant pour déterminer le serveur LFS. Vous pouvez également spécifier un point d'accès personnalisé si les données LFS du dépôt sont stockées ailleurs. diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 02f4249105a02..2363f202c145b 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -740,7 +740,6 @@ form.name_pattern_not_allowed=Il modello '%s' non è consentito come nome di un migrate_options=Opzioni di migrazione migrate_service=Servizio migrazione migrate_options_mirror_helper=Questo repository sarà un mirror -migrate_options_mirror_disabled=L'amministratore del sito ha disabilitato i nuovi mirror. migrate_items=Elementi di migrazione migrate_items_wiki=Wiki migrate_items_milestones=Milestone diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 85e2fd31bee61..b0afaaef5133b 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -930,7 +930,6 @@ need_auth=認証 migrate_options=移行オプション migrate_service=移行するサービス migrate_options_mirror_helper=このリポジトリをミラーにする -migrate_options_mirror_disabled=サイト管理者はミラーの新規作成を無効にしています。 migrate_options_lfs=LFS ファイルのマイグレート migrate_options_lfs_endpoint.label=LFS エンドポイント migrate_options_lfs_endpoint.description=マイグレーションでは、リモート側のGitをもとにLFSサーバーを決定しようとします。 リポジトリのLFSデータがほかの場所に保存されている場合は、独自のエンドポイントを指定することができます。 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 0d2649424b4d6..b5673ecc7048b 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -908,7 +908,6 @@ need_auth=Autorizācija migrate_options=Migrācijas opcijas migrate_service=Migrācijas serviss migrate_options_mirror_helper=Šis repozitorijs būs spogulis -migrate_options_mirror_disabled=Lapas administrators ir atslēdzies iespēju viedot jaunus spoguļus. migrate_options_lfs=Migrēt LFS failus migrate_options_lfs_endpoint.label=LFS galapunkts migrate_options_lfs_endpoint.description=Migrācija mēģinās izmantot attālināto URL, lai noteiktu LFS serveri. Var norādīt arī citu galapunktu, ja repozitorija LFS dati ir izvietoti citā vietā. diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 0cec095ccca5e..245fcf1bc261c 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -758,7 +758,6 @@ form.name_pattern_not_allowed=Het patroon '%s' is niet toegestaan in de naam van migrate_options=Migratie opties migrate_service=Migratie Service migrate_options_mirror_helper=Deze repository zal een kopie zijn -migrate_options_mirror_disabled=Uw sitebeheerder heeft nieuwe mirrors uitgeschakeld. migrate_items=Migratie Items migrate_items_wiki=Wiki migrate_items_milestones=Mijlpalen diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 8bf5449fccd98..4a526115f2f4f 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -875,7 +875,6 @@ need_auth=Autoryzacja migrate_options=Opcje migracji migrate_service=Usługa migracji migrate_options_mirror_helper=To repozytorium będzie kopią lustrzaną -migrate_options_mirror_disabled=Administrator witryny wyłączył tworzenie nowych repozytoriów lustrzanych. migrate_options_lfs=Migruj pliki LFS migrate_options_lfs_endpoint.label=Punkt końcowy LFS migrate_options_lfs_endpoint.description=Migracja spróbuje użyć Git remote, aby określić serwer LFS. Możesz również określić niestandardowy punkt końcowy, jeśli dane repozytorium LFS są przechowywane gdzieś indziej. diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index b344d1dd6ad67..240a6a3d05534 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -930,7 +930,6 @@ need_auth=Autorização migrate_options=Opções de Migração migrate_service=Serviço de Migração migrate_options_mirror_helper=Este repositório será um espelho -migrate_options_mirror_disabled=O administrador do site desabilitou novos espelhamentos. migrate_options_lfs=Migrar arquivos LFS migrate_options_lfs_endpoint.label=Destino LFS migrate_options_lfs_endpoint.description=A migração tentará usar seu controle remoto Git para determinar o servidor LFS. Você também pode especificar um destino personalizado se os dados do repositório LFS forem armazenados em outro lugar. diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 64a0087b51b5a..d7ebae4456025 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -929,7 +929,6 @@ need_auth=Autorização migrate_options=Opções de migração migrate_service=Serviço de migração migrate_options_mirror_helper=Este repositório irá ser uma réplica -migrate_options_mirror_disabled=O administrador desabilitou novas réplicas. migrate_options_lfs=Migrar ficheiros LFS migrate_options_lfs_endpoint.label=Destino LFS migrate_options_lfs_endpoint.description=A migração irá tentar usar o seu controlo remoto do Git para determinar o servidor LFS. Também pode especificar um destino personalizado se os dados do repositório LFS forem armazenados noutro lugar. diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index de5583828dd4e..477d3bf4037d9 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -922,7 +922,6 @@ need_auth=Авторизация migrate_options=Параметры миграции migrate_service=Сервис миграции migrate_options_mirror_helper=Этот репозиторий будет зеркалом -migrate_options_mirror_disabled=Администратор вашего сайта отключил новые зеркала. migrate_options_lfs=Перенос LFS файлов migrate_options_lfs_endpoint.label=LFS Endpoint migrate_options_lfs_endpoint.description=Миграция попытается использовать ваш Git удаленно, чтобы определить сервер LFS. Вы также можете указать пользовательскую конечную точку, если данные хранятся в другом месте. diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index afb77c87c8f1f..eaa75b3f56a1c 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -823,7 +823,6 @@ template.topics=මාතෘකා -migrate_options_mirror_disabled=ඔබේ වෙබ් අඩවිය පරිපාලක නව දර්පණ අක්රීය කර ඇත. migrate_options_lfs=LFS ගොනු සංක්රමණය migrate_options_lfs_endpoint.label=LFS එන්පොයින්ට් migrate_options_lfs_endpoint.description=සංක්රමණය ඔබගේ Git දුරස්ථ භාවිතා කිරීමට උත්සාහ කරනු ඇත LFS සේවාදායකය තීරණය. නිධිය LFS දත්ත වෙන කොහේ හරි ගබඩා කර තිබේ නම් ඔබට අභිරුචි අන්ත ලක්ෂ්යයක් නියම කළ හැකිය. diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index ab85d69f160a0..4afa39b074192 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -704,7 +704,6 @@ form.name_pattern_not_allowed=Mönstret '%s' är otillåtet i ett utvecklingskat migrate_options=Migrationsalternativ migrate_service=Migreringstjänst migrate_options_mirror_helper=Denna utvecklingskatalog kommer att vara en spegel -migrate_options_mirror_disabled=Din webbplatsadministratör har inaktiverat nya speglar. migrate_items=Migrationsobjekt migrate_items_wiki=Wiki migrate_items_milestones=Milstenar diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index a165ee3645204..48131ade8e120 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -850,7 +850,6 @@ need_auth=Yetkilendirme migrate_options=Göç Seçenekleri migrate_service=Göç Hizmeti migrate_options_mirror_helper=Bu depo bir yansı olacaktır -migrate_options_mirror_disabled=Site yöneticiniz yeni yansıları devre dışı bıraktı. migrate_options_lfs=LFS dosyalarını taşı migrate_options_lfs_endpoint.label=LFS Uç Noktası migrate_options_lfs_endpoint.description=Taşıma, LFS sunucusunu belirlemek için Git uzak sunucusunu kullanmaya çalışacak. Eğer LFS veri deposu başka yerdeyse özel bir uç nokta da belirtebilirsiniz. diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 5de1ba80a76e8..0f538e49ca76e 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -874,7 +874,6 @@ need_auth=Авторизація migrate_options=Параметри міграції migrate_service=Сервіс міграції migrate_options_mirror_helper=Цей репозиторій буде дзеркалом -migrate_options_mirror_disabled=Адміністратор вашого сайту вимкнув створення нових дзеркал. migrate_options_lfs=Перенесення LFS файлів migrate_options_lfs_endpoint.label=Кінцева точка LFS migrate_options_lfs_endpoint.description=Міграція буде намагатися використовувати ваш Git віддалено, щоб визначати LFS сервер. Ви також можете вказати свою кінцеву точку, якщо дані репозиторію LFS зберігаються в іншому місці. diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 3b5d8f8267949..3b309c077e03e 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -930,7 +930,6 @@ need_auth=授权 migrate_options=迁移选项 migrate_service=迁移服务 migrate_options_mirror_helper=该仓库将是一个 镜像 -migrate_options_mirror_disabled=您的站点管理员已禁用创建新镜像。 migrate_options_lfs=迁移 LFS 文件 migrate_options_lfs_endpoint.label=LFS 网址 migrate_options_lfs_endpoint.description=迁移将尝试使用你的 Git remote 来 确定 LFS 服务器。如果仓库 LFS 数据存储在其他位置,你还可以指定自定义网址。 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 27338ce4b1aaa..b9fb9105c8754 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -928,7 +928,6 @@ need_auth=授權 migrate_options=遷移選項 migrate_service=遷移服務 migrate_options_mirror_helper=將此儲存庫設定為鏡像儲存庫 -migrate_options_mirror_disabled=您的網站管理員已經停用新增鏡像儲存庫的功能。 migrate_options_lfs=遷移 LFS 檔案 migrate_options_lfs_endpoint.label=LFS 端點 migrate_options_lfs_endpoint.description=遷移將會嘗試使用您的 Git Remote 來確認 LFS 伺服器。如果存儲庫的 LFS 資料放在其他地方,您也可以指定自訂的端點。 From df0fb17d041b34854c5a0bdd4a14ad50807ded4a Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 6 Jun 2022 05:27:25 +0200 Subject: [PATCH 3/5] Modernize JS build scripts (#19824) - Remove __dirname, use file URLs instead - Upgrade fabric dependency - Use fs/promises syntax, this breaks node 12 but we require 14 already The change in public/img/favicon.svg is not caused by the fabric upgrade, but it seems it was not properly generated when introduced. Co-authored-by: Lunny Xiao --- Makefile | 2 +- build/generate-images.js | 35 +++++++++++++++-------------------- build/generate-svg.js | 26 +++++++++++++------------- public/img/favicon.svg | 2 +- webpack.config.js | 34 ++++++++++++++++++---------------- 5 files changed, 48 insertions(+), 51 deletions(-) diff --git a/Makefile b/Makefile index d2ed8d3294691..ce4dec21a298e 100644 --- a/Makefile +++ b/Makefile @@ -761,7 +761,7 @@ generate-gitignore: .PHONY: generate-images generate-images: | node_modules - npm install --no-save --no-package-lock fabric@4 imagemin-zopfli@7 + npm install --no-save --no-package-lock fabric@5 imagemin-zopfli@7 node build/generate-images.js $(TAGS) .PHONY: generate-manpage diff --git a/build/generate-images.js b/build/generate-images.js index 0a91d896a8065..62ce5244f0023 100755 --- a/build/generate-images.js +++ b/build/generate-images.js @@ -1,14 +1,8 @@ +#!/usr/bin/env node import imageminZopfli from 'imagemin-zopfli'; import {optimize} from 'svgo'; import {fabric} from 'fabric'; -import fs from 'fs'; -import {resolve, dirname} from 'path'; -import {fileURLToPath} from 'url'; - -const {readFile, writeFile} = fs.promises; -const __dirname = dirname(fileURLToPath(import.meta.url)); -const logoFile = resolve(__dirname, '../assets/logo.svg'); -const faviconFile = resolve(__dirname, '../assets/favicon.svg'); +import {readFile, writeFile} from 'fs/promises'; function exit(err) { if (err) console.error(err); @@ -23,8 +17,10 @@ function loadSvg(svg) { }); } -async function generate(svg, outputFile, {size, bg}) { - if (outputFile.endsWith('.svg')) { +async function generate(svg, path, {size, bg}) { + const outputFile = new URL(path, import.meta.url); + + if (String(outputFile).endsWith('.svg')) { const {data} = optimize(svg, { plugins: [ 'preset-default', @@ -69,19 +65,18 @@ async function generate(svg, outputFile, {size, bg}) { async function main() { const gitea = process.argv.slice(2).includes('gitea'); - const logoSvg = await readFile(logoFile, 'utf8'); - const faviconSvg = await readFile(faviconFile, 'utf8'); + const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8'); + const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8'); await Promise.all([ - generate(logoSvg, resolve(__dirname, '../public/img/logo.svg'), {size: 32}), - generate(logoSvg, resolve(__dirname, '../public/img/logo.png'), {size: 512}), - generate(faviconSvg, resolve(__dirname, '../public/img/favicon.svg'), {size: 32}), - generate(faviconSvg, resolve(__dirname, '../public/img/favicon.png'), {size: 180}), - generate(logoSvg, resolve(__dirname, '../public/img/avatar_default.png'), {size: 200}), - generate(logoSvg, resolve(__dirname, '../public/img/apple-touch-icon.png'), {size: 180, bg: true}), - gitea && generate(logoSvg, resolve(__dirname, '../public/img/gitea.svg'), {size: 32}), + generate(logoSvg, '../public/img/logo.svg', {size: 32}), + generate(logoSvg, '../public/img/logo.png', {size: 512}), + generate(faviconSvg, '../public/img/favicon.svg', {size: 32}), + generate(faviconSvg, '../public/img/favicon.png', {size: 180}), + generate(logoSvg, '../public/img/avatar_default.png', {size: 200}), + generate(logoSvg, '../public/img/apple-touch-icon.png', {size: 180, bg: true}), + gitea && generate(logoSvg, '../public/img/gitea.svg', {size: 32}), ]); } main().then(exit).catch(exit); - diff --git a/build/generate-svg.js b/build/generate-svg.js index 29b7d476938e9..c4f3d5a7f9f8f 100755 --- a/build/generate-svg.js +++ b/build/generate-svg.js @@ -1,13 +1,14 @@ +#!/usr/bin/env node import fastGlob from 'fast-glob'; import {optimize} from 'svgo'; -import {resolve, parse, dirname} from 'path'; -import fs from 'fs'; +import {parse} from 'path'; +import {readFile, writeFile, mkdir} from 'fs/promises'; import {fileURLToPath} from 'url'; -const {readFile, writeFile, mkdir} = fs.promises; -const __dirname = dirname(fileURLToPath(import.meta.url)); -const glob = (pattern) => fastGlob.sync(pattern, {cwd: resolve(__dirname), absolute: true}); -const outputDir = resolve(__dirname, '../public/img/svg'); +const glob = (pattern) => fastGlob.sync(pattern, { + cwd: fileURLToPath(new URL('..', import.meta.url)), + absolute: true, +}); function exit(err) { if (err) console.error(err); @@ -16,7 +17,6 @@ function exit(err) { async function processFile(file, {prefix, fullName} = {}) { let name; - if (fullName) { name = fullName; } else { @@ -35,7 +35,8 @@ async function processFile(file, {prefix, fullName} = {}) { {name: 'addAttributesToSVGElement', params: {attributes: [{'width': '16'}, {'height': '16'}, {'aria-hidden': 'true'}]}}, ], }); - await writeFile(resolve(outputDir, `${name}.svg`), data); + + await writeFile(fileURLToPath(new URL(`../public/img/svg/${name}.svg`, import.meta.url)), data); } function processFiles(pattern, opts) { @@ -44,15 +45,14 @@ function processFiles(pattern, opts) { async function main() { try { - await mkdir(outputDir); + await mkdir(fileURLToPath(new URL('../public/img/svg', import.meta.url)), {recursive: true}); } catch {} await Promise.all([ - ...processFiles('../node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}), - ...processFiles('../web_src/svg/*.svg'), - ...processFiles('../public/img/gitea.svg', {fullName: 'gitea-gitea'}), + ...processFiles('node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}), + ...processFiles('web_src/svg/*.svg'), + ...processFiles('public/img/gitea.svg', {fullName: 'gitea-gitea'}), ]); } main().then(exit).catch(exit); - diff --git a/public/img/favicon.svg b/public/img/favicon.svg index dca9b4f4db554..afeeacb77cc8a 100644 --- a/public/img/favicon.svg +++ b/public/img/favicon.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 86517f54cbe88..3851e8e893421 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,15 +6,17 @@ import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'; import VueLoader from 'vue-loader'; import EsBuildLoader from 'esbuild-loader'; -import {resolve, parse, dirname} from 'path'; +import {parse, dirname} from 'path'; import webpack from 'webpack'; import {fileURLToPath} from 'url'; const {VueLoaderPlugin} = VueLoader; const {ESBuildMinifyPlugin} = EsBuildLoader; const {SourceMapDevToolPlugin} = webpack; -const __dirname = dirname(fileURLToPath(import.meta.url)); -const glob = (pattern) => fastGlob.sync(pattern, {cwd: __dirname, absolute: true}); +const glob = (pattern) => fastGlob.sync(pattern, { + cwd: dirname(fileURLToPath(new URL(import.meta.url))), + absolute: true, +}); const themes = {}; for (const path of glob('web_src/less/themes/*.less')) { @@ -43,29 +45,29 @@ export default { mode: isProduction ? 'production' : 'development', entry: { index: [ - resolve(__dirname, 'web_src/js/jquery.js'), - resolve(__dirname, 'web_src/fomantic/build/semantic.js'), - resolve(__dirname, 'web_src/js/index.js'), - resolve(__dirname, 'node_modules/easymde/dist/easymde.min.css'), - resolve(__dirname, 'web_src/fomantic/build/semantic.css'), - resolve(__dirname, 'web_src/less/misc.css'), - resolve(__dirname, 'web_src/less/index.less'), + fileURLToPath(new URL('web_src/js/jquery.js', import.meta.url)), + fileURLToPath(new URL('web_src/fomantic/build/semantic.js', import.meta.url)), + fileURLToPath(new URL('web_src/js/index.js', import.meta.url)), + fileURLToPath(new URL('node_modules/easymde/dist/easymde.min.css', import.meta.url)), + fileURLToPath(new URL('web_src/fomantic/build/semantic.css', import.meta.url)), + fileURLToPath(new URL('web_src/less/misc.css', import.meta.url)), + fileURLToPath(new URL('web_src/less/index.less', import.meta.url)), ], swagger: [ - resolve(__dirname, 'web_src/js/standalone/swagger.js'), - resolve(__dirname, 'web_src/less/standalone/swagger.less'), + fileURLToPath(new URL('web_src/js/standalone/swagger.js', import.meta.url)), + fileURLToPath(new URL('web_src/less/standalone/swagger.less', import.meta.url)), ], serviceworker: [ - resolve(__dirname, 'web_src/js/serviceworker.js'), + fileURLToPath(new URL('web_src/js/serviceworker.js', import.meta.url)), ], 'eventsource.sharedworker': [ - resolve(__dirname, 'web_src/js/features/eventsource.sharedworker.js'), + fileURLToPath(new URL('web_src/js/features/eventsource.sharedworker.js', import.meta.url)), ], ...themes, }, devtool: false, output: { - path: resolve(__dirname, 'public'), + path: fileURLToPath(new URL('public', import.meta.url)), filename: ({chunk}) => { // serviceworker can only manage assets below it's script's directory so // we have to put it in / instead of /js/ @@ -165,7 +167,7 @@ export default { }, { test: /\.svg$/, - include: resolve(__dirname, 'public/img/svg'), + include: fileURLToPath(new URL('public/img/svg', import.meta.url)), type: 'asset/source', }, { From ebeb6e7c71a0c763b52153f4eb427e7c5b89a95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Mon, 6 Jun 2022 06:58:53 +0200 Subject: [PATCH 4/5] A minimal change to replace data calls with attr as per guidelines (#19900) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This affects the manage topics on a repository. Namely the done button once changes are made. Signed-off-by: André Jaenisch Co-authored-by: Lunny Xiao Co-authored-by: wxiaoguang --- web_src/js/features/repo-home.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/features/repo-home.js b/web_src/js/features/repo-home.js index f0ea18dd10c8a..c4bdf3d4506dc 100644 --- a/web_src/js/features/repo-home.js +++ b/web_src/js/features/repo-home.js @@ -30,7 +30,7 @@ export function initRepoTopicBar() { saveBtn.on('click', () => { const topics = $('input[name=topics]').val(); - $.post(saveBtn.data('link'), { + $.post(saveBtn.attr('data-link'), { _csrf: csrfToken, topics }, (_data, _textStatus, xhr) => { From 26095115f4ae90e3fdc6ab695978efd16e317f75 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 6 Jun 2022 16:01:49 +0800 Subject: [PATCH 5/5] Move some repository related code into sub package (#19711) * Move some repository related code into sub package * Move more repository functions out of models * Fix lint * Some performance optimization for webhooks and others * some refactors * Fix lint * Fix * Update modules/repository/delete.go Co-authored-by: delvh * Fix test * Merge * Fix test * Fix test * Fix test * Fix test Co-authored-by: delvh --- cmd/admin.go | 5 +- models/action.go | 2 +- models/asymkey/ssh_key_deploy.go | 7 + models/db/engine.go | 5 - models/issue.go | 58 +- models/issue_list.go | 2 +- models/issue_user.go | 2 +- models/lfs.go | 13 +- models/notification.go | 10 +- models/org.go | 2 +- models/repo.go | 403 +--------- models/repo/attachment_test.go | 33 +- models/repo/collaboration_test.go | 13 +- models/repo/fork.go | 50 ++ models/repo/fork_test.go | 11 +- models/repo/main_test.go | 21 +- models/repo/mirror.go | 13 +- models/repo/pushmirror_test.go | 13 +- models/repo/redirect_test.go | 31 +- models/repo/repo_list.go | 661 +++++++++++++++- models/{ => repo}/repo_list_test.go | 91 +-- models/repo/repo_test.go | 21 +- models/repo/star_test.go | 29 +- models/repo/topic_test.go | 43 +- models/repo/update.go | 8 + models/repo/user_repo.go | 122 +++ models/repo/user_repo_test.go | 74 ++ models/repo/watch_test.go | 87 +-- models/repo/wiki_test.go | 13 +- models/repo_generate.go | 118 --- models/repo_list.go | 704 ------------------ models/repo_test.go | 119 --- models/webhook/webhook.go | 8 + modules/context/repo.go | 5 +- modules/indexer/issues/indexer.go | 4 +- modules/repository/create.go | 115 ++- modules/repository/create_test.go | 21 + modules/repository/delete.go | 33 + modules/repository/fork.go | 31 + modules/repository/generate.go | 65 +- .../repository/generate_test.go | 2 +- modules/repository/init.go | 2 +- modules/repository/repo.go | 25 +- routers/api/v1/repo/collaborators.go | 4 +- routers/api/v1/repo/issue.go | 7 +- routers/api/v1/repo/repo.go | 10 +- routers/api/v1/user/repo.go | 8 +- routers/web/explore/code.go | 4 +- routers/web/explore/repo.go | 3 +- routers/web/org/home.go | 3 +- routers/web/org/setting.go | 5 +- routers/web/repo/attachment.go | 4 +- routers/web/repo/compare.go | 4 +- routers/web/repo/issue.go | 15 +- routers/web/repo/pull.go | 2 +- routers/web/repo/repo.go | 10 +- routers/web/repo/setting.go | 8 +- routers/web/repo/view.go | 3 +- routers/web/user/home.go | 10 +- routers/web/user/home_test.go | 4 +- routers/web/user/package.go | 3 +- routers/web/user/profile.go | 6 +- routers/web/user/setting/profile.go | 5 +- services/gitdiff/gitdiff.go | 2 +- services/mailer/mail_issue.go | 2 +- services/mirror/mirror_pull.go | 3 +- services/repository/adopt.go | 6 +- services/repository/check.go | 3 +- services/repository/fork.go | 10 +- services/repository/hooks.go | 27 + services/repository/push.go | 2 +- services/repository/repository.go | 40 + services/repository/repository_test.go | 43 ++ services/repository/review.go | 24 + services/repository/review_test.go | 28 + services/repository/template.go | 25 +- 76 files changed, 1755 insertions(+), 1673 deletions(-) rename models/{ => repo}/repo_list_test.go (57%) create mode 100644 models/repo/user_repo_test.go delete mode 100644 models/repo_generate.go delete mode 100644 models/repo_list.go create mode 100644 modules/repository/delete.go create mode 100644 modules/repository/fork.go rename models/repo_generate_test.go => modules/repository/generate_test.go (98%) create mode 100644 services/repository/repository_test.go create mode 100644 services/repository/review.go create mode 100644 services/repository/review_test.go diff --git a/cmd/admin.go b/cmd/admin.go index 0629dfc2dbc06..32f9a95a66581 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -17,6 +17,7 @@ import ( asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" + 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/graceful" @@ -722,9 +723,9 @@ func runRepoSyncReleases(_ *cli.Context) error { log.Trace("Synchronizing repository releases (this may take a while)") for page := 1; ; page++ { - repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{ + repos, count, err := repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ - PageSize: models.RepositoryListDefaultPageSize, + PageSize: repo_model.RepositoryListDefaultPageSize, Page: page, }, Private: true, diff --git a/models/action.go b/models/action.go index 87bfcbbbad4b6..882bc59d8f327 100644 --- a/models/action.go +++ b/models/action.go @@ -393,7 +393,7 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) { // check readable repositories by doer/actor if opts.Actor == nil || !opts.Actor.IsAdmin { - cond = cond.And(builder.In("repo_id", AccessibleRepoIDsQuery(opts.Actor))) + cond = cond.And(builder.In("repo_id", repo_model.AccessibleRepoIDsQuery(opts.Actor))) } if opts.RequestedRepo != nil { diff --git a/models/asymkey/ssh_key_deploy.go b/models/asymkey/ssh_key_deploy.go index 22fcefff69a5a..fd8388e61d5a2 100644 --- a/models/asymkey/ssh_key_deploy.go +++ b/models/asymkey/ssh_key_deploy.go @@ -190,6 +190,13 @@ func GetDeployKeyByRepo(ctx context.Context, keyID, repoID int64) (*DeployKey, e return key, nil } +// IsDeployKeyExistByKeyID return true if there is at least one deploykey with the key id +func IsDeployKeyExistByKeyID(ctx context.Context, keyID int64) (bool, error) { + return db.GetEngine(ctx). + Where("key_id = ?", keyID). + Get(new(DeployKey)) +} + // UpdateDeployKeyCols updates deploy key information in the specified columns. func UpdateDeployKeyCols(key *DeployKey, cols ...string) error { _, err := db.GetEngine(db.DefaultContext).ID(key.ID).Cols(cols...).Update(key) diff --git a/models/db/engine.go b/models/db/engine.go index 23eb59dcf5371..8a3b4b206e485 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -271,11 +271,6 @@ func MaxBatchInsertSize(bean interface{}) int { return 999 / len(t.ColumnsSeq()) } -// Count returns records number according struct's fields as database query conditions -func Count(bean interface{}) (int64, error) { - return x.Count(bean) -} - // IsTableNotEmpty returns true if table has at least one record func IsTableNotEmpty(tableName string) (bool, error) { return x.Table(tableName).Exist() diff --git a/models/issue.go b/models/issue.go index 4150d66a65ecf..a22c11552338f 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1343,6 +1343,48 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) { } } +// teamUnitsRepoCond returns query condition for those repo id in the special org team with special units access +func teamUnitsRepoCond(id string, userID, orgID, teamID int64, units ...unit.Type) builder.Cond { + return builder.In(id, + builder.Select("repo_id").From("team_repo").Where( + builder.Eq{ + "team_id": teamID, + }.And( + builder.Or( + // Check if the user is member of the team. + builder.In( + "team_id", builder.Select("team_id").From("team_user").Where( + builder.Eq{ + "uid": userID, + }, + ), + ), + // Check if the user is in the owner team of the organisation. + builder.Exists(builder.Select("team_id").From("team_user"). + Where(builder.Eq{ + "org_id": orgID, + "team_id": builder.Select("id").From("team").Where( + builder.Eq{ + "org_id": orgID, + "lower_name": strings.ToLower(organization.OwnerTeamName), + }), + "uid": userID, + }), + ), + )).And( + builder.In( + "team_id", builder.Select("team_id").From("team_unit").Where( + builder.Eq{ + "`team_unit`.org_id": orgID, + }.And( + builder.In("`team_unit`.type", units), + ), + ), + ), + ), + )) +} + // issuePullAccessibleRepoCond userID must not be zero, this condition require join repository table func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organization.Organization, team *organization.Team, isPull bool) builder.Cond { cond := builder.NewCond() @@ -1356,19 +1398,19 @@ func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organizati } else { cond = cond.And( builder.Or( - userOrgUnitRepoCond(repoIDstr, userID, org.ID, unitType), // team member repos - userOrgPublicUnitRepoCond(userID, org.ID), // user org public non-member repos, TODO: check repo has issues + repo_model.UserOrgUnitRepoCond(repoIDstr, userID, org.ID, unitType), // team member repos + repo_model.UserOrgPublicUnitRepoCond(userID, org.ID), // user org public non-member repos, TODO: check repo has issues ), ) } } else { cond = cond.And( builder.Or( - userOwnedRepoCond(userID), // owned repos - userCollaborationRepoCond(repoIDstr, userID), // collaboration repos - userAssignedRepoCond(repoIDstr, userID), // user has been assigned accessible public repos - userMentionedRepoCond(repoIDstr, userID), // user has been mentioned accessible public repos - userCreateIssueRepoCond(repoIDstr, userID, isPull), // user has created issue/pr accessible public repos + repo_model.UserOwnedRepoCond(userID), // owned repos + repo_model.UserCollaborationRepoCond(repoIDstr, userID), // collaboration repos + repo_model.UserAssignedRepoCond(repoIDstr, userID), // user has been assigned accessible public repos + repo_model.UserMentionedRepoCond(repoIDstr, userID), // user has been mentioned accessible public repos + repo_model.UserCreateIssueRepoCond(repoIDstr, userID, isPull), // user has created issue/pr accessible public repos ), ) } @@ -1434,7 +1476,7 @@ func GetRepoIDsForIssuesOptions(opts *IssuesOptions, user *user_model.User) ([]i opts.setupSessionNoLimit(sess) - accessCond := accessibleRepositoryCondition(user) + accessCond := repo_model.AccessibleRepositoryCondition(user) if err := sess.Where(accessCond). Distinct("issue.repo_id"). Table("issue"). diff --git a/models/issue_list.go b/models/issue_list.go index 31588c02a471a..a5fc095e12b5c 100644 --- a/models/issue_list.go +++ b/models/issue_list.go @@ -75,7 +75,7 @@ func (issues IssueList) loadRepositories(ctx context.Context) ([]*repo_model.Rep } } } - return valuesRepository(repoMaps), nil + return repo_model.ValuesRepository(repoMaps), nil } // LoadRepositories loads issues' all repositories diff --git a/models/issue_user.go b/models/issue_user.go index 0b1f8204ba8b1..19c64094a1640 100644 --- a/models/issue_user.go +++ b/models/issue_user.go @@ -26,7 +26,7 @@ func init() { } func newIssueUsers(ctx context.Context, repo *repo_model.Repository, issue *Issue) error { - assignees, err := getRepoAssignees(ctx, repo) + assignees, err := repo_model.GetRepoAssignees(ctx, repo) if err != nil { return fmt.Errorf("getAssignees: %v", err) } diff --git a/models/lfs.go b/models/lfs.go index 037ed8556b758..d9eea6bb8959e 100644 --- a/models/lfs.go +++ b/models/lfs.go @@ -142,7 +142,7 @@ func LFSObjectAccessible(user *user_model.User, oid string) (bool, error) { count, err := db.GetEngine(db.DefaultContext).Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}) return count > 0, err } - cond := accessibleRepositoryCondition(user) + cond := repo_model.AccessibleRepositoryCondition(user) count, err := db.GetEngine(db.DefaultContext).Where(cond).Join("INNER", "repository", "`lfs_meta_object`.repository_id = `repository`.id").Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}) return count > 0, err } @@ -173,7 +173,7 @@ func LFSAutoAssociate(metas []*LFSMetaObject, user *user_model.User, repoID int6 newMetas := make([]*LFSMetaObject, 0, len(metas)) cond := builder.In( "`lfs_meta_object`.repository_id", - builder.Select("`repository`.id").From("repository").Where(accessibleRepositoryCondition(user)), + builder.Select("`repository`.id").From("repository").Where(repo_model.AccessibleRepositoryCondition(user)), ) err = sess.Cols("oid").Where(cond).In("oid", oids...).GroupBy("oid").Find(&newMetas) if err != nil { @@ -246,3 +246,12 @@ func CopyLFS(ctx context.Context, newRepo, oldRepo *repo_model.Repository) error return nil } + +// GetRepoLFSSize return a repository's lfs files size +func GetRepoLFSSize(ctx context.Context, repoID int64) (int64, error) { + lfsSize, err := db.GetEngine(ctx).Where("repository_id = ?", repoID).SumInt(new(LFSMetaObject), "size") + if err != nil { + return 0, fmt.Errorf("updateSize: GetLFSMetaObjects: %v", err) + } + return lfsSize, nil +} diff --git a/models/notification.go b/models/notification.go index 548362d190c6a..ac5abc6f92bc8 100644 --- a/models/notification.go +++ b/models/notification.go @@ -266,10 +266,10 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n return err } - if issue.IsPull && !checkRepoUnitUser(ctx, issue.Repo, user, unit.TypePullRequests) { + if issue.IsPull && !CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypePullRequests) { continue } - if !issue.IsPull && !checkRepoUnitUser(ctx, issue.Repo, user, unit.TypeIssues) { + if !issue.IsPull && !CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypeIssues) { continue } @@ -510,9 +510,9 @@ func (nl NotificationList) getPendingRepoIDs() []int64 { } // LoadRepos loads repositories from database -func (nl NotificationList) LoadRepos() (RepositoryList, []int, error) { +func (nl NotificationList) LoadRepos() (repo_model.RepositoryList, []int, error) { if len(nl) == 0 { - return RepositoryList{}, []int{}, nil + return repo_model.RepositoryList{}, []int{}, nil } repoIDs := nl.getPendingRepoIDs() @@ -548,7 +548,7 @@ func (nl NotificationList) LoadRepos() (RepositoryList, []int, error) { failed := []int{} - reposList := make(RepositoryList, 0, len(repoIDs)) + reposList := make(repo_model.RepositoryList, 0, len(repoIDs)) for i, notification := range nl { if notification.Repository == nil { notification.Repository = repos[notification.RepoID] diff --git a/models/org.go b/models/org.go index 681b367f4571a..009fe758b5739 100644 --- a/models/org.go +++ b/models/org.go @@ -54,7 +54,7 @@ func GetUserOrgsList(user *user_model.User) ([]*MinimalOrg, error) { Join("LEFT", builder. Select("id as repo_id, owner_id as repo_owner_id"). From("repository"). - Where(accessibleRepositoryCondition(user)), "`repository`.repo_owner_id = `team`.org_id"). + Where(repo_model.AccessibleRepositoryCondition(user)), "`repository`.repo_owner_id = `team`.org_id"). Where("`team_user`.uid = ?", user.ID). GroupBy(groupByStr) diff --git a/models/repo.go b/models/repo.go index 6c3dca41be062..fff9cc5271f77 100644 --- a/models/repo.go +++ b/models/repo.go @@ -8,11 +8,7 @@ package models import ( "context" "fmt" - "os" - "path" "strconv" - "strings" - "unicode/utf8" _ "image/jpeg" // Needed for jpeg support @@ -47,11 +43,7 @@ func NewRepoContext() { } // CheckRepoUnitUser check whether user could visit the unit of this repository -func CheckRepoUnitUser(repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool { - return checkRepoUnitUser(db.DefaultContext, repo, user, unitType) -} - -func checkRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool { +func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool { if user != nil && user.IsAdmin { return true } @@ -64,241 +56,6 @@ func checkRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *u return perm.CanRead(unitType) } -func getRepoAssignees(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) { - if err = repo.GetOwner(ctx); err != nil { - return nil, err - } - - e := db.GetEngine(ctx) - userIDs := make([]int64, 0, 10) - if err = e.Table("access"). - Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite). - Select("user_id"). - Find(&userIDs); err != nil { - return nil, err - } - - additionalUserIDs := make([]int64, 0, 10) - if err = e.Table("team_user"). - Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id"). - Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id"). - Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite). - Distinct("`team_user`.uid"). - Select("`team_user`.uid"). - Find(&additionalUserIDs); err != nil { - return nil, err - } - - uidMap := map[int64]bool{} - i := 0 - for _, uid := range userIDs { - if uidMap[uid] { - continue - } - uidMap[uid] = true - userIDs[i] = uid - i++ - } - userIDs = userIDs[:i] - userIDs = append(userIDs, additionalUserIDs...) - - for _, uid := range additionalUserIDs { - if uidMap[uid] { - continue - } - userIDs[i] = uid - i++ - } - userIDs = userIDs[:i] - - // Leave a seat for owner itself to append later, but if owner is an organization - // and just waste 1 unit is cheaper than re-allocate memory once. - users := make([]*user_model.User, 0, len(userIDs)+1) - if len(userIDs) > 0 { - if err = e.In("id", userIDs).Find(&users); err != nil { - return nil, err - } - } - if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] { - users = append(users, repo.Owner) - } - - return users, nil -} - -// GetRepoAssignees returns all users that have write access and can be assigned to issues -// of the repository, -func GetRepoAssignees(repo *repo_model.Repository) (_ []*user_model.User, err error) { - return getRepoAssignees(db.DefaultContext, repo) -} - -func getReviewers(ctx context.Context, repo *repo_model.Repository, doerID, posterID int64) ([]*user_model.User, error) { - // Get the owner of the repository - this often already pre-cached and if so saves complexity for the following queries - if err := repo.GetOwner(ctx); err != nil { - return nil, err - } - - cond := builder.And(builder.Neq{"`user`.id": posterID}) - - if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate { - // This a private repository: - // Anyone who can read the repository is a requestable reviewer - - cond = cond.And(builder.In("`user`.id", - builder.Select("user_id").From("access").Where( - builder.Eq{"repo_id": repo.ID}. - And(builder.Gte{"mode": perm.AccessModeRead}), - ), - )) - - if repo.Owner.Type == user_model.UserTypeIndividual && repo.Owner.ID != posterID { - // as private *user* repos don't generate an entry in the `access` table, - // the owner of a private repo needs to be explicitly added. - cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID}) - } - - } else { - // This is a "public" repository: - // Any user that has read access, is a watcher or organization member can be requested to review - cond = cond.And(builder.And(builder.In("`user`.id", - builder.Select("user_id").From("access"). - Where(builder.Eq{"repo_id": repo.ID}. - And(builder.Gte{"mode": perm.AccessModeRead})), - ).Or(builder.In("`user`.id", - builder.Select("user_id").From("watch"). - Where(builder.Eq{"repo_id": repo.ID}. - And(builder.In("mode", repo_model.WatchModeNormal, repo_model.WatchModeAuto))), - ).Or(builder.In("`user`.id", - builder.Select("uid").From("org_user"). - Where(builder.Eq{"org_id": repo.OwnerID}), - ))))) - } - - users := make([]*user_model.User, 0, 8) - return users, db.GetEngine(ctx).Where(cond).OrderBy("name").Find(&users) -} - -// GetReviewers get all users can be requested to review: -// * for private repositories this returns all users that have read access or higher to the repository. -// * for public repositories this returns all users that have read access or higher to the repository, -// all repo watchers and all organization members. -// TODO: may be we should have a busy choice for users to block review request to them. -func GetReviewers(repo *repo_model.Repository, doerID, posterID int64) ([]*user_model.User, error) { - return getReviewers(db.DefaultContext, repo, doerID, posterID) -} - -// GetReviewerTeams get all teams can be requested to review -func GetReviewerTeams(repo *repo_model.Repository) ([]*organization.Team, error) { - if err := repo.GetOwner(db.DefaultContext); err != nil { - return nil, err - } - if !repo.Owner.IsOrganization() { - return nil, nil - } - - teams, err := organization.GetTeamsWithAccessToRepo(db.DefaultContext, repo.OwnerID, repo.ID, perm.AccessModeRead) - if err != nil { - return nil, err - } - - return teams, err -} - -// UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize -func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error { - size, err := util.GetDirectorySize(repo.RepoPath()) - if err != nil { - return fmt.Errorf("updateSize: %v", err) - } - - lfsSize, err := db.GetEngine(ctx).Where("repository_id = ?", repo.ID).SumInt(new(LFSMetaObject), "size") - if err != nil { - return fmt.Errorf("updateSize: GetLFSMetaObjects: %v", err) - } - - repo.Size = size + lfsSize - _, err = db.GetEngine(ctx).ID(repo.ID).Cols("size").NoAutoTime().Update(repo) - return err -} - -// CanUserForkRepo returns true if specified user can fork repository. -func CanUserForkRepo(user *user_model.User, repo *repo_model.Repository) (bool, error) { - if user == nil { - return false, nil - } - if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(user.ID, repo.ID) { - return true, nil - } - ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(user.ID) - if err != nil { - return false, err - } - for _, org := range ownedOrgs { - if repo.OwnerID != org.ID && !repo_model.HasForkedRepo(org.ID, repo.ID) { - return true, nil - } - } - return false, nil -} - -// FindUserOrgForks returns the forked repositories for one user from a repository -func FindUserOrgForks(ctx context.Context, repoID, userID int64) ([]*repo_model.Repository, error) { - cond := builder.And( - builder.Eq{"fork_id": repoID}, - builder.In("owner_id", - builder.Select("org_id"). - From("org_user"). - Where(builder.Eq{"uid": userID}), - ), - ) - - var repos []*repo_model.Repository - return repos, db.GetEngine(ctx).Table("repository").Where(cond).Find(&repos) -} - -// GetForksByUserAndOrgs return forked repos of the user and owned orgs -func GetForksByUserAndOrgs(ctx context.Context, user *user_model.User, repo *repo_model.Repository) ([]*repo_model.Repository, error) { - var repoList []*repo_model.Repository - if user == nil { - return repoList, nil - } - forkedRepo, err := repo_model.GetUserFork(ctx, repo.ID, user.ID) - if err != nil { - return repoList, err - } - if forkedRepo != nil { - repoList = append(repoList, forkedRepo) - } - orgForks, err := FindUserOrgForks(ctx, repo.ID, user.ID) - if err != nil { - return nil, err - } - repoList = append(repoList, orgForks...) - return repoList, nil -} - -// CanUserDelete returns true if user could delete the repository -func CanUserDelete(repo *repo_model.Repository, user *user_model.User) (bool, error) { - if user.IsAdmin || user.ID == repo.OwnerID { - return true, nil - } - - if err := repo.GetOwner(db.DefaultContext); err != nil { - return false, err - } - - if repo.Owner.IsOrganization() { - isOwner, err := organization.OrgFromUser(repo.Owner).IsOwnedBy(user.ID) - if err != nil { - return false, err - } else if isOwner { - return true, nil - } - } - - return false, nil -} - // CreateRepoOptions contains the create repository options type CreateRepoOptions struct { Name string @@ -441,126 +198,6 @@ func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_ return nil } -// CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon... -func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error { - if err := repo.GetOwner(ctx); err != nil { - return err - } - - // Create/Remove git-daemon-export-ok for git-daemon... - daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`) - - isExist, err := util.IsExist(daemonExportFile) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err) - return err - } - - isPublic := !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePublic - if !isPublic && isExist { - if err = util.Remove(daemonExportFile); err != nil { - log.Error("Failed to remove %s: %v", daemonExportFile, err) - } - } else if isPublic && !isExist { - if f, err := os.Create(daemonExportFile); err != nil { - log.Error("Failed to create %s: %v", daemonExportFile, err) - } else { - f.Close() - } - } - - return nil -} - -// IncrementRepoForkNum increment repository fork number -func IncrementRepoForkNum(ctx context.Context, repoID int64) error { - _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID) - return err -} - -// DecrementRepoForkNum decrement repository fork number -func DecrementRepoForkNum(ctx context.Context, repoID int64) error { - _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repoID) - return err -} - -// UpdateRepositoryCtx updates a repository with db context -func UpdateRepositoryCtx(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { - repo.LowerName = strings.ToLower(repo.Name) - - if utf8.RuneCountInString(repo.Description) > 255 { - repo.Description = string([]rune(repo.Description)[:255]) - } - if utf8.RuneCountInString(repo.Website) > 255 { - repo.Website = string([]rune(repo.Website)[:255]) - } - - e := db.GetEngine(ctx) - - if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { - return fmt.Errorf("update: %v", err) - } - - if err = UpdateRepoSize(ctx, repo); err != nil { - log.Error("Failed to update size for repository: %v", err) - } - - if visibilityChanged { - if err = repo.GetOwner(ctx); err != nil { - return fmt.Errorf("getOwner: %v", err) - } - if repo.Owner.IsOrganization() { - // Organization repository need to recalculate access table when visibility is changed. - if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { - return fmt.Errorf("recalculateTeamAccesses: %v", err) - } - } - - // If repo has become private, we need to set its actions to private. - if repo.IsPrivate { - _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&Action{ - IsPrivate: true, - }) - if err != nil { - return err - } - } - - // Create/Remove git-daemon-export-ok for git-daemon... - if err := CheckDaemonExportOK(ctx, repo); err != nil { - return err - } - - forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID) - if err != nil { - return fmt.Errorf("GetRepositoriesByForkID: %v", err) - } - for i := range forkRepos { - forkRepos[i].IsPrivate = repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate - if err = UpdateRepositoryCtx(ctx, forkRepos[i], true); err != nil { - return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err) - } - } - } - - return nil -} - -// UpdateRepository updates a repository -func UpdateRepository(repo *repo_model.Repository, visibilityChanged bool) (err error) { - ctx, committer, err := db.TxContext() - if err != nil { - return err - } - defer committer.Close() - - if err = UpdateRepositoryCtx(ctx, repo, visibilityChanged); err != nil { - return fmt.Errorf("updateRepository: %v", err) - } - - return committer.Commit() -} - // 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 DeleteRepository(doer *user_model.User, uid, repoID int64) error { @@ -1052,14 +689,14 @@ func CheckRepoStats(ctx context.Context) error { continue } - rawResult, err := db.GetEngine(db.DefaultContext).Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) + rawResult, err := e.Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) if err != nil { log.Error("Select count of forks[%d]: %v", repo.ID, err) continue } repo.NumForks = int(parseCountResult(rawResult)) - if err = UpdateRepository(repo, false); err != nil { + if _, err = e.ID(repo.ID).Cols("num_forks").Update(repo); err != nil { log.Error("UpdateRepository[%d]: %v", id, err) continue } @@ -1130,30 +767,6 @@ func DoctorUserStarNum() (err error) { return } -// LinkedRepository returns the linked repo if any -func LinkedRepository(a *repo_model.Attachment) (*repo_model.Repository, unit.Type, error) { - if a.IssueID != 0 { - iss, err := GetIssueByID(a.IssueID) - if err != nil { - return nil, unit.TypeIssues, err - } - repo, err := repo_model.GetRepositoryByID(iss.RepoID) - unitType := unit.TypeIssues - if iss.IsPull { - unitType = unit.TypePullRequests - } - return repo, unitType, err - } else if a.ReleaseID != 0 { - rel, err := GetReleaseByID(db.DefaultContext, a.ReleaseID) - if err != nil { - return nil, unit.TypeReleases, err - } - repo, err := repo_model.GetRepositoryByID(rel.RepoID) - return repo, unit.TypeReleases, err - } - return nil, -1, nil -} - // DeleteDeployKey delete deploy keys func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error { key, err := asymkey_model.GetDeployKeyByID(ctx, id) @@ -1164,8 +777,6 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error return fmt.Errorf("GetDeployKeyByID: %v", err) } - sess := db.GetEngine(ctx) - // Check if user has access to delete this key. if !doer.IsAdmin { repo, err := repo_model.GetRepositoryByIDCtx(ctx, key.RepoID) @@ -1184,14 +795,14 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error } } - if _, err = sess.ID(key.ID).Delete(new(asymkey_model.DeployKey)); err != nil { + if _, err := db.DeleteByBean(ctx, &asymkey_model.DeployKey{ + ID: key.ID, + }); err != nil { return fmt.Errorf("delete deploy key [%d]: %v", key.ID, err) } // Check if this is the last reference to same key content. - has, err := sess. - Where("key_id = ?", key.KeyID). - Get(new(asymkey_model.DeployKey)) + has, err := asymkey_model.IsDeployKeyExistByKeyID(ctx, key.KeyID) if err != nil { return err } else if !has { diff --git a/models/repo/attachment_test.go b/models/repo/attachment_test.go index da486fdb2b0e3..d7c2f529dbcac 100644 --- a/models/repo/attachment_test.go +++ b/models/repo/attachment_test.go @@ -2,12 +2,13 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package repo +package repo_test import ( "testing" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -16,7 +17,7 @@ import ( func TestIncreaseDownloadCount(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - attachment, err := GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") + attachment, err := repo_model.GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") assert.NoError(t, err) assert.Equal(t, int64(0), attachment.DownloadCount) @@ -24,7 +25,7 @@ func TestIncreaseDownloadCount(t *testing.T) { err = attachment.IncreaseDownloadCount() assert.NoError(t, err) - attachment, err = GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") + attachment, err = repo_model.GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") assert.NoError(t, err) assert.Equal(t, int64(1), attachment.DownloadCount) } @@ -33,11 +34,11 @@ func TestGetByCommentOrIssueID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) // count of attachments from issue ID - attachments, err := GetAttachmentsByIssueID(db.DefaultContext, 1) + attachments, err := repo_model.GetAttachmentsByIssueID(db.DefaultContext, 1) assert.NoError(t, err) assert.Len(t, attachments, 1) - attachments, err = GetAttachmentsByCommentID(db.DefaultContext, 1) + attachments, err = repo_model.GetAttachmentsByCommentID(db.DefaultContext, 1) assert.NoError(t, err) assert.Len(t, attachments, 2) } @@ -45,33 +46,33 @@ func TestGetByCommentOrIssueID(t *testing.T) { func TestDeleteAttachments(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - count, err := DeleteAttachmentsByIssue(4, false) + count, err := repo_model.DeleteAttachmentsByIssue(4, false) assert.NoError(t, err) assert.Equal(t, 2, count) - count, err = DeleteAttachmentsByComment(2, false) + count, err = repo_model.DeleteAttachmentsByComment(2, false) assert.NoError(t, err) assert.Equal(t, 2, count) - err = DeleteAttachment(&Attachment{ID: 8}, false) + err = repo_model.DeleteAttachment(&repo_model.Attachment{ID: 8}, false) assert.NoError(t, err) - attachment, err := GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18") + attachment, err := repo_model.GetAttachmentByUUID(db.DefaultContext, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18") assert.Error(t, err) - assert.True(t, IsErrAttachmentNotExist(err)) + assert.True(t, repo_model.IsErrAttachmentNotExist(err)) assert.Nil(t, attachment) } func TestGetAttachmentByID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - attach, err := GetAttachmentByID(db.DefaultContext, 1) + attach, err := repo_model.GetAttachmentByID(db.DefaultContext, 1) assert.NoError(t, err) assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.UUID) } func TestAttachment_DownloadURL(t *testing.T) { - attach := &Attachment{ + attach := &repo_model.Attachment{ UUID: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", ID: 1, } @@ -81,20 +82,20 @@ func TestAttachment_DownloadURL(t *testing.T) { func TestUpdateAttachment(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - attach, err := GetAttachmentByID(db.DefaultContext, 1) + attach, err := repo_model.GetAttachmentByID(db.DefaultContext, 1) assert.NoError(t, err) assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.UUID) attach.Name = "new_name" - assert.NoError(t, UpdateAttachment(db.DefaultContext, attach)) + assert.NoError(t, repo_model.UpdateAttachment(db.DefaultContext, attach)) - unittest.AssertExistsAndLoadBean(t, &Attachment{Name: "new_name"}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{Name: "new_name"}) } func TestGetAttachmentsByUUIDs(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - attachList, err := GetAttachmentsByUUIDs(db.DefaultContext, []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"}) + attachList, err := repo_model.GetAttachmentsByUUIDs(db.DefaultContext, []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"}) assert.NoError(t, err) assert.Len(t, attachList, 2) assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attachList[0].UUID) diff --git a/models/repo/collaboration_test.go b/models/repo/collaboration_test.go index a7d04498e9db5..8cb7980a75a2b 100644 --- a/models/repo/collaboration_test.go +++ b/models/repo/collaboration_test.go @@ -2,12 +2,13 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package repo +package repo_test import ( "testing" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -16,10 +17,10 @@ import ( func TestRepository_GetCollaborators(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) test := func(repoID int64) { - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) - collaborators, err := GetCollaborators(db.DefaultContext, repo.ID, db.ListOptions{}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository) + collaborators, err := repo_model.GetCollaborators(db.DefaultContext, repo.ID, db.ListOptions{}) assert.NoError(t, err) - expectedLen, err := db.GetEngine(db.DefaultContext).Count(&Collaboration{RepoID: repoID}) + expectedLen, err := db.GetEngine(db.DefaultContext).Count(&repo_model.Collaboration{RepoID: repoID}) assert.NoError(t, err) assert.Len(t, collaborators, int(expectedLen)) for _, collaborator := range collaborators { @@ -37,8 +38,8 @@ func TestRepository_IsCollaborator(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) test := func(repoID, userID int64, expected bool) { - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) - actual, err := IsCollaborator(db.DefaultContext, repo.ID, userID) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository) + actual, err := repo_model.IsCollaborator(db.DefaultContext, repo.ID, userID) assert.NoError(t, err) assert.Equal(t, expected, actual) } diff --git a/models/repo/fork.go b/models/repo/fork.go index b48126253c47f..938bbae17e16b 100644 --- a/models/repo/fork.go +++ b/models/repo/fork.go @@ -8,6 +8,8 @@ import ( "context" "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "xorm.io/builder" ) // GetRepositoriesByForkID returns all repositories with given fork ID. @@ -63,3 +65,51 @@ func GetForks(repo *Repository, listOptions db.ListOptions) ([]*Repository, erro forks := make([]*Repository, 0, listOptions.PageSize) return forks, sess.Find(&forks, &Repository{ForkID: repo.ID}) } + +// IncrementRepoForkNum increment repository fork number +func IncrementRepoForkNum(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID) + return err +} + +// DecrementRepoForkNum decrement repository fork number +func DecrementRepoForkNum(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repoID) + return err +} + +// FindUserOrgForks returns the forked repositories for one user from a repository +func FindUserOrgForks(ctx context.Context, repoID, userID int64) ([]*Repository, error) { + cond := builder.And( + builder.Eq{"fork_id": repoID}, + builder.In("owner_id", + builder.Select("org_id"). + From("org_user"). + Where(builder.Eq{"uid": userID}), + ), + ) + + var repos []*Repository + return repos, db.GetEngine(ctx).Table("repository").Where(cond).Find(&repos) +} + +// GetForksByUserAndOrgs return forked repos of the user and owned orgs +func GetForksByUserAndOrgs(ctx context.Context, user *user_model.User, repo *Repository) ([]*Repository, error) { + var repoList []*Repository + if user == nil { + return repoList, nil + } + forkedRepo, err := GetUserFork(ctx, repo.ID, user.ID) + if err != nil { + return repoList, err + } + if forkedRepo != nil { + repoList = append(repoList, forkedRepo) + } + orgForks, err := FindUserOrgForks(ctx, repo.ID, user.ID) + if err != nil { + return nil, err + } + repoList = append(repoList, orgForks...) + return repoList, nil +} diff --git a/models/repo/fork_test.go b/models/repo/fork_test.go index 263aec4e3ab1d..9e08d8136e2f6 100644 --- a/models/repo/fork_test.go +++ b/models/repo/fork_test.go @@ -2,12 +2,13 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package repo +package repo_test import ( "testing" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -17,17 +18,17 @@ func TestGetUserFork(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) // User13 has repo 11 forked from repo10 - repo, err := GetRepositoryByID(10) + repo, err := repo_model.GetRepositoryByID(10) assert.NoError(t, err) assert.NotNil(t, repo) - repo, err = GetUserFork(db.DefaultContext, repo.ID, 13) + repo, err = repo_model.GetUserFork(db.DefaultContext, repo.ID, 13) assert.NoError(t, err) assert.NotNil(t, repo) - repo, err = GetRepositoryByID(9) + repo, err = repo_model.GetRepositoryByID(9) assert.NoError(t, err) assert.NotNil(t, repo) - repo, err = GetUserFork(db.DefaultContext, repo.ID, 13) + repo, err = repo_model.GetUserFork(db.DefaultContext, repo.ID, 13) assert.NoError(t, err) assert.Nil(t, repo) } diff --git a/models/repo/main_test.go b/models/repo/main_test.go index 375d0e0df1afd..eb04aa8227d8d 100644 --- a/models/repo/main_test.go +++ b/models/repo/main_test.go @@ -2,31 +2,22 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package repo +package repo_test import ( "path/filepath" "testing" + _ "code.gitea.io/gitea/models" // register table model + _ "code.gitea.io/gitea/models/perm/access" // register table model + _ "code.gitea.io/gitea/models/repo" // register table model + _ "code.gitea.io/gitea/models/user" // register table model + "code.gitea.io/gitea/models/unittest" ) func TestMain(m *testing.M) { unittest.MainTest(m, &unittest.TestOptions{ GiteaRootPath: filepath.Join("..", ".."), - FixtureFiles: []string{ - "attachment.yml", - "repo_archiver.yml", - "repository.yml", - "repo_unit.yml", - "repo_indexer_status.yml", - "repo_redirect.yml", - "watch.yml", - "star.yml", - "topic.yml", - "repo_topic.yml", - "user.yml", - "collaboration.yml", - }, }) } diff --git a/models/repo/mirror.go b/models/repo/mirror.go index 5d20b7f83343d..bd83d244245d9 100644 --- a/models/repo/mirror.go +++ b/models/repo/mirror.go @@ -123,8 +123,8 @@ func MirrorsIterate(limit int, f func(idx int, bean interface{}) error) error { } // InsertMirror inserts a mirror to database -func InsertMirror(mirror *Mirror) error { - _, err := db.GetEngine(db.DefaultContext).Insert(mirror) +func InsertMirror(ctx context.Context, mirror *Mirror) error { + _, err := db.GetEngine(ctx).Insert(mirror) return err } @@ -168,3 +168,12 @@ func (repos MirrorRepositoryList) loadAttributes(ctx context.Context) error { func (repos MirrorRepositoryList) LoadAttributes() error { return repos.loadAttributes(db.DefaultContext) } + +// GetUserMirrorRepositories returns a list of mirror repositories of given user. +func GetUserMirrorRepositories(userID int64) ([]*Repository, error) { + repos := make([]*Repository, 0, 10) + return repos, db.GetEngine(db.DefaultContext). + Where("owner_id = ?", userID). + And("is_mirror = ?", true). + Find(&repos) +} diff --git a/models/repo/pushmirror_test.go b/models/repo/pushmirror_test.go index 83cf86131f4e2..d36a48547e1c6 100644 --- a/models/repo/pushmirror_test.go +++ b/models/repo/pushmirror_test.go @@ -2,12 +2,13 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package repo +package repo_test import ( "testing" "time" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/timeutil" @@ -19,20 +20,20 @@ func TestPushMirrorsIterate(t *testing.T) { now := timeutil.TimeStampNow() - InsertPushMirror(&PushMirror{ + repo_model.InsertPushMirror(&repo_model.PushMirror{ RemoteName: "test-1", LastUpdateUnix: now, Interval: 1, }) long, _ := time.ParseDuration("24h") - InsertPushMirror(&PushMirror{ + repo_model.InsertPushMirror(&repo_model.PushMirror{ RemoteName: "test-2", LastUpdateUnix: now, Interval: long, }) - InsertPushMirror(&PushMirror{ + repo_model.InsertPushMirror(&repo_model.PushMirror{ RemoteName: "test-3", LastUpdateUnix: now, Interval: 0, @@ -40,8 +41,8 @@ func TestPushMirrorsIterate(t *testing.T) { time.Sleep(1 * time.Millisecond) - PushMirrorsIterate(1, func(idx int, bean interface{}) error { - m, ok := bean.(*PushMirror) + repo_model.PushMirrorsIterate(1, func(idx int, bean interface{}) error { + m, ok := bean.(*repo_model.PushMirror) assert.True(t, ok) assert.Equal(t, "test-1", m.RemoteName) assert.Equal(t, m.RemoteName, m.GetRemoteName()) diff --git a/models/repo/redirect_test.go b/models/repo/redirect_test.go index 2dca2cbbfd050..05b105cf63ff2 100644 --- a/models/repo/redirect_test.go +++ b/models/repo/redirect_test.go @@ -2,12 +2,13 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package repo +package repo_test import ( "testing" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -16,27 +17,27 @@ import ( func TestLookupRedirect(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - repoID, err := LookupRedirect(2, "oldrepo1") + repoID, err := repo_model.LookupRedirect(2, "oldrepo1") assert.NoError(t, err) assert.EqualValues(t, 1, repoID) - _, err = LookupRedirect(unittest.NonexistentID, "doesnotexist") - assert.True(t, IsErrRedirectNotExist(err)) + _, err = repo_model.LookupRedirect(unittest.NonexistentID, "doesnotexist") + assert.True(t, repo_model.IsErrRedirectNotExist(err)) } func TestNewRedirect(t *testing.T) { // redirect to a completely new name assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - assert.NoError(t, NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "newreponame")) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + assert.NoError(t, repo_model.NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "newreponame")) - unittest.AssertExistsAndLoadBean(t, &Redirect{ + unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ OwnerID: repo.OwnerID, LowerName: repo.LowerName, RedirectRepoID: repo.ID, }) - unittest.AssertExistsAndLoadBean(t, &Redirect{ + unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ OwnerID: repo.OwnerID, LowerName: "oldrepo1", RedirectRepoID: repo.ID, @@ -47,15 +48,15 @@ func TestNewRedirect2(t *testing.T) { // redirect to previously used name assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - assert.NoError(t, NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "oldrepo1")) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + assert.NoError(t, repo_model.NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "oldrepo1")) - unittest.AssertExistsAndLoadBean(t, &Redirect{ + unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ OwnerID: repo.OwnerID, LowerName: repo.LowerName, RedirectRepoID: repo.ID, }) - unittest.AssertNotExistsBean(t, &Redirect{ + unittest.AssertNotExistsBean(t, &repo_model.Redirect{ OwnerID: repo.OwnerID, LowerName: "oldrepo1", RedirectRepoID: repo.ID, @@ -66,10 +67,10 @@ func TestNewRedirect3(t *testing.T) { // redirect for a previously-unredirected repo assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) - assert.NoError(t, NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "newreponame")) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) + assert.NoError(t, repo_model.NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "newreponame")) - unittest.AssertExistsAndLoadBean(t, &Redirect{ + unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ OwnerID: repo.OwnerID, LowerName: repo.LowerName, RedirectRepoID: repo.ID, diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index 23cdd6cad66f3..1bec35d5710e8 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -5,18 +5,21 @@ package repo import ( + "context" + "fmt" + "strings" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/setting" -) + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" -// GetUserMirrorRepositories returns a list of mirror repositories of given user. -func GetUserMirrorRepositories(userID int64) ([]*Repository, error) { - repos := make([]*Repository, 0, 10) - return repos, db.GetEngine(db.DefaultContext). - Where("owner_id = ?", userID). - And("is_mirror = ?", true). - Find(&repos) -} + "xorm.io/builder" +) // IterateRepository iterate repositories func IterateRepository(f func(repo *Repository) error) error { @@ -45,3 +48,643 @@ func IterateRepository(f func(repo *Repository) error) error { func FindReposMapByIDs(repoIDs []int64, res map[int64]*Repository) error { return db.GetEngine(db.DefaultContext).In("id", repoIDs).Find(&res) } + +// RepositoryListDefaultPageSize is the default number of repositories +// to load in memory when running administrative tasks on all (or almost +// all) of them. +// The number should be low enough to avoid filling up all RAM with +// repository data... +const RepositoryListDefaultPageSize = 64 + +// RepositoryList contains a list of repositories +type RepositoryList []*Repository + +func (repos RepositoryList) Len() int { + return len(repos) +} + +func (repos RepositoryList) Less(i, j int) bool { + return repos[i].FullName() < repos[j].FullName() +} + +func (repos RepositoryList) Swap(i, j int) { + repos[i], repos[j] = repos[j], repos[i] +} + +// ValuesRepository converts a repository map to a list +// FIXME: Remove in favor of maps.values when MIN_GO_VERSION >= 1.18 +func ValuesRepository(m map[int64]*Repository) []*Repository { + values := make([]*Repository, 0, len(m)) + for _, v := range m { + values = append(values, v) + } + return values +} + +// RepositoryListOfMap make list from values of map +func RepositoryListOfMap(repoMap map[int64]*Repository) RepositoryList { + return RepositoryList(ValuesRepository(repoMap)) +} + +func (repos RepositoryList) loadAttributes(ctx context.Context) error { + if len(repos) == 0 { + return nil + } + + set := make(map[int64]struct{}) + repoIDs := make([]int64, len(repos)) + for i := range repos { + set[repos[i].OwnerID] = struct{}{} + repoIDs[i] = repos[i].ID + } + + // Load owners. + users := make(map[int64]*user_model.User, len(set)) + if err := db.GetEngine(ctx). + Where("id > 0"). + In("id", container.KeysInt64(set)). + Find(&users); err != nil { + return fmt.Errorf("find users: %v", err) + } + for i := range repos { + repos[i].Owner = users[repos[i].OwnerID] + } + + // Load primary language. + stats := make(LanguageStatList, 0, len(repos)) + if err := db.GetEngine(ctx). + Where("`is_primary` = ? AND `language` != ?", true, "other"). + In("`repo_id`", repoIDs). + Find(&stats); err != nil { + return fmt.Errorf("find primary languages: %v", err) + } + stats.LoadAttributes() + for i := range repos { + for _, st := range stats { + if st.RepoID == repos[i].ID { + repos[i].PrimaryLanguage = st + break + } + } + } + + return nil +} + +// LoadAttributes loads the attributes for the given RepositoryList +func (repos RepositoryList) LoadAttributes() error { + return repos.loadAttributes(db.DefaultContext) +} + +// SearchRepoOptions holds the search options +type SearchRepoOptions struct { + db.ListOptions + Actor *user_model.User + Keyword string + OwnerID int64 + PriorityOwnerID int64 + TeamID int64 + OrderBy db.SearchOrderBy + Private bool // Include private repositories in results + StarredByID int64 + WatchedByID int64 + AllPublic bool // Include also all public repositories of users and public organisations + AllLimited bool // Include also all public repositories of limited organisations + // None -> include public and private + // True -> include just private + // False -> include just public + IsPrivate util.OptionalBool + // None -> include collaborative AND non-collaborative + // True -> include just collaborative + // False -> include just non-collaborative + Collaborate util.OptionalBool + // None -> include forks AND non-forks + // True -> include just forks + // False -> include just non-forks + Fork util.OptionalBool + // None -> include templates AND non-templates + // True -> include just templates + // False -> include just non-templates + Template util.OptionalBool + // None -> include mirrors AND non-mirrors + // True -> include just mirrors + // False -> include just non-mirrors + Mirror util.OptionalBool + // None -> include archived AND non-archived + // True -> include just archived + // False -> include just non-archived + Archived util.OptionalBool + // only search topic name + TopicOnly bool + // only search repositories with specified primary language + Language string + // include description in keyword search + IncludeDescription bool + // None -> include has milestones AND has no milestone + // True -> include just has milestones + // False -> include just has no milestone + HasMilestones util.OptionalBool + // LowerNames represents valid lower names to restrict to + LowerNames []string +} + +// SearchOrderBy is used to sort the result +type SearchOrderBy string + +func (s SearchOrderBy) String() string { + return string(s) +} + +// Strings for sorting result +const ( + SearchOrderByAlphabetically SearchOrderBy = "name ASC" + SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC" + SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC" + SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC" + SearchOrderByOldest SearchOrderBy = "created_unix ASC" + SearchOrderByNewest SearchOrderBy = "created_unix DESC" + SearchOrderBySize SearchOrderBy = "size ASC" + SearchOrderBySizeReverse SearchOrderBy = "size DESC" + SearchOrderByID SearchOrderBy = "id ASC" + SearchOrderByIDReverse SearchOrderBy = "id DESC" + SearchOrderByStars SearchOrderBy = "num_stars ASC" + SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC" + SearchOrderByForks SearchOrderBy = "num_forks ASC" + SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" +) + +// UserOwnedRepoCond returns user ownered repositories +func UserOwnedRepoCond(userID int64) builder.Cond { + return builder.Eq{ + "repository.owner_id": userID, + } +} + +// UserAssignedRepoCond return user as assignee repositories list +func UserAssignedRepoCond(id string, userID int64) builder.Cond { + return builder.And( + builder.Eq{ + "repository.is_private": false, + }, + builder.In(id, + builder.Select("issue.repo_id").From("issue_assignees"). + InnerJoin("issue", "issue.id = issue_assignees.issue_id"). + Where(builder.Eq{ + "issue_assignees.assignee_id": userID, + }), + ), + ) +} + +// UserCreateIssueRepoCond return user created issues repositories list +func UserCreateIssueRepoCond(id string, userID int64, isPull bool) builder.Cond { + return builder.And( + builder.Eq{ + "repository.is_private": false, + }, + builder.In(id, + builder.Select("issue.repo_id").From("issue"). + Where(builder.Eq{ + "issue.poster_id": userID, + "issue.is_pull": isPull, + }), + ), + ) +} + +// UserMentionedRepoCond return user metinoed repositories list +func UserMentionedRepoCond(id string, userID int64) builder.Cond { + return builder.And( + builder.Eq{ + "repository.is_private": false, + }, + builder.In(id, + builder.Select("issue.repo_id").From("issue_user"). + InnerJoin("issue", "issue.id = issue_user.issue_id"). + Where(builder.Eq{ + "issue_user.is_mentioned": true, + "issue_user.uid": userID, + }), + ), + ) +} + +// UserCollaborationRepoCond returns user as collabrators repositories list +func UserCollaborationRepoCond(idStr string, userID int64) builder.Cond { + return builder.In(idStr, builder.Select("repo_id"). + From("`access`"). + Where(builder.And( + builder.Eq{"`access`.user_id": userID}, + builder.Gt{"`access`.mode": int(perm.AccessModeNone)}, + )), + ) +} + +// userOrgTeamRepoCond selects repos that the given user has access to through team membership +func userOrgTeamRepoCond(idStr string, userID int64) builder.Cond { + return builder.In(idStr, userOrgTeamRepoBuilder(userID)) +} + +// userOrgTeamRepoBuilder returns repo ids where user's teams can access. +func userOrgTeamRepoBuilder(userID int64) *builder.Builder { + return builder.Select("`team_repo`.repo_id"). + From("team_repo"). + Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id"). + Where(builder.Eq{"`team_user`.uid": userID}) +} + +// userOrgTeamUnitRepoBuilder returns repo ids where user's teams can access the special unit. +func userOrgTeamUnitRepoBuilder(userID int64, unitType unit.Type) *builder.Builder { + return userOrgTeamRepoBuilder(userID). + Join("INNER", "team_unit", "`team_unit`.team_id = `team_repo`.team_id"). + Where(builder.Eq{"`team_unit`.`type`": unitType}) +} + +// UserOrgUnitRepoCond selects repos that the given user has access to through org and the special unit +func UserOrgUnitRepoCond(idStr string, userID, orgID int64, unitType unit.Type) builder.Cond { + return builder.In(idStr, + userOrgTeamUnitRepoBuilder(userID, unitType). + And(builder.Eq{"`team_unit`.org_id": orgID}), + ) +} + +// userOrgPublicRepoCond returns the condition that one user could access all public repositories in organizations +func userOrgPublicRepoCond(userID int64) builder.Cond { + return builder.And( + builder.Eq{"`repository`.is_private": false}, + builder.In("`repository`.owner_id", + builder.Select("`org_user`.org_id"). + From("org_user"). + Where(builder.Eq{"`org_user`.uid": userID}), + ), + ) +} + +// userOrgPublicRepoCondPrivate returns the condition that one user could access all public repositories in private organizations +func userOrgPublicRepoCondPrivate(userID int64) builder.Cond { + return builder.And( + builder.Eq{"`repository`.is_private": false}, + builder.In("`repository`.owner_id", + builder.Select("`org_user`.org_id"). + From("org_user"). + Join("INNER", "`user`", "`user`.id = `org_user`.org_id"). + Where(builder.Eq{ + "`org_user`.uid": userID, + "`user`.`type`": user_model.UserTypeOrganization, + "`user`.visibility": structs.VisibleTypePrivate, + }), + ), + ) +} + +// UserOrgPublicUnitRepoCond returns the condition that one user could access all public repositories in the special organization +func UserOrgPublicUnitRepoCond(userID, orgID int64) builder.Cond { + return userOrgPublicRepoCond(userID). + And(builder.Eq{"`repository`.owner_id": orgID}) +} + +// SearchRepositoryCondition creates a query condition according search repository options +func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { + cond := builder.NewCond() + + if opts.Private { + if opts.Actor != nil && !opts.Actor.IsAdmin && opts.Actor.ID != opts.OwnerID { + // OK we're in the context of a User + cond = cond.And(AccessibleRepositoryCondition(opts.Actor)) + } + } else { + // Not looking at private organisations and users + // We should be able to see all non-private repositories that + // isn't in a private or limited organisation. + cond = cond.And( + builder.Eq{"is_private": false}, + builder.NotIn("owner_id", builder.Select("id").From("`user`").Where( + builder.Or(builder.Eq{"visibility": structs.VisibleTypeLimited}, builder.Eq{"visibility": structs.VisibleTypePrivate}), + ))) + } + + if opts.IsPrivate != util.OptionalBoolNone { + cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.IsTrue()}) + } + + if opts.Template != util.OptionalBoolNone { + cond = cond.And(builder.Eq{"is_template": opts.Template == util.OptionalBoolTrue}) + } + + // Restrict to starred repositories + if opts.StarredByID > 0 { + cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID}))) + } + + // Restrict to watched repositories + if opts.WatchedByID > 0 { + cond = cond.And(builder.In("id", builder.Select("repo_id").From("watch").Where(builder.Eq{"user_id": opts.WatchedByID}))) + } + + // Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate + if opts.OwnerID > 0 { + accessCond := builder.NewCond() + if opts.Collaborate != util.OptionalBoolTrue { + accessCond = builder.Eq{"owner_id": opts.OwnerID} + } + + if opts.Collaborate != util.OptionalBoolFalse { + // A Collaboration is: + collaborateCond := builder.And( + // 1. Repository we don't own + builder.Neq{"owner_id": opts.OwnerID}, + // 2. But we can see because of: + builder.Or( + // A. We have access + UserCollaborationRepoCond("`repository`.id", opts.OwnerID), + // B. We are in a team for + userOrgTeamRepoCond("`repository`.id", opts.OwnerID), + // C. Public repositories in organizations that we are member of + userOrgPublicRepoCondPrivate(opts.OwnerID), + ), + ) + if !opts.Private { + collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false)) + } + + accessCond = accessCond.Or(collaborateCond) + } + + if opts.AllPublic { + accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})))) + } + + if opts.AllLimited { + accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypeLimited})))) + } + + cond = cond.And(accessCond) + } + + if opts.TeamID > 0 { + cond = cond.And(builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").From("team_repo").Where(builder.Eq{"`team_repo`.team_id": opts.TeamID}))) + } + + if opts.Keyword != "" { + // separate keyword + subQueryCond := builder.NewCond() + for _, v := range strings.Split(opts.Keyword, ",") { + if opts.TopicOnly { + subQueryCond = subQueryCond.Or(builder.Eq{"topic.name": strings.ToLower(v)}) + } else { + subQueryCond = subQueryCond.Or(builder.Like{"topic.name", strings.ToLower(v)}) + } + } + subQuery := builder.Select("repo_topic.repo_id").From("repo_topic"). + Join("INNER", "topic", "topic.id = repo_topic.topic_id"). + Where(subQueryCond). + GroupBy("repo_topic.repo_id") + + keywordCond := builder.In("id", subQuery) + if !opts.TopicOnly { + likes := builder.NewCond() + for _, v := range strings.Split(opts.Keyword, ",") { + likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) + + // If the string looks like "org/repo", match against that pattern too + if opts.TeamID == 0 && strings.Count(opts.Keyword, "/") == 1 { + pieces := strings.Split(opts.Keyword, "/") + ownerName := pieces[0] + repoName := pieces[1] + likes = likes.Or(builder.And(builder.Like{"owner_name", strings.ToLower(ownerName)}, builder.Like{"lower_name", strings.ToLower(repoName)})) + } + + if opts.IncludeDescription { + likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)}) + } + } + keywordCond = keywordCond.Or(likes) + } + cond = cond.And(keywordCond) + } + + if opts.Language != "" { + cond = cond.And(builder.In("id", builder. + Select("repo_id"). + From("language_stat"). + Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true}))) + } + + if opts.Fork != util.OptionalBoolNone { + cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue}) + } + + if opts.Mirror != util.OptionalBoolNone { + cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue}) + } + + if opts.Actor != nil && opts.Actor.IsRestricted { + cond = cond.And(AccessibleRepositoryCondition(opts.Actor)) + } + + if opts.Archived != util.OptionalBoolNone { + cond = cond.And(builder.Eq{"is_archived": opts.Archived == util.OptionalBoolTrue}) + } + + switch opts.HasMilestones { + case util.OptionalBoolTrue: + cond = cond.And(builder.Gt{"num_milestones": 0}) + case util.OptionalBoolFalse: + cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"})) + } + + return cond +} + +// SearchRepository returns repositories based on search options, +// it returns results in given range and number of total results. +func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) { + cond := SearchRepositoryCondition(opts) + return SearchRepositoryByCondition(opts, cond, true) +} + +// SearchRepositoryByCondition search repositories by condition +func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loadAttributes bool) (RepositoryList, int64, error) { + ctx := db.DefaultContext + sess, count, err := searchRepositoryByCondition(ctx, opts, cond) + if err != nil { + return nil, 0, err + } + + defaultSize := 50 + if opts.PageSize > 0 { + defaultSize = opts.PageSize + } + repos := make(RepositoryList, 0, defaultSize) + if err := sess.Find(&repos); err != nil { + return nil, 0, fmt.Errorf("Repo: %v", err) + } + + if opts.PageSize <= 0 { + count = int64(len(repos)) + } + + if loadAttributes { + if err := repos.loadAttributes(ctx); err != nil { + return nil, 0, fmt.Errorf("LoadAttributes: %v", err) + } + } + + return repos, count, nil +} + +func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, cond builder.Cond) (db.Engine, int64, error) { + if opts.Page <= 0 { + opts.Page = 1 + } + + if len(opts.OrderBy) == 0 { + opts.OrderBy = db.SearchOrderByAlphabetically + } + + args := make([]interface{}, 0) + if opts.PriorityOwnerID > 0 { + opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = ? THEN 0 ELSE owner_id END, %s", opts.OrderBy)) + args = append(args, opts.PriorityOwnerID) + } else if strings.Count(opts.Keyword, "/") == 1 { + // With "owner/repo" search times, prioritise results which match the owner field + orgName := strings.Split(opts.Keyword, "/")[0] + opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_name LIKE ? THEN 0 ELSE 1 END, %s", opts.OrderBy)) + args = append(args, orgName) + } + + sess := db.GetEngine(ctx) + + var count int64 + if opts.PageSize > 0 { + var err error + count, err = sess. + Where(cond). + Count(new(Repository)) + if err != nil { + return nil, 0, fmt.Errorf("Count: %v", err) + } + } + + sess = sess.Where(cond).OrderBy(opts.OrderBy.String(), args...) + if opts.PageSize > 0 { + sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) + } + return sess, count, nil +} + +// AccessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible +func AccessibleRepositoryCondition(user *user_model.User) builder.Cond { + cond := builder.NewCond() + + if user == nil || !user.IsRestricted || user.ID <= 0 { + orgVisibilityLimit := []structs.VisibleType{structs.VisibleTypePrivate} + if user == nil || user.ID <= 0 { + orgVisibilityLimit = append(orgVisibilityLimit, structs.VisibleTypeLimited) + } + // 1. Be able to see all non-private repositories that either: + cond = cond.Or(builder.And( + builder.Eq{"`repository`.is_private": false}, + // 2. Aren't in an private organisation or limited organisation if we're not logged in + builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where( + builder.And( + builder.Eq{"type": user_model.UserTypeOrganization}, + builder.In("visibility", orgVisibilityLimit)), + )))) + } + + if user != nil { + cond = cond.Or( + // 2. Be able to see all repositories that we have access to + UserCollaborationRepoCond("`repository`.id", user.ID), + // 3. Repositories that we directly own + builder.Eq{"`repository`.owner_id": user.ID}, + // 4. Be able to see all repositories that we are in a team + userOrgTeamRepoCond("`repository`.id", user.ID), + // 5. Be able to see all public repos in private organizations that we are an org_user of + userOrgPublicRepoCond(user.ID), + ) + } + + return cond +} + +// SearchRepositoryByName takes keyword and part of repository name to search, +// it returns results in given range and number of total results. +func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, error) { + opts.IncludeDescription = false + return SearchRepository(opts) +} + +// SearchRepositoryIDs takes keyword and part of repository name to search, +// it returns results in given range and number of total results. +func SearchRepositoryIDs(opts *SearchRepoOptions) ([]int64, int64, error) { + opts.IncludeDescription = false + + cond := SearchRepositoryCondition(opts) + + sess, count, err := searchRepositoryByCondition(db.DefaultContext, opts, cond) + if err != nil { + return nil, 0, err + } + + defaultSize := 50 + if opts.PageSize > 0 { + defaultSize = opts.PageSize + } + + ids := make([]int64, 0, defaultSize) + err = sess.Select("id").Table("repository").Find(&ids) + if opts.PageSize <= 0 { + count = int64(len(ids)) + } + + return ids, count, err +} + +// AccessibleRepoIDsQuery queries accessible repository ids. Usable as a subquery wherever repo ids need to be filtered. +func AccessibleRepoIDsQuery(user *user_model.User) *builder.Builder { + // NB: Please note this code needs to still work if user is nil + return builder.Select("id").From("repository").Where(AccessibleRepositoryCondition(user)) +} + +// FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id +func FindUserAccessibleRepoIDs(user *user_model.User) ([]int64, error) { + repoIDs := make([]int64, 0, 10) + if err := db.GetEngine(db.DefaultContext). + Table("repository"). + Cols("id"). + Where(AccessibleRepositoryCondition(user)). + Find(&repoIDs); err != nil { + return nil, fmt.Errorf("FindUserAccesibleRepoIDs: %v", err) + } + return repoIDs, nil +} + +// GetUserRepositories returns a list of repositories of given user. +func GetUserRepositories(opts *SearchRepoOptions) (RepositoryList, int64, error) { + if len(opts.OrderBy) == 0 { + opts.OrderBy = "updated_unix DESC" + } + + cond := builder.NewCond() + cond = cond.And(builder.Eq{"owner_id": opts.Actor.ID}) + if !opts.Private { + cond = cond.And(builder.Eq{"is_private": false}) + } + + if opts.LowerNames != nil && len(opts.LowerNames) > 0 { + cond = cond.And(builder.In("lower_name", opts.LowerNames)) + } + + sess := db.GetEngine(db.DefaultContext) + + count, err := sess.Where(cond).Count(new(Repository)) + if err != nil { + return nil, 0, fmt.Errorf("Count: %v", err) + } + + sess = sess.Where(cond).OrderBy(opts.OrderBy.String()) + repos := make(RepositoryList, 0, opts.PageSize) + return repos, count, db.SetSessionPagination(sess, opts).Find(&repos) +} diff --git a/models/repo_list_test.go b/models/repo/repo_list_test.go similarity index 57% rename from models/repo_list_test.go rename to models/repo/repo_list_test.go index d45e10fb80de1..f9c84a0f3f910 100644 --- a/models/repo_list_test.go +++ b/models/repo/repo_list_test.go @@ -2,13 +2,14 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package repo_test import ( "strings" "testing" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/util" @@ -19,7 +20,7 @@ func TestSearchRepository(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) // test search public repository on explore page - repos, count, err := SearchRepositoryByName(&SearchRepoOptions{ + repos, count, err := repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ Page: 1, PageSize: 10, @@ -34,7 +35,7 @@ func TestSearchRepository(t *testing.T) { } assert.Equal(t, int64(1), count) - repos, count, err = SearchRepositoryByName(&SearchRepoOptions{ + repos, count, err = repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ Page: 1, PageSize: 10, @@ -48,7 +49,7 @@ func TestSearchRepository(t *testing.T) { assert.Len(t, repos, 2) // test search private repository on explore page - repos, count, err = SearchRepositoryByName(&SearchRepoOptions{ + repos, count, err = repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ Page: 1, PageSize: 10, @@ -64,7 +65,7 @@ func TestSearchRepository(t *testing.T) { } assert.Equal(t, int64(1), count) - repos, count, err = SearchRepositoryByName(&SearchRepoOptions{ + repos, count, err = repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ Page: 1, PageSize: 10, @@ -79,14 +80,14 @@ func TestSearchRepository(t *testing.T) { assert.Len(t, repos, 3) // Test non existing owner - repos, count, err = SearchRepositoryByName(&SearchRepoOptions{OwnerID: unittest.NonexistentID}) + repos, count, err = repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{OwnerID: unittest.NonexistentID}) assert.NoError(t, err) assert.Empty(t, repos) assert.Equal(t, int64(0), count) // Test search within description - repos, count, err = SearchRepository(&SearchRepoOptions{ + repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ Page: 1, PageSize: 10, @@ -103,7 +104,7 @@ func TestSearchRepository(t *testing.T) { assert.Equal(t, int64(1), count) // Test NOT search within description - repos, count, err = SearchRepository(&SearchRepoOptions{ + repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ Page: 1, PageSize: 10, @@ -119,164 +120,164 @@ func TestSearchRepository(t *testing.T) { testCases := []struct { name string - opts *SearchRepoOptions + opts *repo_model.SearchRepoOptions count int }{ { name: "PublicRepositoriesByName", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, Collaborate: util.OptionalBoolFalse}, count: 7, }, { name: "PublicAndPrivateRepositoriesByName", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, Collaborate: util.OptionalBoolFalse}, count: 14, }, { name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFirstPage", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, count: 14, }, { name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitSecondPage", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 2, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 2, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, count: 14, }, { name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitThirdPage", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, count: 14, }, { name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFourthPage", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, count: 14, }, { name: "PublicRepositoriesOfUser", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Collaborate: util.OptionalBoolFalse}, count: 2, }, { name: "PublicRepositoriesOfUser2", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Collaborate: util.OptionalBoolFalse}, count: 0, }, { name: "PublicRepositoriesOfUser3", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: util.OptionalBoolFalse}, count: 2, }, { name: "PublicAndPrivateRepositoriesOfUser", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, Collaborate: util.OptionalBoolFalse}, count: 4, }, { name: "PublicAndPrivateRepositoriesOfUser2", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, Collaborate: util.OptionalBoolFalse}, count: 0, }, { name: "PublicAndPrivateRepositoriesOfUser3", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: util.OptionalBoolFalse}, count: 4, }, { name: "PublicRepositoriesOfUserIncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15}, count: 5, }, { name: "PublicRepositoriesOfUser2IncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18}, count: 1, }, { name: "PublicRepositoriesOfUser3IncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20}, count: 3, }, { name: "PublicAndPrivateRepositoriesOfUserIncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true}, count: 9, }, { name: "PublicAndPrivateRepositoriesOfUser2IncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true}, count: 4, }, { name: "PublicAndPrivateRepositoriesOfUser3IncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true}, count: 7, }, { name: "PublicRepositoriesOfOrganization", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Collaborate: util.OptionalBoolFalse}, count: 1, }, { name: "PublicAndPrivateRepositoriesOfOrganization", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Private: true, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Private: true, Collaborate: util.OptionalBoolFalse}, count: 2, }, { name: "AllPublic/PublicRepositoriesByName", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, AllPublic: true, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, AllPublic: true, Collaborate: util.OptionalBoolFalse}, count: 7, }, { name: "AllPublic/PublicAndPrivateRepositoriesByName", - opts: &SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, AllPublic: true, Collaborate: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, AllPublic: true, Collaborate: util.OptionalBoolFalse}, count: 14, }, { name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse}, count: 28, }, { name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse}, count: 33, }, { name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", - opts: &SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true}, + opts: &repo_model.SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true}, count: 15, }, { name: "AllPublic/PublicAndPrivateRepositoriesOfUser2IncludingCollaborativeByName", - opts: &SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, AllPublic: true}, + opts: &repo_model.SearchRepoOptions{Keyword: "test", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, AllPublic: true}, count: 13, }, { name: "AllPublic/PublicRepositoriesOfOrganization", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse}, count: 28, }, { name: "AllTemplates", - opts: &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: util.OptionalBoolTrue}, + opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: util.OptionalBoolTrue}, count: 2, }, { name: "OwnerSlashRepoSearch", - opts: &SearchRepoOptions{Keyword: "user/repo2", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0}, + opts: &repo_model.SearchRepoOptions{Keyword: "user/repo2", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0}, count: 3, }, { name: "OwnerSlashSearch", - opts: &SearchRepoOptions{Keyword: "user20/", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0}, + opts: &repo_model.SearchRepoOptions{Keyword: "user20/", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0}, count: 4, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - repos, count, err := SearchRepositoryByName(testCase.opts) + repos, count, err := repo_model.SearchRepositoryByName(testCase.opts) assert.NoError(t, err) assert.Equal(t, int64(testCase.count), count) @@ -354,29 +355,29 @@ func TestSearchRepositoryByTopicName(t *testing.T) { testCases := []struct { name string - opts *SearchRepoOptions + opts *repo_model.SearchRepoOptions count int }{ { name: "AllPublic/SearchPublicRepositoriesFromTopicAndName", - opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql"}, + opts: &repo_model.SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql"}, count: 2, }, { name: "AllPublic/OnlySearchPublicRepositoriesFromTopic", - opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql", TopicOnly: true}, + opts: &repo_model.SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql", TopicOnly: true}, count: 1, }, { name: "AllPublic/OnlySearchMultipleKeywordPublicRepositoriesFromTopic", - opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql,golang", TopicOnly: true}, + opts: &repo_model.SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql,golang", TopicOnly: true}, count: 2, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - _, count, err := SearchRepositoryByName(testCase.opts) + _, count, err := repo_model.SearchRepositoryByName(testCase.opts) assert.NoError(t, err) assert.Equal(t, int64(testCase.count), count) }) diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go index cf6ee8b67a6fc..8ae84eab52c76 100644 --- a/models/repo/repo_test.go +++ b/models/repo/repo_test.go @@ -2,12 +2,13 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package repo +package repo_test import ( "testing" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/util" @@ -15,18 +16,18 @@ import ( ) var ( - countRepospts = CountRepositoryOptions{OwnerID: 10} - countReposptsPublic = CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolFalse} - countReposptsPrivate = CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolTrue} + countRepospts = repo_model.CountRepositoryOptions{OwnerID: 10} + countReposptsPublic = repo_model.CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolFalse} + countReposptsPrivate = repo_model.CountRepositoryOptions{OwnerID: 10, Private: util.OptionalBoolTrue} ) func TestGetRepositoryCount(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) ctx := db.DefaultContext - count, err1 := CountRepositories(ctx, countRepospts) - privateCount, err2 := CountRepositories(ctx, countReposptsPrivate) - publicCount, err3 := CountRepositories(ctx, countReposptsPublic) + count, err1 := repo_model.CountRepositories(ctx, countRepospts) + privateCount, err2 := repo_model.CountRepositories(ctx, countReposptsPrivate) + publicCount, err3 := repo_model.CountRepositories(ctx, countReposptsPublic) assert.NoError(t, err1) assert.NoError(t, err2) assert.NoError(t, err3) @@ -37,7 +38,7 @@ func TestGetRepositoryCount(t *testing.T) { func TestGetPublicRepositoryCount(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - count, err := CountRepositories(db.DefaultContext, countReposptsPublic) + count, err := repo_model.CountRepositories(db.DefaultContext, countReposptsPublic) assert.NoError(t, err) assert.Equal(t, int64(1), count) } @@ -45,14 +46,14 @@ func TestGetPublicRepositoryCount(t *testing.T) { func TestGetPrivateRepositoryCount(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - count, err := CountRepositories(db.DefaultContext, countReposptsPrivate) + count, err := repo_model.CountRepositories(db.DefaultContext, countReposptsPrivate) assert.NoError(t, err) assert.Equal(t, int64(2), count) } func TestRepoAPIURL(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}).(*repo_model.Repository) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user12/repo10", repo.APIURL()) } diff --git a/models/repo/star_test.go b/models/repo/star_test.go index 2dde09c74514b..aa72b1dac8c0d 100644 --- a/models/repo/star_test.go +++ b/models/repo/star_test.go @@ -2,12 +2,13 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package repo +package repo_test import ( "testing" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -17,26 +18,26 @@ func TestStarRepo(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) const userID = 2 const repoID = 1 - unittest.AssertNotExistsBean(t, &Star{UID: userID, RepoID: repoID}) - assert.NoError(t, StarRepo(userID, repoID, true)) - unittest.AssertExistsAndLoadBean(t, &Star{UID: userID, RepoID: repoID}) - assert.NoError(t, StarRepo(userID, repoID, true)) - unittest.AssertExistsAndLoadBean(t, &Star{UID: userID, RepoID: repoID}) - assert.NoError(t, StarRepo(userID, repoID, false)) - unittest.AssertNotExistsBean(t, &Star{UID: userID, RepoID: repoID}) + unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) + assert.NoError(t, repo_model.StarRepo(userID, repoID, true)) + unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) + assert.NoError(t, repo_model.StarRepo(userID, repoID, true)) + unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) + assert.NoError(t, repo_model.StarRepo(userID, repoID, false)) + unittest.AssertNotExistsBean(t, &repo_model.Star{UID: userID, RepoID: repoID}) } func TestIsStaring(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.True(t, IsStaring(db.DefaultContext, 2, 4)) - assert.False(t, IsStaring(db.DefaultContext, 3, 4)) + assert.True(t, repo_model.IsStaring(db.DefaultContext, 2, 4)) + assert.False(t, repo_model.IsStaring(db.DefaultContext, 3, 4)) } func TestRepository_GetStargazers(t *testing.T) { // repo with stargazers assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) - gazers, err := GetStargazers(repo, db.ListOptions{Page: 0}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository) + gazers, err := repo_model.GetStargazers(repo, db.ListOptions{Page: 0}) assert.NoError(t, err) if assert.Len(t, gazers, 1) { assert.Equal(t, int64(2), gazers[0].ID) @@ -46,8 +47,8 @@ func TestRepository_GetStargazers(t *testing.T) { func TestRepository_GetStargazers2(t *testing.T) { // repo with stargazers assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) - gazers, err := GetStargazers(repo, db.ListOptions{Page: 0}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) + gazers, err := repo_model.GetStargazers(repo, db.ListOptions{Page: 0}) assert.NoError(t, err) assert.Len(t, gazers, 0) } diff --git a/models/repo/topic_test.go b/models/repo/topic_test.go index 353d96ef3e2fd..8187addb81fe4 100644 --- a/models/repo/topic_test.go +++ b/models/repo/topic_test.go @@ -2,12 +2,13 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package repo +package repo_test import ( "testing" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -19,47 +20,47 @@ func TestAddTopic(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - topics, _, err := FindTopics(&FindTopicOptions{}) + topics, _, err := repo_model.FindTopics(&repo_model.FindTopicOptions{}) assert.NoError(t, err) assert.Len(t, topics, totalNrOfTopics) - topics, total, err := FindTopics(&FindTopicOptions{ + topics, total, err := repo_model.FindTopics(&repo_model.FindTopicOptions{ ListOptions: db.ListOptions{Page: 1, PageSize: 2}, }) assert.NoError(t, err) assert.Len(t, topics, 2) assert.EqualValues(t, 6, total) - topics, _, err = FindTopics(&FindTopicOptions{ + topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{ RepoID: 1, }) assert.NoError(t, err) assert.Len(t, topics, repo1NrOfTopics) - assert.NoError(t, SaveTopics(2, "golang")) + assert.NoError(t, repo_model.SaveTopics(2, "golang")) repo2NrOfTopics := 1 - topics, _, err = FindTopics(&FindTopicOptions{}) + topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{}) assert.NoError(t, err) assert.Len(t, topics, totalNrOfTopics) - topics, _, err = FindTopics(&FindTopicOptions{ + topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{ RepoID: 2, }) assert.NoError(t, err) assert.Len(t, topics, repo2NrOfTopics) - assert.NoError(t, SaveTopics(2, "golang", "gitea")) + assert.NoError(t, repo_model.SaveTopics(2, "golang", "gitea")) repo2NrOfTopics = 2 totalNrOfTopics++ - topic, err := GetTopicByName("gitea") + topic, err := repo_model.GetTopicByName("gitea") assert.NoError(t, err) assert.EqualValues(t, 1, topic.RepoCount) - topics, _, err = FindTopics(&FindTopicOptions{}) + topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{}) assert.NoError(t, err) assert.Len(t, topics, totalNrOfTopics) - topics, _, err = FindTopics(&FindTopicOptions{ + topics, _, err = repo_model.FindTopics(&repo_model.FindTopicOptions{ RepoID: 2, }) assert.NoError(t, err) @@ -67,14 +68,14 @@ func TestAddTopic(t *testing.T) { } func TestTopicValidator(t *testing.T) { - assert.True(t, ValidateTopic("12345")) - assert.True(t, ValidateTopic("2-test")) - assert.True(t, ValidateTopic("test-3")) - assert.True(t, ValidateTopic("first")) - assert.True(t, ValidateTopic("second-test-topic")) - assert.True(t, ValidateTopic("third-project-topic-with-max-length")) - - assert.False(t, ValidateTopic("$fourth-test,topic")) - assert.False(t, ValidateTopic("-fifth-test-topic")) - assert.False(t, ValidateTopic("sixth-go-project-topic-with-excess-length")) + assert.True(t, repo_model.ValidateTopic("12345")) + assert.True(t, repo_model.ValidateTopic("2-test")) + assert.True(t, repo_model.ValidateTopic("test-3")) + assert.True(t, repo_model.ValidateTopic("first")) + assert.True(t, repo_model.ValidateTopic("second-test-topic")) + assert.True(t, repo_model.ValidateTopic("third-project-topic-with-max-length")) + + assert.False(t, repo_model.ValidateTopic("$fourth-test,topic")) + assert.False(t, repo_model.ValidateTopic("-fifth-test-topic")) + assert.False(t, repo_model.ValidateTopic("sixth-go-project-topic-with-excess-length")) } diff --git a/models/repo/update.go b/models/repo/update.go index 7fb51c9593ccf..07776ebc01941 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -172,3 +172,11 @@ func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName s return committer.Commit() } + +// UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize +func UpdateRepoSize(ctx context.Context, repoID, size int64) error { + _, err := db.GetEngine(ctx).ID(repoID).Cols("size").NoAutoTime().Update(&Repository{ + Size: size, + }) + return err +} diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go index fe9677179662c..e697505b81e4e 100644 --- a/models/repo/user_repo.go +++ b/models/repo/user_repo.go @@ -5,7 +5,14 @@ package repo import ( + "context" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/perm" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + + "xorm.io/builder" ) // GetStarredRepos returns the repos starred by a particular user @@ -48,3 +55,118 @@ func GetWatchedRepos(userID int64, private bool, listOptions db.ListOptions) ([] total, err := sess.FindAndCount(&repos) return repos, total, err } + +// GetRepoAssignees returns all users that have write access and can be assigned to issues +// of the repository, +func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.User, err error) { + if err = repo.GetOwner(ctx); err != nil { + return nil, err + } + + e := db.GetEngine(ctx) + userIDs := make([]int64, 0, 10) + if err = e.Table("access"). + Where("repo_id = ? AND mode >= ?", repo.ID, perm.AccessModeWrite). + Select("user_id"). + Find(&userIDs); err != nil { + return nil, err + } + + additionalUserIDs := make([]int64, 0, 10) + if err = e.Table("team_user"). + Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id"). + Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id"). + Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite). + Distinct("`team_user`.uid"). + Select("`team_user`.uid"). + Find(&additionalUserIDs); err != nil { + return nil, err + } + + uidMap := map[int64]bool{} + i := 0 + for _, uid := range userIDs { + if uidMap[uid] { + continue + } + uidMap[uid] = true + userIDs[i] = uid + i++ + } + userIDs = userIDs[:i] + userIDs = append(userIDs, additionalUserIDs...) + + for _, uid := range additionalUserIDs { + if uidMap[uid] { + continue + } + userIDs[i] = uid + i++ + } + userIDs = userIDs[:i] + + // Leave a seat for owner itself to append later, but if owner is an organization + // and just waste 1 unit is cheaper than re-allocate memory once. + users := make([]*user_model.User, 0, len(userIDs)+1) + if len(userIDs) > 0 { + if err = e.In("id", userIDs).Find(&users); err != nil { + return nil, err + } + } + if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] { + users = append(users, repo.Owner) + } + + return users, nil +} + +// GetReviewers get all users can be requested to review: +// * for private repositories this returns all users that have read access or higher to the repository. +// * for public repositories this returns all users that have read access or higher to the repository, +// all repo watchers and all organization members. +// TODO: may be we should have a busy choice for users to block review request to them. +func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64) ([]*user_model.User, error) { + // Get the owner of the repository - this often already pre-cached and if so saves complexity for the following queries + if err := repo.GetOwner(ctx); err != nil { + return nil, err + } + + cond := builder.And(builder.Neq{"`user`.id": posterID}) + + if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate { + // This a private repository: + // Anyone who can read the repository is a requestable reviewer + + cond = cond.And(builder.In("`user`.id", + builder.Select("user_id").From("access").Where( + builder.Eq{"repo_id": repo.ID}. + And(builder.Gte{"mode": perm.AccessModeRead}), + ), + )) + + if repo.Owner.Type == user_model.UserTypeIndividual && repo.Owner.ID != posterID { + // as private *user* repos don't generate an entry in the `access` table, + // the owner of a private repo needs to be explicitly added. + cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID}) + } + + } else { + // This is a "public" repository: + // Any user that has read access, is a watcher or organization member can be requested to review + cond = cond.And(builder.And(builder.In("`user`.id", + builder.Select("user_id").From("access"). + Where(builder.Eq{"repo_id": repo.ID}. + And(builder.Gte{"mode": perm.AccessModeRead})), + ).Or(builder.In("`user`.id", + builder.Select("user_id").From("watch"). + Where(builder.Eq{"repo_id": repo.ID}. + And(builder.In("mode", WatchModeNormal, WatchModeAuto))), + ).Or(builder.In("`user`.id", + builder.Select("uid").From("org_user"). + Where(builder.Eq{"org_id": repo.OwnerID}), + ))))) + } + + users := make([]*user_model.User, 0, 8) + return users, db.GetEngine(ctx).Where(cond).OrderBy("name").Find(&users) +} diff --git a/models/repo/user_repo_test.go b/models/repo/user_repo_test.go new file mode 100644 index 0000000000000..d024729b9cde4 --- /dev/null +++ b/models/repo/user_repo_test.go @@ -0,0 +1,74 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo_test + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestRepoAssignees(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) + users, err := repo_model.GetRepoAssignees(db.DefaultContext, repo2) + assert.NoError(t, err) + assert.Len(t, users, 1) + assert.Equal(t, users[0].ID, int64(2)) + + repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}).(*repo_model.Repository) + users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21) + assert.NoError(t, err) + assert.Len(t, users, 3) + assert.Equal(t, users[0].ID, int64(15)) + assert.Equal(t, users[1].ID, int64(18)) + assert.Equal(t, users[2].ID, int64(16)) +} + +func TestRepoGetReviewers(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // test public repo + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + + ctx := db.DefaultContext + reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2) + assert.NoError(t, err) + assert.Len(t, reviewers, 4) + + // should include doer if doer is not PR poster. + reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 2) + assert.NoError(t, err) + assert.Len(t, reviewers, 4) + + // should not include PR poster, if PR poster would be otherwise eligible + reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 4) + assert.NoError(t, err) + assert.Len(t, reviewers, 3) + + // test private user repo + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) + + reviewers, err = repo_model.GetReviewers(ctx, repo2, 2, 4) + assert.NoError(t, err) + assert.Len(t, reviewers, 1) + assert.EqualValues(t, reviewers[0].ID, 2) + + // test private org repo + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) + + reviewers, err = repo_model.GetReviewers(ctx, repo3, 2, 1) + assert.NoError(t, err) + assert.Len(t, reviewers, 2) + + reviewers, err = repo_model.GetReviewers(ctx, repo3, 2, 2) + assert.NoError(t, err) + assert.Len(t, reviewers, 1) +} diff --git a/models/repo/watch_test.go b/models/repo/watch_test.go index 2f4e04ab17aae..3875e63fd873b 100644 --- a/models/repo/watch_test.go +++ b/models/repo/watch_test.go @@ -2,12 +2,13 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package repo +package repo_test import ( "testing" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/setting" @@ -17,20 +18,20 @@ import ( func TestIsWatching(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.True(t, IsWatching(1, 1)) - assert.True(t, IsWatching(4, 1)) - assert.True(t, IsWatching(11, 1)) + assert.True(t, repo_model.IsWatching(1, 1)) + assert.True(t, repo_model.IsWatching(4, 1)) + assert.True(t, repo_model.IsWatching(11, 1)) - assert.False(t, IsWatching(1, 5)) - assert.False(t, IsWatching(8, 1)) - assert.False(t, IsWatching(unittest.NonexistentID, unittest.NonexistentID)) + assert.False(t, repo_model.IsWatching(1, 5)) + assert.False(t, repo_model.IsWatching(8, 1)) + assert.False(t, repo_model.IsWatching(unittest.NonexistentID, unittest.NonexistentID)) } func TestGetWatchers(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - watches, err := GetWatchers(db.DefaultContext, repo.ID) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + watches, err := repo_model.GetWatchers(db.DefaultContext, repo.ID) assert.NoError(t, err) // One watchers are inactive, thus minus 1 assert.Len(t, watches, repo.NumWatches-1) @@ -38,7 +39,7 @@ func TestGetWatchers(t *testing.T) { assert.EqualValues(t, repo.ID, watch.RepoID) } - watches, err = GetWatchers(db.DefaultContext, unittest.NonexistentID) + watches, err = repo_model.GetWatchers(db.DefaultContext, unittest.NonexistentID) assert.NoError(t, err) assert.Len(t, watches, 0) } @@ -46,16 +47,16 @@ func TestGetWatchers(t *testing.T) { func TestRepository_GetWatchers(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - watchers, err := GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + watchers, err := repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, repo.NumWatches) for _, watcher := range watchers { - unittest.AssertExistsAndLoadBean(t, &Watch{UserID: watcher.ID, RepoID: repo.ID}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{UserID: watcher.ID, RepoID: repo.ID}) } - repo = unittest.AssertExistsAndLoadBean(t, &Repository{ID: 9}).(*Repository) - watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 9}).(*repo_model.Repository) + watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, 0) } @@ -63,8 +64,8 @@ func TestRepository_GetWatchers(t *testing.T) { func TestWatchIfAuto(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) - watchers, err := GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + watchers, err := repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, repo.NumWatches) @@ -73,46 +74,46 @@ func TestWatchIfAuto(t *testing.T) { prevCount := repo.NumWatches // Must not add watch - assert.NoError(t, WatchIfAuto(db.DefaultContext, 8, 1, true)) - watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 8, 1, true)) + watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) // Should not add watch - assert.NoError(t, WatchIfAuto(db.DefaultContext, 10, 1, true)) - watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 10, 1, true)) + watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) setting.Service.AutoWatchOnChanges = true // Must not add watch - assert.NoError(t, WatchIfAuto(db.DefaultContext, 8, 1, true)) - watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 8, 1, true)) + watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) // Should not add watch - assert.NoError(t, WatchIfAuto(db.DefaultContext, 12, 1, false)) - watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 12, 1, false)) + watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) // Should add watch - assert.NoError(t, WatchIfAuto(db.DefaultContext, 12, 1, true)) - watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 12, 1, true)) + watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount+1) // Should remove watch, inhibit from adding auto - assert.NoError(t, WatchRepo(db.DefaultContext, 12, 1, false)) - watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + assert.NoError(t, repo_model.WatchRepo(db.DefaultContext, 12, 1, false)) + watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) // Must not add watch - assert.NoError(t, WatchIfAuto(db.DefaultContext, 12, 1, true)) - watchers, err = GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) + assert.NoError(t, repo_model.WatchIfAuto(db.DefaultContext, 12, 1, true)) + watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) assert.Len(t, watchers, prevCount) } @@ -120,20 +121,20 @@ func TestWatchIfAuto(t *testing.T) { func TestWatchRepoMode(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0) + unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 0) - assert.NoError(t, WatchRepoMode(12, 1, WatchModeAuto)) - unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) - unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: WatchModeAuto}, 1) + assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeAuto)) + unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1) + unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeAuto}, 1) - assert.NoError(t, WatchRepoMode(12, 1, WatchModeNormal)) - unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) - unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: WatchModeNormal}, 1) + assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeNormal)) + unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1) + unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeNormal}, 1) - assert.NoError(t, WatchRepoMode(12, 1, WatchModeDont)) - unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1) - unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: WatchModeDont}, 1) + assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeDont)) + unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1) + unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeDont}, 1) - assert.NoError(t, WatchRepoMode(12, 1, WatchModeNone)) - unittest.AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0) + assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeNone)) + unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 0) } diff --git a/models/repo/wiki_test.go b/models/repo/wiki_test.go index f5e61e5ae3eeb..339289e05d866 100644 --- a/models/repo/wiki_test.go +++ b/models/repo/wiki_test.go @@ -2,12 +2,13 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package repo +package repo_test import ( "path/filepath" "testing" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/setting" @@ -17,7 +18,7 @@ import ( func TestRepository_WikiCloneLink(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) cloneLink := repo.WikiCloneLink() assert.Equal(t, "ssh://sshuser@try.gitea.io:3000/user2/repo1.wiki.git", cloneLink.SSH) assert.Equal(t, "https://try.gitea.io/user2/repo1.wiki.git", cloneLink.HTTPS) @@ -26,20 +27,20 @@ func TestRepository_WikiCloneLink(t *testing.T) { func TestWikiPath(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git") - assert.Equal(t, expected, WikiPath("user2", "repo1")) + assert.Equal(t, expected, repo_model.WikiPath("user2", "repo1")) } func TestRepository_WikiPath(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git") assert.Equal(t, expected, repo.WikiPath()) } func TestRepository_HasWiki(t *testing.T) { unittest.PrepareTestEnv(t) - repo1 := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) assert.True(t, repo1.HasWiki()) - repo2 := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) assert.False(t, repo2.HasWiki()) } diff --git a/models/repo_generate.go b/models/repo_generate.go deleted file mode 100644 index 6b720b4969546..0000000000000 --- a/models/repo_generate.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "bufio" - "bytes" - "context" - "strings" - - "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/webhook" - "code.gitea.io/gitea/modules/log" - - "github.com/gobwas/glob" -) - -// GenerateRepoOptions contains the template units to generate -type GenerateRepoOptions struct { - Name string - DefaultBranch string - Description string - Private bool - GitContent bool - Topics bool - GitHooks bool - Webhooks bool - Avatar bool - IssueLabels bool -} - -// IsValid checks whether at least one option is chosen for generation -func (gro GenerateRepoOptions) IsValid() bool { - return gro.GitContent || gro.Topics || gro.GitHooks || gro.Webhooks || gro.Avatar || gro.IssueLabels // or other items as they are added -} - -// GiteaTemplate holds information about a .gitea/template file -type GiteaTemplate struct { - Path string - Content []byte - - globs []glob.Glob -} - -// Globs parses the .gitea/template globs or returns them if they were already parsed -func (gt GiteaTemplate) Globs() []glob.Glob { - if gt.globs != nil { - return gt.globs - } - - gt.globs = make([]glob.Glob, 0) - scanner := bufio.NewScanner(bytes.NewReader(gt.Content)) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if line == "" || strings.HasPrefix(line, "#") { - continue - } - g, err := glob.Compile(line, '/') - if err != nil { - log.Info("Invalid glob expression '%s' (skipped): %v", line, err) - continue - } - gt.globs = append(gt.globs, g) - } - return gt.globs -} - -// GenerateWebhooks generates webhooks from a template repository -func GenerateWebhooks(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { - templateWebhooks, err := webhook.ListWebhooksByOpts(ctx, &webhook.ListWebhookOptions{RepoID: templateRepo.ID}) - if err != nil { - return err - } - - for _, templateWebhook := range templateWebhooks { - generateWebhook := &webhook.Webhook{ - RepoID: generateRepo.ID, - URL: templateWebhook.URL, - HTTPMethod: templateWebhook.HTTPMethod, - ContentType: templateWebhook.ContentType, - Secret: templateWebhook.Secret, - HookEvent: templateWebhook.HookEvent, - IsActive: templateWebhook.IsActive, - Type: templateWebhook.Type, - OrgID: templateWebhook.OrgID, - Events: templateWebhook.Events, - Meta: templateWebhook.Meta, - } - if err := webhook.CreateWebhook(ctx, generateWebhook); err != nil { - return err - } - } - return nil -} - -// GenerateIssueLabels generates issue labels from a template repository -func GenerateIssueLabels(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { - templateLabels, err := GetLabelsByRepoID(ctx, templateRepo.ID, "", db.ListOptions{}) - if err != nil { - return err - } - - for _, templateLabel := range templateLabels { - generateLabel := &Label{ - RepoID: generateRepo.ID, - Name: templateLabel.Name, - Description: templateLabel.Description, - Color: templateLabel.Color, - } - if err := db.Insert(ctx, generateLabel); err != nil { - return err - } - } - return nil -} diff --git a/models/repo_list.go b/models/repo_list.go deleted file mode 100644 index 45fb10c3642bf..0000000000000 --- a/models/repo_list.go +++ /dev/null @@ -1,704 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package models - -import ( - "context" - "fmt" - "strings" - - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/organization" - "code.gitea.io/gitea/models/perm" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unit" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" - - "xorm.io/builder" -) - -// RepositoryListDefaultPageSize is the default number of repositories -// to load in memory when running administrative tasks on all (or almost -// all) of them. -// The number should be low enough to avoid filling up all RAM with -// repository data... -const RepositoryListDefaultPageSize = 64 - -// RepositoryList contains a list of repositories -type RepositoryList []*repo_model.Repository - -func (repos RepositoryList) Len() int { - return len(repos) -} - -func (repos RepositoryList) Less(i, j int) bool { - return repos[i].FullName() < repos[j].FullName() -} - -func (repos RepositoryList) Swap(i, j int) { - repos[i], repos[j] = repos[j], repos[i] -} - -// FIXME: Remove in favor of maps.values when MIN_GO_VERSION >= 1.18 -func valuesRepository(m map[int64]*repo_model.Repository) []*repo_model.Repository { - values := make([]*repo_model.Repository, 0, len(m)) - for _, v := range m { - values = append(values, v) - } - return values -} - -// RepositoryListOfMap make list from values of map -func RepositoryListOfMap(repoMap map[int64]*repo_model.Repository) RepositoryList { - return RepositoryList(valuesRepository(repoMap)) -} - -func (repos RepositoryList) loadAttributes(ctx context.Context) error { - if len(repos) == 0 { - return nil - } - - set := make(map[int64]struct{}) - repoIDs := make([]int64, len(repos)) - for i := range repos { - set[repos[i].OwnerID] = struct{}{} - repoIDs[i] = repos[i].ID - } - - // Load owners. - users := make(map[int64]*user_model.User, len(set)) - if err := db.GetEngine(ctx). - Where("id > 0"). - In("id", container.KeysInt64(set)). - Find(&users); err != nil { - return fmt.Errorf("find users: %v", err) - } - for i := range repos { - repos[i].Owner = users[repos[i].OwnerID] - } - - // Load primary language. - stats := make(repo_model.LanguageStatList, 0, len(repos)) - if err := db.GetEngine(ctx). - Where("`is_primary` = ? AND `language` != ?", true, "other"). - In("`repo_id`", repoIDs). - Find(&stats); err != nil { - return fmt.Errorf("find primary languages: %v", err) - } - stats.LoadAttributes() - for i := range repos { - for _, st := range stats { - if st.RepoID == repos[i].ID { - repos[i].PrimaryLanguage = st - break - } - } - } - - return nil -} - -// LoadAttributes loads the attributes for the given RepositoryList -func (repos RepositoryList) LoadAttributes() error { - return repos.loadAttributes(db.DefaultContext) -} - -// SearchRepoOptions holds the search options -type SearchRepoOptions struct { - db.ListOptions - Actor *user_model.User - Keyword string - OwnerID int64 - PriorityOwnerID int64 - TeamID int64 - OrderBy db.SearchOrderBy - Private bool // Include private repositories in results - StarredByID int64 - WatchedByID int64 - AllPublic bool // Include also all public repositories of users and public organisations - AllLimited bool // Include also all public repositories of limited organisations - // None -> include public and private - // True -> include just private - // False -> include just public - IsPrivate util.OptionalBool - // None -> include collaborative AND non-collaborative - // True -> include just collaborative - // False -> include just non-collaborative - Collaborate util.OptionalBool - // None -> include forks AND non-forks - // True -> include just forks - // False -> include just non-forks - Fork util.OptionalBool - // None -> include templates AND non-templates - // True -> include just templates - // False -> include just non-templates - Template util.OptionalBool - // None -> include mirrors AND non-mirrors - // True -> include just mirrors - // False -> include just non-mirrors - Mirror util.OptionalBool - // None -> include archived AND non-archived - // True -> include just archived - // False -> include just non-archived - Archived util.OptionalBool - // only search topic name - TopicOnly bool - // only search repositories with specified primary language - Language string - // include description in keyword search - IncludeDescription bool - // None -> include has milestones AND has no milestone - // True -> include just has milestones - // False -> include just has no milestone - HasMilestones util.OptionalBool - // LowerNames represents valid lower names to restrict to - LowerNames []string -} - -// SearchOrderBy is used to sort the result -type SearchOrderBy string - -func (s SearchOrderBy) String() string { - return string(s) -} - -// Strings for sorting result -const ( - SearchOrderByAlphabetically SearchOrderBy = "name ASC" - SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC" - SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC" - SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC" - SearchOrderByOldest SearchOrderBy = "created_unix ASC" - SearchOrderByNewest SearchOrderBy = "created_unix DESC" - SearchOrderBySize SearchOrderBy = "size ASC" - SearchOrderBySizeReverse SearchOrderBy = "size DESC" - SearchOrderByID SearchOrderBy = "id ASC" - SearchOrderByIDReverse SearchOrderBy = "id DESC" - SearchOrderByStars SearchOrderBy = "num_stars ASC" - SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC" - SearchOrderByForks SearchOrderBy = "num_forks ASC" - SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" -) - -// userOwnedRepoCond returns user ownered repositories -func userOwnedRepoCond(userID int64) builder.Cond { - return builder.Eq{ - "repository.owner_id": userID, - } -} - -// userAssignedRepoCond return user as assignee repositories list -func userAssignedRepoCond(id string, userID int64) builder.Cond { - return builder.And( - builder.Eq{ - "repository.is_private": false, - }, - builder.In(id, - builder.Select("issue.repo_id").From("issue_assignees"). - InnerJoin("issue", "issue.id = issue_assignees.issue_id"). - Where(builder.Eq{ - "issue_assignees.assignee_id": userID, - }), - ), - ) -} - -// userCreateIssueRepoCond return user created issues repositories list -func userCreateIssueRepoCond(id string, userID int64, isPull bool) builder.Cond { - return builder.And( - builder.Eq{ - "repository.is_private": false, - }, - builder.In(id, - builder.Select("issue.repo_id").From("issue"). - Where(builder.Eq{ - "issue.poster_id": userID, - "issue.is_pull": isPull, - }), - ), - ) -} - -// userMentionedRepoCond return user metinoed repositories list -func userMentionedRepoCond(id string, userID int64) builder.Cond { - return builder.And( - builder.Eq{ - "repository.is_private": false, - }, - builder.In(id, - builder.Select("issue.repo_id").From("issue_user"). - InnerJoin("issue", "issue.id = issue_user.issue_id"). - Where(builder.Eq{ - "issue_user.is_mentioned": true, - "issue_user.uid": userID, - }), - ), - ) -} - -// teamUnitsRepoCond returns query condition for those repo id in the special org team with special units access -func teamUnitsRepoCond(id string, userID, orgID, teamID int64, units ...unit.Type) builder.Cond { - return builder.In(id, - builder.Select("repo_id").From("team_repo").Where( - builder.Eq{ - "team_id": teamID, - }.And( - builder.Or( - // Check if the user is member of the team. - builder.In( - "team_id", builder.Select("team_id").From("team_user").Where( - builder.Eq{ - "uid": userID, - }, - ), - ), - // Check if the user is in the owner team of the organisation. - builder.Exists(builder.Select("team_id").From("team_user"). - Where(builder.Eq{ - "org_id": orgID, - "team_id": builder.Select("id").From("team").Where( - builder.Eq{ - "org_id": orgID, - "lower_name": strings.ToLower(organization.OwnerTeamName), - }), - "uid": userID, - }), - ), - )).And( - builder.In( - "team_id", builder.Select("team_id").From("team_unit").Where( - builder.Eq{ - "`team_unit`.org_id": orgID, - }.And( - builder.In("`team_unit`.type", units), - ), - ), - ), - ), - )) -} - -// userCollaborationRepoCond returns user as collabrators repositories list -func userCollaborationRepoCond(idStr string, userID int64) builder.Cond { - return builder.In(idStr, builder.Select("repo_id"). - From("`access`"). - Where(builder.And( - builder.Eq{"`access`.user_id": userID}, - builder.Gt{"`access`.mode": int(perm.AccessModeNone)}, - )), - ) -} - -// userOrgTeamRepoCond selects repos that the given user has access to through team membership -func userOrgTeamRepoCond(idStr string, userID int64) builder.Cond { - return builder.In(idStr, userOrgTeamRepoBuilder(userID)) -} - -// userOrgTeamRepoBuilder returns repo ids where user's teams can access. -func userOrgTeamRepoBuilder(userID int64) *builder.Builder { - return builder.Select("`team_repo`.repo_id"). - From("team_repo"). - Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id"). - Where(builder.Eq{"`team_user`.uid": userID}) -} - -// userOrgTeamUnitRepoBuilder returns repo ids where user's teams can access the special unit. -func userOrgTeamUnitRepoBuilder(userID int64, unitType unit.Type) *builder.Builder { - return userOrgTeamRepoBuilder(userID). - Join("INNER", "team_unit", "`team_unit`.team_id = `team_repo`.team_id"). - Where(builder.Eq{"`team_unit`.`type`": unitType}) -} - -// userOrgUnitRepoCond selects repos that the given user has access to through org and the special unit -func userOrgUnitRepoCond(idStr string, userID, orgID int64, unitType unit.Type) builder.Cond { - return builder.In(idStr, - userOrgTeamUnitRepoBuilder(userID, unitType). - And(builder.Eq{"`team_unit`.org_id": orgID}), - ) -} - -// userOrgPublicRepoCond returns the condition that one user could access all public repositories in organizations -func userOrgPublicRepoCond(userID int64) builder.Cond { - return builder.And( - builder.Eq{"`repository`.is_private": false}, - builder.In("`repository`.owner_id", - builder.Select("`org_user`.org_id"). - From("org_user"). - Where(builder.Eq{"`org_user`.uid": userID}), - ), - ) -} - -// userOrgPublicRepoCondPrivate returns the condition that one user could access all public repositories in private organizations -func userOrgPublicRepoCondPrivate(userID int64) builder.Cond { - return builder.And( - builder.Eq{"`repository`.is_private": false}, - builder.In("`repository`.owner_id", - builder.Select("`org_user`.org_id"). - From("org_user"). - Join("INNER", "`user`", "`user`.id = `org_user`.org_id"). - Where(builder.Eq{ - "`org_user`.uid": userID, - "`user`.`type`": user_model.UserTypeOrganization, - "`user`.visibility": structs.VisibleTypePrivate, - }), - ), - ) -} - -// userOrgPublicUnitRepoCond returns the condition that one user could access all public repositories in the special organization -func userOrgPublicUnitRepoCond(userID, orgID int64) builder.Cond { - return userOrgPublicRepoCond(userID). - And(builder.Eq{"`repository`.owner_id": orgID}) -} - -// SearchRepositoryCondition creates a query condition according search repository options -func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { - cond := builder.NewCond() - - if opts.Private { - if opts.Actor != nil && !opts.Actor.IsAdmin && opts.Actor.ID != opts.OwnerID { - // OK we're in the context of a User - cond = cond.And(accessibleRepositoryCondition(opts.Actor)) - } - } else { - // Not looking at private organisations and users - // We should be able to see all non-private repositories that - // isn't in a private or limited organisation. - cond = cond.And( - builder.Eq{"is_private": false}, - builder.NotIn("owner_id", builder.Select("id").From("`user`").Where( - builder.Or(builder.Eq{"visibility": structs.VisibleTypeLimited}, builder.Eq{"visibility": structs.VisibleTypePrivate}), - ))) - } - - if opts.IsPrivate != util.OptionalBoolNone { - cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.IsTrue()}) - } - - if opts.Template != util.OptionalBoolNone { - cond = cond.And(builder.Eq{"is_template": opts.Template == util.OptionalBoolTrue}) - } - - // Restrict to starred repositories - if opts.StarredByID > 0 { - cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID}))) - } - - // Restrict to watched repositories - if opts.WatchedByID > 0 { - cond = cond.And(builder.In("id", builder.Select("repo_id").From("watch").Where(builder.Eq{"user_id": opts.WatchedByID}))) - } - - // Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate - if opts.OwnerID > 0 { - accessCond := builder.NewCond() - if opts.Collaborate != util.OptionalBoolTrue { - accessCond = builder.Eq{"owner_id": opts.OwnerID} - } - - if opts.Collaborate != util.OptionalBoolFalse { - // A Collaboration is: - collaborateCond := builder.And( - // 1. Repository we don't own - builder.Neq{"owner_id": opts.OwnerID}, - // 2. But we can see because of: - builder.Or( - // A. We have access - userCollaborationRepoCond("`repository`.id", opts.OwnerID), - // B. We are in a team for - userOrgTeamRepoCond("`repository`.id", opts.OwnerID), - // C. Public repositories in organizations that we are member of - userOrgPublicRepoCondPrivate(opts.OwnerID), - ), - ) - if !opts.Private { - collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false)) - } - - accessCond = accessCond.Or(collaborateCond) - } - - if opts.AllPublic { - accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})))) - } - - if opts.AllLimited { - accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypeLimited})))) - } - - cond = cond.And(accessCond) - } - - if opts.TeamID > 0 { - cond = cond.And(builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").From("team_repo").Where(builder.Eq{"`team_repo`.team_id": opts.TeamID}))) - } - - if opts.Keyword != "" { - // separate keyword - subQueryCond := builder.NewCond() - for _, v := range strings.Split(opts.Keyword, ",") { - if opts.TopicOnly { - subQueryCond = subQueryCond.Or(builder.Eq{"topic.name": strings.ToLower(v)}) - } else { - subQueryCond = subQueryCond.Or(builder.Like{"topic.name", strings.ToLower(v)}) - } - } - subQuery := builder.Select("repo_topic.repo_id").From("repo_topic"). - Join("INNER", "topic", "topic.id = repo_topic.topic_id"). - Where(subQueryCond). - GroupBy("repo_topic.repo_id") - - keywordCond := builder.In("id", subQuery) - if !opts.TopicOnly { - likes := builder.NewCond() - for _, v := range strings.Split(opts.Keyword, ",") { - likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) - - // If the string looks like "org/repo", match against that pattern too - if opts.TeamID == 0 && strings.Count(opts.Keyword, "/") == 1 { - pieces := strings.Split(opts.Keyword, "/") - ownerName := pieces[0] - repoName := pieces[1] - likes = likes.Or(builder.And(builder.Like{"owner_name", strings.ToLower(ownerName)}, builder.Like{"lower_name", strings.ToLower(repoName)})) - } - - if opts.IncludeDescription { - likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)}) - } - } - keywordCond = keywordCond.Or(likes) - } - cond = cond.And(keywordCond) - } - - if opts.Language != "" { - cond = cond.And(builder.In("id", builder. - Select("repo_id"). - From("language_stat"). - Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true}))) - } - - if opts.Fork != util.OptionalBoolNone { - cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue}) - } - - if opts.Mirror != util.OptionalBoolNone { - cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue}) - } - - if opts.Actor != nil && opts.Actor.IsRestricted { - cond = cond.And(accessibleRepositoryCondition(opts.Actor)) - } - - if opts.Archived != util.OptionalBoolNone { - cond = cond.And(builder.Eq{"is_archived": opts.Archived == util.OptionalBoolTrue}) - } - - switch opts.HasMilestones { - case util.OptionalBoolTrue: - cond = cond.And(builder.Gt{"num_milestones": 0}) - case util.OptionalBoolFalse: - cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"})) - } - - return cond -} - -// SearchRepository returns repositories based on search options, -// it returns results in given range and number of total results. -func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) { - cond := SearchRepositoryCondition(opts) - return SearchRepositoryByCondition(opts, cond, true) -} - -// SearchRepositoryByCondition search repositories by condition -func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loadAttributes bool) (RepositoryList, int64, error) { - ctx := db.DefaultContext - sess, count, err := searchRepositoryByCondition(ctx, opts, cond) - if err != nil { - return nil, 0, err - } - - defaultSize := 50 - if opts.PageSize > 0 { - defaultSize = opts.PageSize - } - repos := make(RepositoryList, 0, defaultSize) - if err := sess.Find(&repos); err != nil { - return nil, 0, fmt.Errorf("Repo: %v", err) - } - - if opts.PageSize <= 0 { - count = int64(len(repos)) - } - - if loadAttributes { - if err := repos.loadAttributes(ctx); err != nil { - return nil, 0, fmt.Errorf("LoadAttributes: %v", err) - } - } - - return repos, count, nil -} - -func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, cond builder.Cond) (db.Engine, int64, error) { - if opts.Page <= 0 { - opts.Page = 1 - } - - if len(opts.OrderBy) == 0 { - opts.OrderBy = db.SearchOrderByAlphabetically - } - - args := make([]interface{}, 0) - if opts.PriorityOwnerID > 0 { - opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = ? THEN 0 ELSE owner_id END, %s", opts.OrderBy)) - args = append(args, opts.PriorityOwnerID) - } else if strings.Count(opts.Keyword, "/") == 1 { - // With "owner/repo" search times, prioritise results which match the owner field - orgName := strings.Split(opts.Keyword, "/")[0] - opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_name LIKE ? THEN 0 ELSE 1 END, %s", opts.OrderBy)) - args = append(args, orgName) - } - - sess := db.GetEngine(ctx) - - var count int64 - if opts.PageSize > 0 { - var err error - count, err = sess. - Where(cond). - Count(new(repo_model.Repository)) - if err != nil { - return nil, 0, fmt.Errorf("Count: %v", err) - } - } - - sess = sess.Where(cond).OrderBy(opts.OrderBy.String(), args...) - if opts.PageSize > 0 { - sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) - } - return sess, count, nil -} - -// accessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible -func accessibleRepositoryCondition(user *user_model.User) builder.Cond { - cond := builder.NewCond() - - if user == nil || !user.IsRestricted || user.ID <= 0 { - orgVisibilityLimit := []structs.VisibleType{structs.VisibleTypePrivate} - if user == nil || user.ID <= 0 { - orgVisibilityLimit = append(orgVisibilityLimit, structs.VisibleTypeLimited) - } - // 1. Be able to see all non-private repositories that either: - cond = cond.Or(builder.And( - builder.Eq{"`repository`.is_private": false}, - // 2. Aren't in an private organisation or limited organisation if we're not logged in - builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where( - builder.And( - builder.Eq{"type": user_model.UserTypeOrganization}, - builder.In("visibility", orgVisibilityLimit)), - )))) - } - - if user != nil { - cond = cond.Or( - // 2. Be able to see all repositories that we have access to - userCollaborationRepoCond("`repository`.id", user.ID), - // 3. Repositories that we directly own - builder.Eq{"`repository`.owner_id": user.ID}, - // 4. Be able to see all repositories that we are in a team - userOrgTeamRepoCond("`repository`.id", user.ID), - // 5. Be able to see all public repos in private organizations that we are an org_user of - userOrgPublicRepoCond(user.ID), - ) - } - - return cond -} - -// SearchRepositoryByName takes keyword and part of repository name to search, -// it returns results in given range and number of total results. -func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, error) { - opts.IncludeDescription = false - return SearchRepository(opts) -} - -// SearchRepositoryIDs takes keyword and part of repository name to search, -// it returns results in given range and number of total results. -func SearchRepositoryIDs(opts *SearchRepoOptions) ([]int64, int64, error) { - opts.IncludeDescription = false - - cond := SearchRepositoryCondition(opts) - - sess, count, err := searchRepositoryByCondition(db.DefaultContext, opts, cond) - if err != nil { - return nil, 0, err - } - - defaultSize := 50 - if opts.PageSize > 0 { - defaultSize = opts.PageSize - } - - ids := make([]int64, 0, defaultSize) - err = sess.Select("id").Table("repository").Find(&ids) - if opts.PageSize <= 0 { - count = int64(len(ids)) - } - - return ids, count, err -} - -// AccessibleRepoIDsQuery queries accessible repository ids. Usable as a subquery wherever repo ids need to be filtered. -func AccessibleRepoIDsQuery(user *user_model.User) *builder.Builder { - // NB: Please note this code needs to still work if user is nil - return builder.Select("id").From("repository").Where(accessibleRepositoryCondition(user)) -} - -// FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id -func FindUserAccessibleRepoIDs(user *user_model.User) ([]int64, error) { - repoIDs := make([]int64, 0, 10) - if err := db.GetEngine(db.DefaultContext). - Table("repository"). - Cols("id"). - Where(accessibleRepositoryCondition(user)). - Find(&repoIDs); err != nil { - return nil, fmt.Errorf("FindUserAccesibleRepoIDs: %v", err) - } - return repoIDs, nil -} - -// GetUserRepositories returns a list of repositories of given user. -func GetUserRepositories(opts *SearchRepoOptions) (RepositoryList, int64, error) { - if len(opts.OrderBy) == 0 { - opts.OrderBy = "updated_unix DESC" - } - - cond := builder.NewCond() - cond = cond.And(builder.Eq{"owner_id": opts.Actor.ID}) - if !opts.Private { - cond = cond.And(builder.Eq{"is_private": false}) - } - - if opts.LowerNames != nil && len(opts.LowerNames) > 0 { - cond = cond.And(builder.In("lower_name", opts.LowerNames)) - } - - sess := db.GetEngine(db.DefaultContext) - - count, err := sess.Where(cond).Count(new(repo_model.Repository)) - if err != nil { - return nil, 0, fmt.Errorf("Count: %v", err) - } - - sess = sess.Where(cond).OrderBy(opts.OrderBy.String()) - repos := make(RepositoryList, 0, opts.PageSize) - return repos, count, db.SetSessionPagination(sess, opts).Find(&repos) -} diff --git a/models/repo_test.go b/models/repo_test.go index dd1673f6bfaa9..c9e66398d1a50 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -84,127 +84,8 @@ func TestMetas(t *testing.T) { assert.Equal(t, ",owners,team1,", metas["teams"]) } -func TestUpdateRepositoryVisibilityChanged(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - // Get sample repo and change visibility - repo, err := repo_model.GetRepositoryByID(9) - assert.NoError(t, err) - repo.IsPrivate = true - - // Update it - err = UpdateRepository(repo, true) - assert.NoError(t, err) - - // Check visibility of action has become private - act := Action{} - _, err = db.GetEngine(db.DefaultContext).ID(3).Get(&act) - - assert.NoError(t, err) - assert.True(t, act.IsPrivate) -} - func TestDoctorUserStarNum(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, DoctorUserStarNum()) } - -func TestRepoGetReviewers(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - // test public repo - repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) - - reviewers, err := GetReviewers(repo1, 2, 2) - assert.NoError(t, err) - assert.Len(t, reviewers, 4) - - // should include doer if doer is not PR poster. - reviewers, err = GetReviewers(repo1, 11, 2) - assert.NoError(t, err) - assert.Len(t, reviewers, 4) - - // should not include PR poster, if PR poster would be otherwise eligible - reviewers, err = GetReviewers(repo1, 11, 4) - assert.NoError(t, err) - assert.Len(t, reviewers, 3) - - // test private user repo - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) - - reviewers, err = GetReviewers(repo2, 2, 4) - assert.NoError(t, err) - assert.Len(t, reviewers, 1) - assert.EqualValues(t, reviewers[0].ID, 2) - - // test private org repo - repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) - - reviewers, err = GetReviewers(repo3, 2, 1) - assert.NoError(t, err) - assert.Len(t, reviewers, 2) - - reviewers, err = GetReviewers(repo3, 2, 2) - assert.NoError(t, err) - assert.Len(t, reviewers, 1) -} - -func TestRepoGetReviewerTeams(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) - teams, err := GetReviewerTeams(repo2) - assert.NoError(t, err) - assert.Empty(t, teams) - - repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) - teams, err = GetReviewerTeams(repo3) - assert.NoError(t, err) - assert.Len(t, teams, 2) -} - -func TestLinkedRepository(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - testCases := []struct { - name string - attachID int64 - expectedRepo *repo_model.Repository - expectedUnitType unit.Type - }{ - {"LinkedIssue", 1, &repo_model.Repository{ID: 1}, unit.TypeIssues}, - {"LinkedComment", 3, &repo_model.Repository{ID: 1}, unit.TypePullRequests}, - {"LinkedRelease", 9, &repo_model.Repository{ID: 1}, unit.TypeReleases}, - {"Notlinked", 10, nil, -1}, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - attach, err := repo_model.GetAttachmentByID(db.DefaultContext, tc.attachID) - assert.NoError(t, err) - repo, unitType, err := LinkedRepository(attach) - assert.NoError(t, err) - if tc.expectedRepo != nil { - assert.Equal(t, tc.expectedRepo.ID, repo.ID) - } - assert.Equal(t, tc.expectedUnitType, unitType) - }) - } -} - -func TestRepoAssignees(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) - users, err := GetRepoAssignees(repo2) - assert.NoError(t, err) - assert.Len(t, users, 1) - assert.Equal(t, users[0].ID, int64(2)) - - repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}).(*repo_model.Repository) - users, err = GetRepoAssignees(repo21) - assert.NoError(t, err) - assert.Len(t, users, 3) - assert.Equal(t, users[0].ID, int64(15)) - assert.Equal(t, users[1].ID, int64(18)) - assert.Equal(t, users[2].ID, int64(16)) -} diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go index 5eea977725961..1b79a414ade52 100644 --- a/models/webhook/webhook.go +++ b/models/webhook/webhook.go @@ -397,6 +397,14 @@ func CreateWebhook(ctx context.Context, w *Webhook) error { return db.Insert(ctx, w) } +// CreateWebhooks creates multiple web hooks +func CreateWebhooks(ctx context.Context, ws []*Webhook) error { + for i := 0; i < len(ws); i++ { + ws[i].Type = strings.TrimSpace(ws[i].Type) + } + return db.Insert(ctx, ws) +} + // getWebhook uses argument bean as query condition, // ID must be specified and do not assign unnecessary fields. func getWebhook(bean *Webhook) (*Webhook, error) { diff --git a/modules/context/repo.go b/modules/context/repo.go index df3fe4e74d538..5f4af114ff223 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -26,6 +26,7 @@ import ( code_indexer "code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup/markdown" + repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -551,14 +552,14 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(unit_model.TypeIssues) ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(unit_model.TypePullRequests) - canSignedUserFork, err := models.CanUserForkRepo(ctx.Doer, ctx.Repo.Repository) + canSignedUserFork, err := repo_module.CanUserForkRepo(ctx.Doer, ctx.Repo.Repository) if err != nil { ctx.ServerError("CanUserForkRepo", err) return } ctx.Data["CanSignedUserFork"] = canSignedUserFork - userAndOrgForks, err := models.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository) + userAndOrgForks, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository) if err != nil { ctx.ServerError("GetForksByUserAndOrgs", err) return diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index 7adc938dccbad..85de4c75b3872 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -291,8 +291,8 @@ func populateIssueIndexer(ctx context.Context) { return default: } - repos, _, err := models.SearchRepositoryByName(&models.SearchRepoOptions{ - ListOptions: db.ListOptions{Page: page, PageSize: models.RepositoryListDefaultPageSize}, + repos, _, err := repo_model.SearchRepositoryByName(&repo_model.SearchRepoOptions{ + ListOptions: db.ListOptions{Page: page, PageSize: repo_model.RepositoryListDefaultPageSize}, OrderBy: db.SearchOrderByID, Private: true, Collaborate: util.OptionalBoolFalse, diff --git a/modules/repository/create.go b/modules/repository/create.go index 21d45c896e650..95bb825403913 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -7,15 +7,20 @@ package repository import ( "context" "fmt" + "os" + "path" "strings" + "unicode/utf8" "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + access_model "code.gitea.io/gitea/models/perm/access" 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/log" "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" ) @@ -108,7 +113,7 @@ func CreateRepository(doer, u *user_model.User, opts models.CreateRepoOptions) ( } } - if err := models.CheckDaemonExportOK(ctx, repo); err != nil { + if err := CheckDaemonExportOK(ctx, repo); err != nil { return fmt.Errorf("checkDaemonExportOK: %v", err) } @@ -133,3 +138,111 @@ func CreateRepository(doer, u *user_model.User, opts models.CreateRepoOptions) ( return repo, nil } + +// UpdateRepoSize updates the repository size, calculating it using util.GetDirectorySize +func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error { + size, err := util.GetDirectorySize(repo.RepoPath()) + if err != nil { + return fmt.Errorf("updateSize: %v", err) + } + + lfsSize, err := models.GetRepoLFSSize(ctx, repo.ID) + if err != nil { + return fmt.Errorf("updateSize: GetLFSMetaObjects: %v", err) + } + + return repo_model.UpdateRepoSize(ctx, repo.ID, size+lfsSize) +} + +// CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon... +func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error { + if err := repo.GetOwner(ctx); err != nil { + return err + } + + // Create/Remove git-daemon-export-ok for git-daemon... + daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`) + + isExist, err := util.IsExist(daemonExportFile) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err) + return err + } + + isPublic := !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePublic + if !isPublic && isExist { + if err = util.Remove(daemonExportFile); err != nil { + log.Error("Failed to remove %s: %v", daemonExportFile, err) + } + } else if isPublic && !isExist { + if f, err := os.Create(daemonExportFile); err != nil { + log.Error("Failed to create %s: %v", daemonExportFile, err) + } else { + f.Close() + } + } + + return nil +} + +// UpdateRepository updates a repository with db context +func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { + repo.LowerName = strings.ToLower(repo.Name) + + if utf8.RuneCountInString(repo.Description) > 255 { + repo.Description = string([]rune(repo.Description)[:255]) + } + if utf8.RuneCountInString(repo.Website) > 255 { + repo.Website = string([]rune(repo.Website)[:255]) + } + + e := db.GetEngine(ctx) + + if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { + return fmt.Errorf("update: %v", err) + } + + if err = UpdateRepoSize(ctx, repo); err != nil { + log.Error("Failed to update size for repository: %v", err) + } + + if visibilityChanged { + if err = repo.GetOwner(ctx); err != nil { + return fmt.Errorf("getOwner: %v", err) + } + if repo.Owner.IsOrganization() { + // Organization repository need to recalculate access table when visibility is changed. + if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { + return fmt.Errorf("recalculateTeamAccesses: %v", err) + } + } + + // If repo has become private, we need to set its actions to private. + if repo.IsPrivate { + _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&models.Action{ + IsPrivate: true, + }) + if err != nil { + return err + } + } + + // Create/Remove git-daemon-export-ok for git-daemon... + if err := CheckDaemonExportOK(db.WithEngine(ctx, e), repo); err != nil { + return err + } + + forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID) + if err != nil { + return fmt.Errorf("getRepositoriesByForkID: %v", err) + } + for i := range forkRepos { + forkRepos[i].IsPrivate = repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate + if err = UpdateRepository(ctx, forkRepos[i], true); err != nil { + return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err) + } + } + } + + return nil +} diff --git a/modules/repository/create_test.go b/modules/repository/create_test.go index b6a89a7ed6c0c..2a47e93631497 100644 --- a/modules/repository/create_test.go +++ b/modules/repository/create_test.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" + 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/structs" @@ -147,3 +148,23 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { } assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization") } + +func TestUpdateRepositoryVisibilityChanged(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // Get sample repo and change visibility + repo, err := repo_model.GetRepositoryByID(9) + assert.NoError(t, err) + repo.IsPrivate = true + + // Update it + err = UpdateRepository(db.DefaultContext, repo, true) + assert.NoError(t, err) + + // Check visibility of action has become private + act := models.Action{} + _, err = db.GetEngine(db.DefaultContext).ID(3).Get(&act) + + assert.NoError(t, err) + assert.True(t, act.IsPrivate) +} diff --git a/modules/repository/delete.go b/modules/repository/delete.go new file mode 100644 index 0000000000000..25fb15e300f30 --- /dev/null +++ b/modules/repository/delete.go @@ -0,0 +1,33 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repository + +import ( + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" +) + +// CanUserDelete returns true if user could delete the repository +func CanUserDelete(repo *repo_model.Repository, user *user_model.User) (bool, error) { + if user.IsAdmin || user.ID == repo.OwnerID { + return true, nil + } + + if err := repo.GetOwner(db.DefaultContext); err != nil { + return false, err + } + + if repo.Owner.IsOrganization() { + isOwner, err := organization.OrgFromUser(repo.Owner).IsOwnedBy(user.ID) + if err != nil { + return false, err + } + return isOwner, nil + } + + return false, nil +} diff --git a/modules/repository/fork.go b/modules/repository/fork.go new file mode 100644 index 0000000000000..c967d3b741be9 --- /dev/null +++ b/modules/repository/fork.go @@ -0,0 +1,31 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repository + +import ( + "code.gitea.io/gitea/models/organization" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" +) + +// CanUserForkRepo returns true if specified user can fork repository. +func CanUserForkRepo(user *user_model.User, repo *repo_model.Repository) (bool, error) { + if user == nil { + return false, nil + } + if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(user.ID, repo.ID) { + return true, nil + } + ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(user.ID) + if err != nil { + return false, err + } + for _, org := range ownedOrgs { + if repo.OwnerID != org.ID && !repo_model.HasForkedRepo(org.ID, repo.ID) { + return true, nil + } + } + return false, nil +} diff --git a/modules/repository/generate.go b/modules/repository/generate.go index b3ce8091731ae..94bb6e6429990 100644 --- a/modules/repository/generate.go +++ b/modules/repository/generate.go @@ -5,6 +5,8 @@ package repository import ( + "bufio" + "bytes" "context" "fmt" "os" @@ -20,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" + "github.com/gobwas/glob" "github.com/huandu/xstrings" ) @@ -78,7 +81,38 @@ func generateExpansion(src string, templateRepo, generateRepo *repo_model.Reposi }) } -func checkGiteaTemplate(tmpDir string) (*models.GiteaTemplate, error) { +// GiteaTemplate holds information about a .gitea/template file +type GiteaTemplate struct { + Path string + Content []byte + + globs []glob.Glob +} + +// Globs parses the .gitea/template globs or returns them if they were already parsed +func (gt GiteaTemplate) Globs() []glob.Glob { + if gt.globs != nil { + return gt.globs + } + + gt.globs = make([]glob.Glob, 0) + scanner := bufio.NewScanner(bytes.NewReader(gt.Content)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + g, err := glob.Compile(line, '/') + if err != nil { + log.Info("Invalid glob expression '%s' (skipped): %v", line, err) + continue + } + gt.globs = append(gt.globs, g) + } + return gt.globs +} + +func checkGiteaTemplate(tmpDir string) (*GiteaTemplate, error) { gtPath := filepath.Join(tmpDir, ".gitea", "template") if _, err := os.Stat(gtPath); os.IsNotExist(err) { return nil, nil @@ -91,7 +125,7 @@ func checkGiteaTemplate(tmpDir string) (*models.GiteaTemplate, error) { return nil, err } - gt := &models.GiteaTemplate{ + gt := &GiteaTemplate{ Path: gtPath, Content: content, } @@ -227,7 +261,7 @@ func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *r if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { return fmt.Errorf("setDefaultBranch: %v", err) } - if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil { + if err = UpdateRepository(ctx, repo, false); err != nil { return fmt.Errorf("updateRepository: %v", err) } @@ -240,7 +274,7 @@ func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_mo return err } - if err := models.UpdateRepoSize(ctx, generateRepo); err != nil { + if err := UpdateRepoSize(ctx, generateRepo); err != nil { return fmt.Errorf("failed to update size for repository: %v", err) } @@ -250,8 +284,27 @@ func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_mo return nil } +// GenerateRepoOptions contains the template units to generate +type GenerateRepoOptions struct { + Name string + DefaultBranch string + Description string + Private bool + GitContent bool + Topics bool + GitHooks bool + Webhooks bool + Avatar bool + IssueLabels bool +} + +// IsValid checks whether at least one option is chosen for generation +func (gro GenerateRepoOptions) IsValid() bool { + return gro.GitContent || gro.Topics || gro.GitHooks || gro.Webhooks || gro.Avatar || gro.IssueLabels // 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 models.GenerateRepoOptions) (_ *repo_model.Repository, err error) { +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, @@ -288,7 +341,7 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ return generateRepo, err } - if err = models.CheckDaemonExportOK(ctx, generateRepo); err != nil { + if err = CheckDaemonExportOK(ctx, generateRepo); err != nil { return generateRepo, fmt.Errorf("checkDaemonExportOK: %v", err) } diff --git a/models/repo_generate_test.go b/modules/repository/generate_test.go similarity index 98% rename from models/repo_generate_test.go rename to modules/repository/generate_test.go index e7a93433a7be3..139fa4c918d8e 100644 --- a/models/repo_generate_test.go +++ b/modules/repository/generate_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package repository import ( "testing" diff --git a/modules/repository/init.go b/modules/repository/init.go index 845a61ed0a0ce..f8c7a89552ea0 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -444,7 +444,7 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re } } - if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil { + if err = UpdateRepository(ctx, repo, false); err != nil { return fmt.Errorf("updateRepository: %v", err) } diff --git a/modules/repository/repo.go b/modules/repository/repo.go index 30ca6fdff84e7..281999a1eb0e8 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -116,7 +116,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, repo.Owner = u } - if err = models.CheckDaemonExportOK(ctx, repo); err != nil { + if err = CheckDaemonExportOK(ctx, repo); err != nil { return repo, fmt.Errorf("checkDaemonExportOK: %v", err) } @@ -168,9 +168,11 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, } } - if err = models.UpdateRepoSize(ctx, repo); err != nil { - log.Error("Failed to update size for repository: %v", err) + ctx, committer, err := db.TxContext() + if err != nil { + return nil, err } + defer committer.Close() if opts.Mirror { mirrorModel := repo_model.Mirror{ @@ -203,17 +205,24 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, } } - if err = repo_model.InsertMirror(&mirrorModel); err != nil { + if err = repo_model.InsertMirror(ctx, &mirrorModel); err != nil { return repo, fmt.Errorf("InsertOne: %v", err) } repo.IsMirror = true - err = models.UpdateRepository(repo, false) + if err = UpdateRepository(ctx, repo, false); err != nil { + return nil, err + } } else { - repo, err = CleanUpMigrateInfo(ctx, repo) + if err = UpdateRepoSize(ctx, repo); err != nil { + log.Error("Failed to update size for repository: %v", err) + } + if repo, err = CleanUpMigrateInfo(ctx, repo); err != nil { + return nil, err + } } - return repo, err + return repo, committer.Commit() } // cleanUpMigrateGitConfig removes mirror info which prevents "push --all". @@ -253,7 +262,7 @@ func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo } } - return repo, models.UpdateRepository(repo, false) + return repo, UpdateRepository(ctx, repo, false) } // SyncReleasesWithTags synchronizes release table with repository tags diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index 248497a561fee..aa425e582818e 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -312,7 +312,7 @@ func GetReviewers(ctx *context.APIContext) { // "200": // "$ref": "#/responses/UserList" - reviewers, err := models.GetReviewers(ctx.Repo.Repository, ctx.Doer.ID, 0) + reviewers, err := repo_model.GetReviewers(ctx, ctx.Repo.Repository, ctx.Doer.ID, 0) if err != nil { ctx.Error(http.StatusInternalServerError, "ListCollaborators", err) return @@ -342,7 +342,7 @@ func GetAssignees(ctx *context.APIContext) { // "200": // "$ref": "#/responses/UserList" - assignees, err := models.GetRepoAssignees(ctx.Repo.Repository) + assignees, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository) if err != nil { ctx.Error(http.StatusInternalServerError, "ListCollaborators", err) return diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 62959c3a76da5..c394ad17564eb 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -17,6 +17,7 @@ import ( issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" access_model "code.gitea.io/gitea/models/perm/access" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" @@ -130,7 +131,7 @@ func SearchIssues(ctx *context.APIContext) { } // find repos user can access (for issue search) - opts := &models.SearchRepoOptions{ + opts := &repo_model.SearchRepoOptions{ Private: false, AllPublic: true, TopicOnly: false, @@ -176,8 +177,8 @@ func SearchIssues(ctx *context.APIContext) { opts.TeamID = team.ID } - repoCond := models.SearchRepositoryCondition(opts) - repoIDs, _, err := models.SearchRepositoryIDs(opts) + repoCond := repo_model.SearchRepositoryCondition(opts) + repoIDs, _, err := repo_model.SearchRepositoryIDs(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err) return diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 8485ffbac2145..cdd1f7d5c4a7c 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -123,7 +123,7 @@ func Search(ctx *context.APIContext) { // "422": // "$ref": "#/responses/validationError" - opts := &models.SearchRepoOptions{ + opts := &repo_model.SearchRepoOptions{ ListOptions: utils.GetListOptions(ctx), Actor: ctx.Doer, Keyword: ctx.FormTrim("q"), @@ -192,7 +192,7 @@ func Search(ctx *context.APIContext) { } var err error - repos, count, err := models.SearchRepository(opts) + repos, count, err := repo_model.SearchRepository(opts) if err != nil { ctx.JSON(http.StatusInternalServerError, api.SearchError{ OK: false, @@ -344,7 +344,7 @@ func Generate(ctx *context.APIContext) { return } - opts := models.GenerateRepoOptions{ + opts := repo_module.GenerateRepoOptions{ Name: form.Name, DefaultBranch: form.DefaultBranch, Description: form.Description, @@ -717,7 +717,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err repo.DefaultBranch = *opts.DefaultBranch } - if err := models.UpdateRepository(repo, visibilityChanged); err != nil { + if err := repo_service.UpdateRepository(repo, visibilityChanged); err != nil { ctx.Error(http.StatusInternalServerError, "UpdateRepository", err) return err } @@ -1036,7 +1036,7 @@ func Delete(ctx *context.APIContext) { owner := ctx.Repo.Owner repo := ctx.Repo.Repository - canDelete, err := models.CanUserDelete(repo, ctx.Doer) + canDelete, err := repo_module.CanUserDelete(repo, ctx.Doer) if err != nil { ctx.Error(http.StatusInternalServerError, "CanUserDelete", err) return diff --git a/routers/api/v1/user/repo.go b/routers/api/v1/user/repo.go index 05cecf508b383..709e3a6c5457d 100644 --- a/routers/api/v1/user/repo.go +++ b/routers/api/v1/user/repo.go @@ -7,9 +7,9 @@ package user import ( "net/http" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" + repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" @@ -21,7 +21,7 @@ import ( func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) { opts := utils.GetListOptions(ctx) - repos, count, err := models.GetUserRepositories(&models.SearchRepoOptions{ + repos, count, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ Actor: u, Private: private, ListOptions: opts, @@ -103,7 +103,7 @@ func ListMyRepos(ctx *context.APIContext) { // "200": // "$ref": "#/responses/RepositoryList" - opts := &models.SearchRepoOptions{ + opts := &repo_model.SearchRepoOptions{ ListOptions: utils.GetListOptions(ctx), Actor: ctx.Doer, OwnerID: ctx.Doer.ID, @@ -112,7 +112,7 @@ func ListMyRepos(ctx *context.APIContext) { } var err error - repos, count, err := models.SearchRepository(opts) + repos, count, err := repo_model.SearchRepository(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "SearchRepository", err) return diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go index 41ca27782f967..3fba2be37d8cb 100644 --- a/routers/web/explore/code.go +++ b/routers/web/explore/code.go @@ -55,7 +55,7 @@ func Code(ctx *context.Context) { // guest user or non-admin user if ctx.Doer == nil || !isAdmin { - repoIDs, err = models.FindUserAccessibleRepoIDs(ctx.Doer) + repoIDs, err = repo_model.FindUserAccessibleRepoIDs(ctx.Doer) if err != nil { ctx.ServerError("SearchResults", err) return @@ -79,7 +79,7 @@ func Code(ctx *context.Context) { rightRepoMap := make(map[int64]*repo_model.Repository, len(repoMaps)) repoIDs = make([]int64, 0, len(repoMaps)) for id, repo := range repoMaps { - if models.CheckRepoUnitUser(repo, ctx.Doer, unit.TypeCode) { + if models.CheckRepoUnitUser(ctx, repo, ctx.Doer, unit.TypeCode) { rightRepoMap[id] = repo repoIDs = append(repoIDs, id) } diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go index 3e8aa2bb0fda7..f64642bc95d36 100644 --- a/routers/web/explore/repo.go +++ b/routers/web/explore/repo.go @@ -7,7 +7,6 @@ package explore import ( "net/http" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/base" @@ -81,7 +80,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { language := ctx.FormTrim("language") ctx.Data["Language"] = language - repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ + repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ Page: page, PageSize: opts.PageSize, diff --git a/routers/web/org/home.go b/routers/web/org/home.go index 24a0f13b542e1..d565a0c242404 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -8,7 +8,6 @@ import ( "net/http" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" @@ -105,7 +104,7 @@ func Home(ctx *context.Context) { count int64 err error ) - repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ + repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 758ca47af6c44..c22a124e7421a 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -24,6 +24,7 @@ import ( user_setting "code.gitea.io/gitea/routers/web/user/setting" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/org" + repo_service "code.gitea.io/gitea/services/repository" user_service "code.gitea.io/gitea/services/user" ) @@ -117,7 +118,7 @@ func SettingsPost(ctx *context.Context) { // update forks visibility if visibilityChanged { - repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{ + repos, _, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ Actor: org.AsUser(), Private: true, ListOptions: db.ListOptions{Page: 1, PageSize: org.NumRepos}, }) if err != nil { @@ -126,7 +127,7 @@ func SettingsPost(ctx *context.Context) { } for _, repo := range repos { repo.OwnerName = org.Name - if err := models.UpdateRepository(repo, true); err != nil { + if err := repo_service.UpdateRepository(repo, true); err != nil { ctx.ServerError("UpdateRepository", err) return } diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go index 701236f838be9..190dc6c2c7517 100644 --- a/routers/web/repo/attachment.go +++ b/routers/web/repo/attachment.go @@ -8,7 +8,6 @@ import ( "fmt" "net/http" - "code.gitea.io/gitea/models" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/context" @@ -19,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/upload" "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/services/attachment" + repo_service "code.gitea.io/gitea/services/repository" ) // UploadIssueAttachment response for Issue/PR attachments @@ -95,7 +95,7 @@ func GetAttachment(ctx *context.Context) { return } - repository, unitType, err := models.LinkedRepository(attach) + repository, unitType, err := repo_service.LinkedRepository(attach) if err != nil { ctx.ServerError("LinkedRepository", err) return diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 9f94489235ed9..d3653f04e9f77 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -457,7 +457,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { if rootRepo != nil && rootRepo.ID != ci.HeadRepo.ID && rootRepo.ID != baseRepo.ID { - canRead := models.CheckRepoUnitUser(rootRepo, ctx.Doer, unit.TypeCode) + canRead := models.CheckRepoUnitUser(ctx, rootRepo, ctx.Doer, unit.TypeCode) if canRead { ctx.Data["RootRepo"] = rootRepo if !fileOnly { @@ -482,7 +482,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ownForkRepo.ID != ci.HeadRepo.ID && ownForkRepo.ID != baseRepo.ID && (rootRepo == nil || ownForkRepo.ID != rootRepo.ID) { - canRead := models.CheckRepoUnitUser(ownForkRepo, ctx.Doer, unit.TypeCode) + canRead := models.CheckRepoUnitUser(ctx, ownForkRepo, ctx.Doer, unit.TypeCode) if canRead { ctx.Data["OwnForkRepo"] = ownForkRepo if !fileOnly { diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index d418907a1f17c..4a732ba45490d 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -49,6 +49,7 @@ import ( "code.gitea.io/gitea/services/forms" issue_service "code.gitea.io/gitea/services/issue" pull_service "code.gitea.io/gitea/services/pull" + repo_service "code.gitea.io/gitea/services/repository" ) const ( @@ -283,7 +284,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti ctx.Data["CommitStatuses"] = commitStatuses // Get assignees. - ctx.Data["Assignees"], err = models.GetRepoAssignees(repo) + ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, repo) if err != nil { ctx.ServerError("GetAssignees", err) return @@ -441,7 +442,7 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R return } - ctx.Data["Assignees"], err = models.GetRepoAssignees(repo) + ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, repo) if err != nil { ctx.ServerError("GetAssignees", err) return @@ -522,13 +523,13 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is posterID = 0 } - reviewers, err = models.GetReviewers(repo, ctx.Doer.ID, posterID) + reviewers, err = repo_model.GetReviewers(ctx, repo, ctx.Doer.ID, posterID) if err != nil { ctx.ServerError("GetReviewers", err) return } - teamReviewers, err = models.GetReviewerTeams(repo) + teamReviewers, err = repo_service.GetReviewerTeams(repo) if err != nil { ctx.ServerError("GetReviewerTeams", err) return @@ -2160,7 +2161,7 @@ func SearchIssues(ctx *context.Context) { } // find repos user can access (for issue search) - opts := &models.SearchRepoOptions{ + opts := &repo_model.SearchRepoOptions{ Private: false, AllPublic: true, TopicOnly: false, @@ -2206,8 +2207,8 @@ func SearchIssues(ctx *context.Context) { opts.TeamID = team.ID } - repoCond := models.SearchRepositoryCondition(opts) - repoIDs, _, err := models.SearchRepositoryIDs(opts) + repoCond := repo_model.SearchRepositoryCondition(opts) + repoIDs, _, err := repo_model.SearchRepositoryIDs(opts) if err != nil { ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err.Error()) return diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 3f24be33d6623..8df4ccc607862 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -758,7 +758,7 @@ func ViewPullFiles(ctx *context.Context) { setCompareContext(ctx, baseCommit, commit, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) ctx.Data["RequireTribute"] = true - if ctx.Data["Assignees"], err = models.GetRepoAssignees(ctx.Repo.Repository); err != nil { + if ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository); err != nil { ctx.ServerError("GetAssignees", err) return } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 30cb888dce989..c2c79e4a0df1c 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -152,7 +152,7 @@ func Create(ctx *context.Context) { templateID := ctx.FormInt64("template_id") if templateID > 0 { templateRepo, err := repo_model.GetRepositoryByID(templateID) - if err == nil && models.CheckRepoUnitUser(templateRepo, ctxUser, unit.TypeCode) { + if err == nil && models.CheckRepoUnitUser(ctx, templateRepo, ctxUser, unit.TypeCode) { ctx.Data["repo_template"] = templateID ctx.Data["repo_template_name"] = templateRepo.Name } @@ -223,7 +223,7 @@ func CreatePost(ctx *context.Context) { var repo *repo_model.Repository var err error if form.RepoTemplate > 0 { - opts := models.GenerateRepoOptions{ + opts := repo_module.GenerateRepoOptions{ Name: form.RepoName, Description: form.Description, Private: form.Private, @@ -304,7 +304,7 @@ func Action(ctx *context.Context) { ctx.Repo.Repository.Description = ctx.FormString("desc") ctx.Repo.Repository.Website = ctx.FormString("site") - err = models.UpdateRepository(ctx.Repo.Repository, false) + err = repo_service.UpdateRepository(ctx.Repo.Repository, false) } if err != nil { @@ -509,7 +509,7 @@ func InitiateDownload(ctx *context.Context) { // SearchRepo repositories via options func SearchRepo(ctx *context.Context) { - opts := &models.SearchRepoOptions{ + opts := &repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ Page: ctx.FormInt("page"), PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), @@ -581,7 +581,7 @@ func SearchRepo(ctx *context.Context) { } var err error - repos, count, err := models.SearchRepository(opts) + repos, count, err := repo_model.SearchRepository(opts) if err != nil { ctx.JSON(http.StatusInternalServerError, api.SearchError{ OK: false, diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index a60bf52622160..b7be0aa3f5083 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -168,7 +168,7 @@ func SettingsPost(ctx *context.Context) { } repo.IsPrivate = form.Private - if err := models.UpdateRepository(repo, visibilityChanged); err != nil { + if err := repo_service.UpdateRepository(repo, visibilityChanged); err != nil { ctx.ServerError("UpdateRepository", err) return } @@ -491,7 +491,7 @@ func SettingsPost(ctx *context.Context) { return } if repoChanged { - if err := models.UpdateRepository(repo, false); err != nil { + if err := repo_service.UpdateRepository(repo, false); err != nil { ctx.ServerError("UpdateRepository", err) return } @@ -510,7 +510,7 @@ func SettingsPost(ctx *context.Context) { } if changed { - if err := models.UpdateRepository(repo, false); err != nil { + if err := repo_service.UpdateRepository(repo, false); err != nil { ctx.ServerError("UpdateRepository", err) return } @@ -530,7 +530,7 @@ func SettingsPost(ctx *context.Context) { repo.IsFsckEnabled = form.EnableHealthCheck } - if err := models.UpdateRepository(repo, false); err != nil { + if err := repo_service.UpdateRepository(repo, false); err != nil { ctx.ServerError("UpdateRepository", err) return } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 95ca81c442d01..2c9e16de5b376 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -34,6 +34,7 @@ import ( "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" + repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/typesniffer" @@ -905,7 +906,7 @@ func renderCode(ctx *context.Context) { ctx.ServerError("UpdateRepositoryCols", err) return } - if err = models.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil { + if err = repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil { ctx.ServerError("UpdateRepoSize", err) return } diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 1412f6cfef2e3..9b4fc652f1fbc 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -166,7 +166,7 @@ func Milestones(ctx *context.Context) { return } - repoOpts := models.SearchRepoOptions{ + repoOpts := repo_model.SearchRepoOptions{ Actor: ctxUser, OwnerID: ctxUser.ID, Private: true, @@ -181,7 +181,7 @@ func Milestones(ctx *context.Context) { } var ( - userRepoCond = models.SearchRepositoryCondition(&repoOpts) // all repo condition user could visit + userRepoCond = repo_model.SearchRepositoryCondition(&repoOpts) // all repo condition user could visit repoCond = userRepoCond repoIDs []int64 @@ -234,7 +234,7 @@ func Milestones(ctx *context.Context) { return } - showRepos, _, err := models.SearchRepositoryByCondition(&repoOpts, userRepoCond, false) + showRepos, _, err := repo_model.SearchRepositoryByCondition(&repoOpts, userRepoCond, false) if err != nil { ctx.ServerError("SearchRepositoryByCondition", err) return @@ -437,7 +437,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { // As team: // - Team org's owns the repository. // - Team has read permission to repository. - repoOpts := &models.SearchRepoOptions{ + repoOpts := &repo_model.SearchRepoOptions{ Actor: ctx.Doer, OwnerID: ctx.Doer.ID, Private: true, @@ -559,7 +559,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { } // a RepositoryList - showRepos := models.RepositoryListOfMap(showReposMap) + showRepos := repo_model.RepositoryListOfMap(showReposMap) sort.Sort(showRepos) // maps pull request IDs to their CommitStatus. Will be posted to ctx.Data. diff --git a/routers/web/user/home_test.go b/routers/web/user/home_test.go index bf78e00adade8..9ad0711dc0ade 100644 --- a/routers/web/user/home_test.go +++ b/routers/web/user/home_test.go @@ -8,7 +8,7 @@ import ( "net/http" "testing" - "code.gitea.io/gitea/models" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" @@ -26,7 +26,7 @@ func TestArchivedIssues(t *testing.T) { ctx.Req.Form.Set("state", "open") // Assume: User 30 has access to two Repos with Issues, one of the Repos being archived. - repos, _, _ := models.GetUserRepositories(&models.SearchRepoOptions{Actor: ctx.Doer}) + repos, _, _ := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{Actor: ctx.Doer}) assert.Len(t, repos, 2) IsArchived := make(map[int64]bool) NumIssues := make(map[int64]int) diff --git a/routers/web/user/package.go b/routers/web/user/package.go index c9aa2471ef33f..b2b550cb73b4d 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -7,7 +7,6 @@ package user import ( "net/http" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" container_model "code.gitea.io/gitea/models/packages/container" @@ -288,7 +287,7 @@ func PackageSettings(ctx *context.Context) { ctx.Data["ContextUser"] = ctx.ContextUser ctx.Data["PackageDescriptor"] = pd - repos, _, _ := models.GetUserRepositories(&models.SearchRepoOptions{ + repos, _, _ := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ Actor: pd.Owner, Private: true, }) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 8bce5460ccec1..44501fc24549c 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -195,7 +195,7 @@ func Profile(ctx *context.Context) { } case "stars": ctx.Data["PageIsProfileStarList"] = true - repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ + repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, @@ -227,7 +227,7 @@ func Profile(ctx *context.Context) { return } case "watching": - repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ + repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, @@ -249,7 +249,7 @@ func Profile(ctx *context.Context) { total = int(count) default: - repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ + repos, count, err = repo_model.SearchRepository(&repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ PageSize: setting.UI.User.RepoPagingNum, Page: page, diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index c2a406b1842ba..972271269f14e 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -15,7 +15,6 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" @@ -304,7 +303,7 @@ func Repos(ctx *context.Context) { return } - userRepos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{ + userRepos, _, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ Actor: ctxUser, Private: true, ListOptions: db.ListOptions{ @@ -329,7 +328,7 @@ func Repos(ctx *context.Context) { ctx.Data["Dirs"] = repoNames ctx.Data["ReposMap"] = repos } else { - repos, count64, err := models.GetUserRepositories(&models.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: opts}) + repos, count64, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: opts}) if err != nil { ctx.ServerError("GetUserRepositories", err) return diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 92ff92a6c0f21..d611ff513e989 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1220,7 +1220,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio oid := strings.TrimPrefix(line[1:], lfs.MetaFileOidPrefix) if len(oid) == 64 { m := &models.LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}} - count, err := db.Count(m) + count, err := db.CountByBean(db.DefaultContext, m) if err == nil && count > 0 { curFile.IsBin = true diff --git a/services/mailer/mail_issue.go b/services/mailer/mail_issue.go index 4abf7eefda633..d479dd0d44361 100644 --- a/services/mailer/mail_issue.go +++ b/services/mailer/mail_issue.go @@ -145,7 +145,7 @@ func mailIssueCommentBatch(ctx *mailCommentContext, users []*user_model.User, vi visited[user.ID] = true // test if this user is allowed to see the issue/pull - if !models.CheckRepoUnitUser(ctx.Issue.Repo, user, checkUnit) { + if !models.CheckRepoUnitUser(ctx, ctx.Issue.Repo, user, checkUnit) { continue } diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index a93aee76cf0a5..caa81f0fe9852 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" admin_model "code.gitea.io/gitea/models/admin" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" @@ -301,7 +300,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo gitRepo.Close() log.Trace("SyncMirrors [repo: %-v]: updating size of repository", m.Repo) - if err := models.UpdateRepoSize(ctx, m.Repo); err != nil { + if err := repo_module.UpdateRepoSize(ctx, m.Repo); err != nil { log.Error("SyncMirrors [repo: %-v]: failed to update size for mirror repository: %v", m.Repo, err) } diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 1e8c22a4792ed..48f049cd28117 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -73,7 +73,7 @@ func AdoptRepository(doer, u *user_model.User, opts models.CreateRepoOptions) (* if err := adoptRepository(ctx, repoPath, doer, repo, opts); err != nil { return fmt.Errorf("createDelegateHooks: %v", err) } - if err := models.CheckDaemonExportOK(ctx, repo); err != nil { + if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { return fmt.Errorf("checkDaemonExportOK: %v", err) } @@ -182,7 +182,7 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r } } - if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil { + if err = repo_module.UpdateRepository(ctx, repo, false); err != nil { return fmt.Errorf("updateRepository: %v", err) } @@ -246,7 +246,7 @@ func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unad } return err } - repos, _, err := models.GetUserRepositories(&models.SearchRepoOptions{ + repos, _, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ Actor: ctxUser, Private: true, ListOptions: db.ListOptions{ diff --git a/services/repository/check.go b/services/repository/check.go index 6fb86d0dc3c25..17bdf2fac1649 100644 --- a/services/repository/check.go +++ b/services/repository/check.go @@ -17,6 +17,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/util" "xorm.io/builder" @@ -89,7 +90,7 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) erro } // Now update the size of the repository - if err := models.UpdateRepoSize(ctx, repo); err != nil { + if err := repo_module.UpdateRepoSize(ctx, repo); err != nil { log.Error("Updating size as part of garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err) desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err) if err = admin_model.CreateRepositoryNotice(desc); err != nil { diff --git a/services/repository/fork.go b/services/repository/fork.go index a2ef75bbd0420..f4888cba1dafe 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -96,7 +96,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork return err } - if err = models.IncrementRepoForkNum(txCtx, opts.BaseRepo.ID); err != nil { + if err = repo_model.IncrementRepoForkNum(txCtx, opts.BaseRepo.ID); err != nil { return err } @@ -116,7 +116,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork return fmt.Errorf("git clone: %v", err) } - if err := models.CheckDaemonExportOK(txCtx, repo); err != nil { + if err := repo_module.CheckDaemonExportOK(txCtx, repo); err != nil { return fmt.Errorf("checkDaemonExportOK: %v", err) } @@ -139,7 +139,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork } // even if below operations failed, it could be ignored. And they will be retried - if err := models.UpdateRepoSize(ctx, repo); err != nil { + if err := repo_module.UpdateRepoSize(ctx, repo); err != nil { log.Error("Failed to update size for repository: %v", err) } if err := repo_model.CopyLanguageStat(opts.BaseRepo, repo); err != nil { @@ -173,7 +173,7 @@ func ConvertForkToNormalRepository(repo *repo_model.Repository) error { return nil } - if err := models.DecrementRepoForkNum(ctx, repo.ForkID); err != nil { + if err := repo_model.DecrementRepoForkNum(ctx, repo.ForkID); err != nil { log.Error("Unable to decrement repo fork num for old root repo %d of repository %-v whilst converting from fork. Error: %v", repo.ForkID, repo, err) return err } @@ -181,7 +181,7 @@ func ConvertForkToNormalRepository(repo *repo_model.Repository) error { repo.IsFork = false repo.ForkID = 0 - if err := models.UpdateRepositoryCtx(ctx, repo, false); err != nil { + if err := repo_module.UpdateRepository(ctx, repo, false); err != nil { log.Error("Unable to update repository %-v whilst converting from fork. Error: %v", repo, err) return err } diff --git a/services/repository/hooks.go b/services/repository/hooks.go index 67931ffcb64b5..45a031f38e6b0 100644 --- a/services/repository/hooks.go +++ b/services/repository/hooks.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" @@ -84,3 +85,29 @@ func GenerateGitHooks(ctx context.Context, templateRepo, generateRepo *repo_mode } return nil } + +// GenerateWebhooks generates webhooks from a template repository +func GenerateWebhooks(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { + templateWebhooks, err := webhook.ListWebhooksByOpts(ctx, &webhook.ListWebhookOptions{RepoID: templateRepo.ID}) + if err != nil { + return err + } + + ws := make([]*webhook.Webhook, 0, len(templateWebhooks)) + for _, templateWebhook := range templateWebhooks { + ws = append(ws, &webhook.Webhook{ + RepoID: generateRepo.ID, + URL: templateWebhook.URL, + HTTPMethod: templateWebhook.HTTPMethod, + ContentType: templateWebhook.ContentType, + Secret: templateWebhook.Secret, + HookEvent: templateWebhook.HookEvent, + IsActive: templateWebhook.IsActive, + Type: templateWebhook.Type, + OrgID: templateWebhook.OrgID, + Events: templateWebhook.Events, + Meta: templateWebhook.Meta, + }) + } + return webhook.CreateWebhooks(ctx, ws) +} diff --git a/services/repository/push.go b/services/repository/push.go index 5e48a19ba80b6..b6741c5ab43e7 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -95,7 +95,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { } defer gitRepo.Close() - if err = models.UpdateRepoSize(ctx, repo); err != nil { + if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { log.Error("Failed to update size for repository: %v", err) } diff --git a/services/repository/repository.go b/services/repository/repository.go index 6799ca586ed9b..6848eda101e28 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" @@ -85,3 +86,42 @@ func Init() error { admin_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath()) return initPushQueue() } + +// UpdateRepository updates a repository +func UpdateRepository(repo *repo_model.Repository, visibilityChanged bool) (err error) { + ctx, committer, err := db.TxContext() + if err != nil { + return err + } + defer committer.Close() + + if err = repo_module.UpdateRepository(ctx, repo, visibilityChanged); err != nil { + return fmt.Errorf("updateRepository: %v", err) + } + + return committer.Commit() +} + +// LinkedRepository returns the linked repo if any +func LinkedRepository(a *repo_model.Attachment) (*repo_model.Repository, unit.Type, error) { + if a.IssueID != 0 { + iss, err := models.GetIssueByID(a.IssueID) + if err != nil { + return nil, unit.TypeIssues, err + } + repo, err := repo_model.GetRepositoryByID(iss.RepoID) + unitType := unit.TypeIssues + if iss.IsPull { + unitType = unit.TypePullRequests + } + return repo, unitType, err + } else if a.ReleaseID != 0 { + rel, err := models.GetReleaseByID(db.DefaultContext, a.ReleaseID) + if err != nil { + return nil, unit.TypeReleases, err + } + repo, err := repo_model.GetRepositoryByID(rel.RepoID) + return repo, unit.TypeReleases, err + } + return nil, -1, nil +} diff --git a/services/repository/repository_test.go b/services/repository/repository_test.go new file mode 100644 index 0000000000000..e0ffcac3cc74d --- /dev/null +++ b/services/repository/repository_test.go @@ -0,0 +1,43 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repository + +import ( + "testing" + + "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" + + "github.com/stretchr/testify/assert" +) + +func TestLinkedRepository(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + testCases := []struct { + name string + attachID int64 + expectedRepo *repo_model.Repository + expectedUnitType unit.Type + }{ + {"LinkedIssue", 1, &repo_model.Repository{ID: 1}, unit.TypeIssues}, + {"LinkedComment", 3, &repo_model.Repository{ID: 1}, unit.TypePullRequests}, + {"LinkedRelease", 9, &repo_model.Repository{ID: 1}, unit.TypeReleases}, + {"Notlinked", 10, nil, -1}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + attach, err := repo_model.GetAttachmentByID(db.DefaultContext, tc.attachID) + assert.NoError(t, err) + repo, unitType, err := LinkedRepository(attach) + assert.NoError(t, err) + if tc.expectedRepo != nil { + assert.Equal(t, tc.expectedRepo.ID, repo.ID) + } + assert.Equal(t, tc.expectedUnitType, unitType) + }) + } +} diff --git a/services/repository/review.go b/services/repository/review.go new file mode 100644 index 0000000000000..9e8012978eaa1 --- /dev/null +++ b/services/repository/review.go @@ -0,0 +1,24 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repository + +import ( + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" +) + +// GetReviewerTeams get all teams can be requested to review +func GetReviewerTeams(repo *repo_model.Repository) ([]*organization.Team, error) { + if err := repo.GetOwner(db.DefaultContext); err != nil { + return nil, err + } + if !repo.Owner.IsOrganization() { + return nil, nil + } + + return organization.GetTeamsWithAccessToRepo(db.DefaultContext, repo.OwnerID, repo.ID, perm.AccessModeRead) +} diff --git a/services/repository/review_test.go b/services/repository/review_test.go new file mode 100644 index 0000000000000..640657d1dd221 --- /dev/null +++ b/services/repository/review_test.go @@ -0,0 +1,28 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repository + +import ( + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestRepoGetReviewerTeams(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository) + teams, err := GetReviewerTeams(repo2) + assert.NoError(t, err) + assert.Empty(t, teams) + + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository) + teams, err = GetReviewerTeams(repo3) + assert.NoError(t, err) + assert.Len(t, teams, 2) +} diff --git a/services/repository/template.go b/services/repository/template.go index 28fa1523a5952..6a1bfaff5b5be 100644 --- a/services/repository/template.go +++ b/services/repository/template.go @@ -16,8 +16,27 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" ) +// GenerateIssueLabels generates issue labels from a template repository +func GenerateIssueLabels(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { + templateLabels, err := models.GetLabelsByRepoID(ctx, templateRepo.ID, "", db.ListOptions{}) + if err != nil { + return err + } + + newLabels := make([]*models.Label, 0, len(templateLabels)) + for _, templateLabel := range templateLabels { + newLabels = append(newLabels, &models.Label{ + RepoID: generateRepo.ID, + Name: templateLabel.Name, + Description: templateLabel.Description, + Color: templateLabel.Color, + }) + } + return db.Insert(ctx, newLabels) +} + // GenerateRepository generates a repository from a template -func GenerateRepository(doer, owner *user_model.User, templateRepo *repo_model.Repository, opts models.GenerateRepoOptions) (_ *repo_model.Repository, err error) { +func GenerateRepository(doer, owner *user_model.User, templateRepo *repo_model.Repository, opts repo_module.GenerateRepoOptions) (_ *repo_model.Repository, err error) { if !doer.IsAdmin && !owner.CanCreateRepo() { return nil, repo_model.ErrReachLimitOfRepo{ Limit: owner.MaxRepoCreation, @@ -54,7 +73,7 @@ func GenerateRepository(doer, owner *user_model.User, templateRepo *repo_model.R // Webhooks if opts.Webhooks { - if err = models.GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil { + if err = GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil { return err } } @@ -68,7 +87,7 @@ func GenerateRepository(doer, owner *user_model.User, templateRepo *repo_model.R // Issue Labels if opts.IssueLabels { - if err = models.GenerateIssueLabels(ctx, templateRepo, generateRepo); err != nil { + if err = GenerateIssueLabels(ctx, templateRepo, generateRepo); err != nil { return err } }