diff options
54 files changed, 443 insertions, 266 deletions
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 2ec013d4fe..42d181a00a 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1155,6 +1155,10 @@ LEVEL = Info ;; ;; Retarget child pull requests to the parent pull request branch target on merge of parent pull request. It only works on merged PRs where the head and base branch target the same repo. ;RETARGET_CHILDREN_ON_MERGE = true +;; +;; Delay mergeable check until page view or API access, for pull requests that have not been updated in the specified days when their base branches get updated. +;; Use "-1" to always check all pull requests (old behavior). Use "0" to always delay the checks. +;DELAY_CHECK_FOR_INACTIVE_DAYS = 7 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/models/issues/pull.go b/models/issues/pull.go index 016db9f75c..e65b214dab 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -10,7 +10,6 @@ import ( "fmt" "io" "regexp" - "strconv" "strings" "code.gitea.io/gitea/models/db" @@ -104,27 +103,6 @@ const ( PullRequestStatusAncestor ) -func (status PullRequestStatus) String() string { - switch status { - case PullRequestStatusConflict: - return "CONFLICT" - case PullRequestStatusChecking: - return "CHECKING" - case PullRequestStatusMergeable: - return "MERGEABLE" - case PullRequestStatusManuallyMerged: - return "MANUALLY_MERGED" - case PullRequestStatusError: - return "ERROR" - case PullRequestStatusEmpty: - return "EMPTY" - case PullRequestStatusAncestor: - return "ANCESTOR" - default: - return strconv.Itoa(int(status)) - } -} - // PullRequestFlow the flow of pull request type PullRequestFlow int diff --git a/modules/setting/repository.go b/modules/setting/repository.go index f99c854e4f..c6bdc65b32 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -82,6 +82,7 @@ var ( AddCoCommitterTrailers bool TestConflictingPatchesWithGitApply bool RetargetChildrenOnMerge bool + DelayCheckForInactiveDays int } `ini:"repository.pull-request"` // Issue Setting @@ -200,6 +201,7 @@ var ( AddCoCommitterTrailers bool TestConflictingPatchesWithGitApply bool RetargetChildrenOnMerge bool + DelayCheckForInactiveDays int }{ WorkInProgressPrefixes: []string{"WIP:", "[WIP]"}, // Same as GitHub. See @@ -215,6 +217,7 @@ var ( PopulateSquashCommentWithCommitMessages: false, AddCoCommitterTrailers: true, RetargetChildrenOnMerge: true, + DelayCheckForInactiveDays: 7, }, // Issue settings diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 45a8a15f71..0dc7b6e26a 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -1849,7 +1849,6 @@ pulls.add_prefix=Přidat prefix <strong>%s</strong> pulls.remove_prefix=Odstranit prefix <strong>%s</strong> pulls.data_broken=Tento pull request je rozbitý kvůli chybějícím informacím o rozštěpení. pulls.files_conflicted=Tento pull request obsahuje změny, které kolidují s cílovou větví. -pulls.is_checking=Právě probíhá kontrola konfliktů při sloučení. Zkuste to za chvíli. pulls.is_ancestor=Tato větev je již součástí cílové větve. Není co sloučit. pulls.is_empty=Změny na této větvi jsou již na cílové větvi. Toto bude prázdný commit. pulls.required_status_check_failed=Některé požadované kontroly nebyly úspěšné. diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 9a18794ef6..9a5ae9418a 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -1848,7 +1848,6 @@ pulls.add_prefix=<strong>%s</strong> Präfix hinzufügen pulls.remove_prefix=<strong>%s</strong> Präfix entfernen pulls.data_broken=Dieser Pull-Requests ist kaputt, da Fork-Informationen gelöscht wurden. pulls.files_conflicted=Dieser Pull-Request hat Änderungen, die im Widerspruch zum Ziel-Branch stehen. -pulls.is_checking=Die Konfliktprüfung läuft noch. Bitte aktualisiere die Seite in wenigen Augenblicken. pulls.is_ancestor=Dieser Branch ist bereits im Zielbranch enthalten. Es gibt nichts zu mergen. pulls.is_empty=Die Änderungen an diesem Branch sind bereits auf dem Zielbranch. Dies wird ein leerer Commit sein. pulls.required_status_check_failed=Einige erforderliche Prüfungen waren nicht erfolgreich. diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 741c80f34f..a70137f8ac 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -1671,7 +1671,6 @@ pulls.add_prefix=Προσθήκη <strong>%s</strong> προθέματος pulls.remove_prefix=Αφαίρεση <strong>%s</strong> προθέματος pulls.data_broken=Αυτό το pull request είναι κατεστραμμένο λόγω των πληροφοριών του fork που λείπουν. pulls.files_conflicted=Αυτό το pull request περιέχει αλλαγές που συγκρούονται με το κλάδο προορισμού. -pulls.is_checking=Ο έλεγχος συγκρούσεων κατά την συγχώνευση είναι σε εξέλιξη. Δοκιμάστε ξανά σε λίγα λεπτά. pulls.is_ancestor=Αυτός ο κλάδος περιλαμβάνεται ήδη στον κλάδο προορισμού. Δεν υπάρχει τίποτα για συγχώνευση. pulls.is_empty=Οι αλλαγές σε αυτόν τον κλάδο είναι ήδη στον κλάδο προορισμού. Θα είναι μια κενή υποβολή. pulls.required_status_check_failed=Ορισμένοι απαιτούμενοι έλεγχοι δεν ήταν επιτυχείς. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b4c5121de5..9928b3588a 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1880,7 +1880,7 @@ pulls.add_prefix = Add <strong>%s</strong> prefix pulls.remove_prefix = Remove <strong>%s</strong> prefix pulls.data_broken = This pull request is broken due to missing fork information. pulls.files_conflicted = This pull request has changes conflicting with the target branch. -pulls.is_checking = "Merge conflict checking is in progress. Try again in few moments." +pulls.is_checking = Checking for merge conflicts ... pulls.is_ancestor = "This branch is already included in the target branch. There is nothing to merge." pulls.is_empty = "The changes on this branch are already on the target branch. This will be an empty commit." pulls.required_status_check_failed = Some required checks were not successful. diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index cdc4510d1d..17c3e8e4a3 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -1661,7 +1661,6 @@ pulls.add_prefix=Añadir prefijo <strong>%s</strong> pulls.remove_prefix=Eliminar prefijo <strong>%s</strong> pulls.data_broken=Este pull request está rota debido a que falta información del fork. pulls.files_conflicted=Este pull request tiene cambios en conflicto con la rama de destino. -pulls.is_checking=La comprobación de conflicto de fusión está en progreso. Inténtalo de nuevo en unos momentos. pulls.is_ancestor=Esta rama ya está incluida en la rama de destino. No hay nada que fusionar. pulls.is_empty=Los cambios en esta rama ya están en la rama de destino. Esto será un commit vacío. pulls.required_status_check_failed=Algunos controles requeridos no han tenido éxito. diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 3b24c71e73..a7a0e33570 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -1285,7 +1285,6 @@ pulls.add_prefix=اضافه کردن پیشوند <strong>%s</strong> pulls.remove_prefix=حذف پیشوند <strong>%s</strong> pulls.data_broken=این تقاضای واکشی به دلیل از دست رفتن اطلاعات انشعاب با شکست مواجه شد. pulls.files_conflicted=این تقاضای واکشی دارای تغییراتی است که با شاخه هدف تداخل دارد. -pulls.is_checking=در حال پردازش تداخل در ادغام میباشد. لطفاً لحظاتی بعد امتحان کنید. pulls.required_status_check_failed=برخی بررسی های ضروری موفقیت آمیز نبود. pulls.required_status_check_missing=برخی بررسی های موردنیاز از قلم افتاده است. pulls.required_status_check_administrator=مثل یک مدیر، ممکن است شما این تقاضای واکشی را مسکوت بگذارید. diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index b2ccdf62da..ec63f34515 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -1879,7 +1879,6 @@ pulls.add_prefix=Ajouter le préfixe <strong>%s</strong> pulls.remove_prefix=Enlever le préfixe <strong>%s</strong> pulls.data_broken=Cette demande d’ajout est impossible par manque d'informations de bifurcation. pulls.files_conflicted=Cette demande d'ajout contient des modifications en conflit avec la branche ciblée. -pulls.is_checking=Vérification des conflits de fusion en cours. Réessayez dans quelques instants. pulls.is_ancestor=Cette branche est déjà présente dans la branche ciblée. Il n'y a rien à fusionner. pulls.is_empty=Les changements sur cette branche sont déjà sur la branche cible. Cette révision sera vide. pulls.required_status_check_failed=Certains contrôles requis n'ont pas réussi. @@ -3843,6 +3842,8 @@ deleted.display_name=Projet supprimé type-1.display_name=Projet personnel type-2.display_name=Projet de dépôt type-3.display_name=Projet d’organisation +enter_fullscreen=Plein écran +exit_fullscreen=Quitter le plein écran [git.filemode] changed_filemode=%[1]s → %[2]s diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index f797408472..8721dd1150 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -1879,7 +1879,6 @@ pulls.add_prefix=Cuir réimír <strong>%s</strong> leis pulls.remove_prefix=Bain an réimír <strong>%s</strong> pulls.data_broken=Tá an t-iarratas tarraingthe seo briste mar gheall ar fhaisnéis forc a bheith in easnamh. pulls.files_conflicted=Tá athruithe ag an iarratas tarraingthe seo atá contrártha leis an spriocbhrainse. -pulls.is_checking=Tá seiceáil coinbhleachta cumaisc ar siúl. Bain triail eile as i gceann cúpla nóiméad. pulls.is_ancestor=Tá an brainse seo san áireamh cheana féin sa spriocbhrainse. Níl aon rud le cumasc. pulls.is_empty=Tá na hathruithe ar an mbrainse seo ar an spriocbhrainse cheana féin. Is tiomantas folamh é seo. pulls.required_status_check_failed=Níor éirigh le roinnt seiceálacha riachtanacha. diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index cc2c71a687..b89b75b2bc 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -1388,7 +1388,6 @@ pulls.add_prefix=Aggiungi prefisso <strong>%s</strong> pulls.remove_prefix=Rimuovi il prefisso <strong>%s</strong> pulls.data_broken=Questa pull request è rovinata a causa di informazioni mancanti del fork. pulls.files_conflicted=Questa pull request ha modifiche in conflitto con il branch di destinazione. -pulls.is_checking=Verifica dei conflitti di merge in corso. Riprova tra qualche istante. pulls.is_ancestor=Questo ramo è già incluso nel ramo di destinazione. Non c'è nulla da unire. pulls.is_empty=Le modifiche di questo ramo sono già nel ramo di destinazione. Questo sarà un commit vuoto. pulls.required_status_check_failed=Alcuni controlli richiesti non hanno avuto successo. diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 74fb9f2cff..9121d8c11a 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -1879,7 +1879,6 @@ pulls.add_prefix=先頭に <strong>%s</strong> を追加 pulls.remove_prefix=先頭の <strong>%s</strong> を除去 pulls.data_broken=このプルリクエストは、フォークの情報が見つからないため壊れています。 pulls.files_conflicted=このプルリクエストは、ターゲットブランチと競合する変更を含んでいます。 -pulls.is_checking=マージのコンフリクトを確認中です。 少し待ってからもう一度実行してください。 pulls.is_ancestor=このブランチは既にターゲットブランチに含まれています。マージするものはありません。 pulls.is_empty=このブランチの変更は既にターゲットブランチにあります。これは空のコミットになります。 pulls.required_status_check_failed=いくつかの必要なステータスチェックが成功していません。 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 1854db5ecb..a55a0574c7 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -1677,7 +1677,6 @@ pulls.add_prefix=Pievienot <strong>%s</strong> prefiksu pulls.remove_prefix=Noņemt <strong>%s</strong> prefiksu pulls.data_broken=Izmaiņu pieprasījums ir bojāts, jo dzēsta informācija no atdalītā repozitorija. pulls.files_conflicted=Šīs izmaiņu pieprasījuma izmaiņas konfliktē ar mērķa atzaru. -pulls.is_checking=Notiek konfliktu pārbaude, mirkli uzgaidiet un atjaunojiet lapu. pulls.is_ancestor=Atzars jau ir pilnībā iekļauts mērķā atzarā. Nav izmaiņu, ko sapludināt. pulls.is_empty=Mērķa atzars jau satur šī atzara izmaiņas. Šī revīzija būs tukša. pulls.required_status_check_failed=Dažas no pārbaudēm nebija veiksmīgas. diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 1e7ba5b3b7..4a7c05238d 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -1385,7 +1385,6 @@ pulls.add_prefix=Voeg <strong>%s</strong> prefix toe pulls.remove_prefix=Verwijder <strong>%s</strong> prefix pulls.data_broken=Deze pull-aanvraag is ongeldig wegens missende fork-informatie. pulls.files_conflicted=Dit pull request heeft wijzigingen die strijdig zijn met de doel branch. -pulls.is_checking=Controle op samenvoegingsconflicten is nog bezig. Probeer later nog een keer. pulls.is_ancestor=Deze branch is al opgenomen in de toegewezen branch. Er is niets om samen te voegen. pulls.is_empty=De wijzigingen in deze branch bevinden zich al in de toegewezen branch. Dit zal een lege commit zijn. pulls.required_status_check_failed=Sommige vereiste controles waren niet succesvol. diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 57dff3ae84..ff4a8d7b81 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -1261,7 +1261,6 @@ pulls.add_prefix=Dodaj <strong>%s</strong> prefiks pulls.remove_prefix=Usuń <strong>%s</strong> prefiks pulls.data_broken=Ten Pull Request jest uszkodzony ze względu na brakujące informacje o forku. pulls.files_conflicted=Ten Pull Request zawiera zmiany konfliktujące z docelową gałęzią. -pulls.is_checking=Sprawdzanie konfliktów ze scalaniem w toku. Spróbuj ponownie za chwilę. pulls.required_status_check_failed=Niektóre kontrole stanów nie były pomyślne. pulls.required_status_check_missing=Brakuje pewnych wymaganych etapów. pulls.required_status_check_administrator=Jako administrator, możesz wciąż scalić ten Pull Request. diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index eecf60be1a..407043ac2c 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -1670,7 +1670,6 @@ pulls.add_prefix=Adicione o prefixo <strong>%s</strong> pulls.remove_prefix=Remover o prefixo <strong>%s</strong> pulls.data_broken=Este pull request está quebrado devido a falta de informação do fork. pulls.files_conflicted=Este pull request tem alterações conflitantes com o branch de destino. -pulls.is_checking=Verificação de conflitos do merge está em andamento. Tente novamente em alguns momentos. pulls.is_ancestor=Este branch já está incluído no branch de destino. Não há nada para mesclar. pulls.is_empty=As alterações neste branch já estão na branch de destino. Este será um commit vazio. pulls.required_status_check_failed=Algumas verificações necessárias não foram bem sucedidas. diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 6bb7031183..98498b252b 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1879,7 +1879,6 @@ pulls.add_prefix=Adicione o prefixo <strong>%s</strong> pulls.remove_prefix=Remover o prefixo <strong>%s</strong> pulls.data_broken=Este pedido de integração está danificado devido à falta de informação da derivação. pulls.files_conflicted=Este pedido de integração contém modificações que entram em conflito com o ramo de destino. -pulls.is_checking=Está em andamento uma verificação de conflitos na integração. Tente novamente daqui a alguns momentos. pulls.is_ancestor=Este ramo já está incluído no ramo de destino. Não há nada a integrar. pulls.is_empty=As modificações feitas neste ramo já existem no ramo de destino. Este cometimento ficará vazio. pulls.required_status_check_failed=Algumas das verificações obrigatórias não foram bem sucedidas. @@ -3843,6 +3842,8 @@ deleted.display_name=Planeamento eliminado type-1.display_name=Planeamento individual type-2.display_name=Planeamento do repositório type-3.display_name=Planeamento da organização +enter_fullscreen=Ecrã inteiro +exit_fullscreen=Sair do ecrã inteiro [git.filemode] changed_filemode=%[1]s → %[2]s diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index f3f8a85e33..970a46ee65 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -1644,7 +1644,6 @@ pulls.add_prefix=Добавить <strong>%s</strong> префикс pulls.remove_prefix=Удалить <strong>%s</strong> префикс pulls.data_broken=Содержимое этого запроса было нарушено вследствие удаления информации форка. pulls.files_conflicted=Этот запрос на слияние имеет изменения конфликтующие с целевой веткой. -pulls.is_checking=Продолжается проверка конфликтов, пожалуйста обновите страницу несколько позже. pulls.is_ancestor=Эта ветка уже включена в целевую ветку. Сливать нечего. pulls.is_empty=Изменения из этой ветки уже есть в целевой ветке. Это будет пустой коммит. pulls.required_status_check_failed=Некоторые необходимые проверки не были пройдены. diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index a7a1b3efcd..f9acaff64f 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -1250,7 +1250,6 @@ pulls.add_prefix=<strong>%s</strong> උපසර්ගය එකතු කර pulls.remove_prefix=<strong>%s</strong> උපසර්ගය ඉවත් කරන්න pulls.data_broken=අතුරුදහන් වූ දෙබලක තොරතුරු හේතුවෙන් මෙම අදින්න ඉල්ලීම කැඩී ඇත. pulls.files_conflicted=මෙම අදින්න ඉල්ලීම ඉලක්කගත ශාඛාව සමග එකිනෙකට වෙනස් වෙනස්කම් ඇත. -pulls.is_checking=ගැටුම් පරීක්ෂා කිරීම ඒකාබද්ධ කිරීම ක්රියාත්මක වෙමින් පවතී. සුළු මොහොතකින් නැවත උත්සාහ කරන්න. pulls.required_status_check_failed=සමහර අවශ්ය චෙක්පත් සාර්ථක නොවීය. pulls.required_status_check_missing=සමහර අවශ්ය චෙක්පත් අස්ථානගත වී ඇත. pulls.required_status_check_administrator=පරිපාලකයෙකු ලෙස, ඔබ තවමත් මෙම අදින්න ඉල්ලීම ඒකාබද්ධ කළ හැකිය. diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index 79a57a089a..3ed904aca2 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -1071,7 +1071,6 @@ pulls.is_closed=Pull-förfrågan har stängts. pulls.title_wip_desc=`<a href="#">Börja titeln med <strong>%s</strong></a> för att förhindra att pull-förfrågan sammanfogas av misstag` pulls.data_broken=Pull-requesten är trasig pågrund av oexisterande information on forken. pulls.files_conflicted=Den här pull-förfrågan ha ändringar som är i konflikt med mål-branchen. -pulls.is_checking=Merge-konfliktkontroll pågår. Försök igen senare. pulls.required_status_check_failed=Vissa tvingande kontroller lyckades inte. pulls.required_status_check_missing=Vissa tvingande kontroller saknas. pulls.required_status_check_administrator=Som administratör kan du fortfarande merga den här pull requesten. diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index d8d00c89b1..4e389217e9 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -1785,7 +1785,6 @@ pulls.add_prefix=<strong>%s</strong> ön ekini ekle pulls.remove_prefix=<strong>%s</strong> ön ekini kaldır pulls.data_broken=Bu değişiklik isteği, çatallama bilgilerinin eksik olması nedeniyle bozuldu. pulls.files_conflicted=Bu değişiklik isteğinde, hedef dalla çakışan değişiklikler var. -pulls.is_checking=Birleştirme çakışması denetimi devam ediyor. Birkaç dakika sonra tekrar deneyin. pulls.is_ancestor=Bu dal zaten hedef dalda mevcut. Birleştirilecek bir şey yok. pulls.is_empty=Bu daldaki değişiklikler zaten hedef dalda mevcut. Bu boş bir işleme olacaktır. pulls.required_status_check_failed=Bazı gerekli denetimler başarılı olmadı. diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 20be6e586b..cc9d668bfb 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -1297,7 +1297,6 @@ pulls.add_prefix=Додати префікс <strong>%s</strong> pulls.remove_prefix=Видалити префікс <strong>%s</strong> pulls.data_broken=Зміст цього запиту було порушено внаслідок видалення інформації Форком. Цей запит тягнеться через відсутність інформації про вилучення. pulls.files_conflicted=Цей запит має зміни, що конфліктують з цільовою гілкою. -pulls.is_checking=Триває перевірка конфліктів, будь ласка обновіть сторінку дещо пізніше. pulls.required_status_check_failed=Деякі необхідні перевірки виконані з помилками. pulls.required_status_check_missing=Декілька з необхідних перевірок відсутні. pulls.required_status_check_administrator=Як адміністратор ви все одно можете об'єднати цей запит на злиття. diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index ae66c5a2e3..b15d2c80a1 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -1878,7 +1878,6 @@ pulls.add_prefix=添加 <strong>%s</strong> 前缀 pulls.remove_prefix=删除 <strong>%s</strong> 前缀 pulls.data_broken=此合并请求因为派生仓库信息缺失而中断。 pulls.files_conflicted=此合并请求有变更与目标分支冲突。 -pulls.is_checking=正在进行合并冲突检测,请稍后再试。 pulls.is_ancestor=此分支已经包含在目标分支中,没有什么可以合并。 pulls.is_empty=此分支上的更改已经在目标分支上。这将是一个空提交。 pulls.required_status_check_failed=一些必要的检查没有成功 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 2e7a71f829..7538cbaf2d 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -1834,7 +1834,6 @@ pulls.add_prefix=加入 <strong>%s</strong> 前綴 pulls.remove_prefix=移除 <strong>%s</strong> 前綴 pulls.data_broken=此合併請求已損毀,因為遺失 Fork 資訊。 pulls.files_conflicted=此合併請求有變更和目標分支衝突。 -pulls.is_checking=正在進行合併衝突檢查,請稍後再試。 pulls.is_ancestor=這個分支已經合併到目標分支上。沒有可以合併的內容。 pulls.is_empty=在這個分支上的更動都已經套用在目標分支上。這將會產生一個空的提交。 pulls.required_status_check_failed=未通過某些必要的檢查。 diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index c0b1810191..04d9b10787 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -202,6 +202,10 @@ func GetPullRequest(ctx *context.APIContext) { ctx.APIErrorInternal(err) return } + + // Consider API access a view for delayed checking. + pull_service.StartPullRequestCheckOnView(ctx, pr) + ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer)) } @@ -287,6 +291,10 @@ func GetPullRequestByBaseHead(ctx *context.APIContext) { ctx.APIErrorInternal(err) return } + + // Consider API access a view for delayed checking. + pull_service.StartPullRequestCheckOnView(ctx, pr) + ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer)) } @@ -921,7 +929,7 @@ func MergePullRequest(ctx *context.APIContext) { if err := pull_service.CheckPullMergeable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge); err != nil { if errors.Is(err, pull_service.ErrIsClosed) { ctx.APIErrorNotFound() - } else if errors.Is(err, pull_service.ErrUserNotAllowedToMerge) { + } else if errors.Is(err, pull_service.ErrNoPermissionToMerge) { ctx.APIError(http.StatusMethodNotAllowed, "User not allowed to merge PR") } else if errors.Is(err, pull_service.ErrHasMerged) { ctx.APIError(http.StatusMethodNotAllowed, "") @@ -929,7 +937,7 @@ func MergePullRequest(ctx *context.APIContext) { ctx.APIError(http.StatusMethodNotAllowed, "Work in progress PRs cannot be merged") } else if errors.Is(err, pull_service.ErrNotMergeableState) { ctx.APIError(http.StatusMethodNotAllowed, "Please try again later") - } else if pull_service.IsErrDisallowedToMerge(err) { + } else if errors.Is(err, pull_service.ErrNotReadyToMerge) { ctx.APIError(http.StatusMethodNotAllowed, err) } else if asymkey_service.IsErrWontSign(err) { ctx.APIError(http.StatusMethodNotAllowed, err) diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index be9923c98f..dd9d0bc15e 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -4,6 +4,7 @@ package private import ( + "errors" "fmt" "net/http" "os" @@ -374,7 +375,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r // Check all status checks and reviews are ok if err := pull_service.CheckPullBranchProtections(ctx, pr, true); err != nil { - if pull_service.IsErrDisallowedToMerge(err) { + if errors.Is(err, pull_service.ErrNotReadyToMerge) { log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", ctx.opts.UserID, branchName, repo, pr.Index, err.Error()) ctx.JSON(http.StatusForbidden, private.Response{ UserMsg: fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, ctx.opts.PullRequestID, err.Error()), diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index dbbe29a3c3..86ee56b467 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -43,7 +43,8 @@ const ( tplIssueChoose templates.TplName = "repo/issue/choose" tplIssueView templates.TplName = "repo/issue/view" - tplReactions templates.TplName = "repo/issue/view_content/reactions" + tplPullMergeBox templates.TplName = "repo/issue/view_content/pull_merge_box" + tplReactions templates.TplName = "repo/issue/view_content/reactions" issueTemplateKey = "IssueTemplate" issueTemplateTitleKey = "IssueTemplateTitle" diff --git a/routers/web/repo/issue_comment.go b/routers/web/repo/issue_comment.go index 45463200f6..8adce26ccc 100644 --- a/routers/web/repo/issue_comment.go +++ b/routers/web/repo/issue_comment.go @@ -96,7 +96,7 @@ func NewComment(ctx *context.Context) { // Regenerate patch and test conflict. if pr == nil { issue.PullRequest.HeadCommitID = "" - pull_service.AddToTaskQueue(ctx, issue.PullRequest) + pull_service.StartPullRequestCheckImmediately(ctx, issue.PullRequest) } // check whether the ref of PR <refs/pulls/pr_index/head> in base repo is consistent with the head commit of head branch in the head repo diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index 3ffcdfe676..13b9d83da4 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -31,6 +31,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates/vars" + "code.gitea.io/gitea/modules/util" asymkey_service "code.gitea.io/gitea/services/asymkey" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context/upload" @@ -271,8 +272,23 @@ func combineLabelComments(issue *issues_model.Issue) { } } -// ViewIssue render issue view page -func ViewIssue(ctx *context.Context) { +func prepareIssueViewLoad(ctx *context.Context) *issues_model.Issue { + issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) + if err != nil { + ctx.NotFoundOrServerError("GetIssueByIndex", issues_model.IsErrIssueNotExist, err) + return nil + } + issue.Repo = ctx.Repo.Repository + ctx.Data["Issue"] = issue + + if err = issue.LoadPullRequest(ctx); err != nil { + ctx.ServerError("LoadPullRequest", err) + return nil + } + return issue +} + +func handleViewIssueRedirectExternal(ctx *context.Context) { if ctx.PathParam("type") == "issues" { // If issue was requested we check if repo has external tracker and redirect extIssueUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypeExternalTracker) @@ -294,18 +310,18 @@ func ViewIssue(ctx *context.Context) { return } } +} - issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) - if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.NotFound(err) - } else { - ctx.ServerError("GetIssueByIndex", err) - } +// ViewIssue render issue view page +func ViewIssue(ctx *context.Context) { + handleViewIssueRedirectExternal(ctx) + if ctx.Written() { return } - if issue.Repo == nil { - issue.Repo = ctx.Repo.Repository + + issue := prepareIssueViewLoad(ctx) + if ctx.Written() { + return } // Make sure type and URL matches. @@ -337,12 +353,12 @@ func ViewIssue(ctx *context.Context) { ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled upload.AddUploadContext(ctx, "comment") - if err = issue.LoadAttributes(ctx); err != nil { + if err := issue.LoadAttributes(ctx); err != nil { ctx.ServerError("LoadAttributes", err) return } - if err = filterXRefComments(ctx, issue); err != nil { + if err := filterXRefComments(ctx, issue); err != nil { ctx.ServerError("filterXRefComments", err) return } @@ -351,7 +367,7 @@ func ViewIssue(ctx *context.Context) { if ctx.IsSigned { // Update issue-user. - if err = activities_model.SetIssueReadBy(ctx, issue.ID, ctx.Doer.ID); err != nil { + if err := activities_model.SetIssueReadBy(ctx, issue.ID, ctx.Doer.ID); err != nil { ctx.ServerError("ReadBy", err) return } @@ -365,15 +381,13 @@ func ViewIssue(ctx *context.Context) { prepareFuncs := []func(*context.Context, *issues_model.Issue){ prepareIssueViewContent, - func(ctx *context.Context, issue *issues_model.Issue) { - preparePullViewPullInfo(ctx, issue) - }, prepareIssueViewCommentsAndSidebarParticipants, - preparePullViewReviewAndMerge, prepareIssueViewSidebarWatch, prepareIssueViewSidebarTimeTracker, prepareIssueViewSidebarDependency, prepareIssueViewSidebarPin, + func(ctx *context.Context, issue *issues_model.Issue) { preparePullViewPullInfo(ctx, issue) }, + preparePullViewReviewAndMerge, } for _, prepareFunc := range prepareFuncs { @@ -412,9 +426,25 @@ func ViewIssue(ctx *context.Context) { return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee) } + if issue.PullRequest != nil && !issue.PullRequest.IsChecking() && !setting.IsProd { + ctx.Data["PullMergeBoxReloadingInterval"] = 1 // in dev env, force using the reloading logic to make sure it won't break + } + ctx.HTML(http.StatusOK, tplIssueView) } +func ViewPullMergeBox(ctx *context.Context) { + issue := prepareIssueViewLoad(ctx) + if !issue.IsPull { + ctx.NotFound(nil) + return + } + preparePullViewPullInfo(ctx, issue) + preparePullViewReviewAndMerge(ctx, issue) + ctx.Data["PullMergeBoxReloading"] = issue.PullRequest.IsChecking() + ctx.HTML(http.StatusOK, tplPullMergeBox) +} + func prepareIssueViewSidebarDependency(ctx *context.Context, issue *issues_model.Issue) { if issue.IsPull && !ctx.Repo.CanRead(unit.TypeIssues) { ctx.Data["IssueDependencySearchType"] = "pulls" @@ -792,6 +822,8 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss allowMerge := false canWriteToHeadRepo := false + pull_service.StartPullRequestCheckOnView(ctx, pull) + if ctx.IsSigned { if err := pull.LoadHeadRepo(ctx); err != nil { log.Error("LoadHeadRepo: %v", err) @@ -838,6 +870,7 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss } } + ctx.Data["PullMergeBoxReloadingInterval"] = util.Iif(pull != nil && pull.IsChecking(), 2000, 0) ctx.Data["CanWriteToHeadRepo"] = canWriteToHeadRepo ctx.Data["ShowMergeInstructions"] = canWriteToHeadRepo ctx.Data["AllowMerge"] = allowMerge @@ -958,5 +991,4 @@ func prepareIssueViewContent(ctx *context.Context, issue *issues_model.Issue) { ctx.ServerError("roleDescriptor", err) return } - ctx.Data["Issue"] = issue } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index a33542fd37..15c9658fa8 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1052,7 +1052,7 @@ func MergePullRequest(ctx *context.Context) { } else { ctx.JSONError(ctx.Tr("repo.issues.closed_title")) } - case errors.Is(err, pull_service.ErrUserNotAllowedToMerge): + case errors.Is(err, pull_service.ErrNoPermissionToMerge): ctx.JSONError(ctx.Tr("repo.pulls.update_not_allowed")) case errors.Is(err, pull_service.ErrHasMerged): ctx.JSONError(ctx.Tr("repo.pulls.has_merged")) @@ -1060,7 +1060,7 @@ func MergePullRequest(ctx *context.Context) { ctx.JSONError(ctx.Tr("repo.pulls.no_merge_wip")) case errors.Is(err, pull_service.ErrNotMergeableState): ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready")) - case pull_service.IsErrDisallowedToMerge(err): + case errors.Is(err, pull_service.ErrNotReadyToMerge): ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready")) case asymkey_service.IsErrWontSign(err): ctx.JSONError(err.Error()) // has no translation ... diff --git a/routers/web/web.go b/routers/web/web.go index f28dc6baa4..bd850baec0 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1505,6 +1505,7 @@ func registerWebRoutes(m *web.Router) { m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue) m.Get(".diff", repo.DownloadPullDiff) m.Get(".patch", repo.DownloadPullPatch) + m.Get("/merge_box", repo.ViewPullMergeBox) m.Group("/commits", func() { m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) m.Get("/list", repo.GetPullCommits) diff --git a/services/agit/agit.go b/services/agit/agit.go index 1e6ce93312..0fe28c5d66 100644 --- a/services/agit/agit.go +++ b/services/agit/agit.go @@ -204,7 +204,7 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git. return nil, fmt.Errorf("failed to update pull ref. Error: %w", err) } - pull_service.AddToTaskQueue(ctx, pr) + pull_service.StartPullRequestCheckImmediately(ctx, pr) err = pr.LoadIssue(ctx) if err != nil { return nil, fmt.Errorf("failed to load pull issue. Error: %w", err) diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go index 9d2f7f4857..0520a097d3 100644 --- a/services/automerge/automerge.go +++ b/services/automerge/automerge.go @@ -289,7 +289,7 @@ func handlePullRequestAutoMerge(pullID int64, sha string) { } if err := pull_service.CheckPullMergeable(ctx, doer, &perm, pr, pull_service.MergeCheckTypeGeneral, false); err != nil { - if errors.Is(err, pull_service.ErrUserNotAllowedToMerge) { + if errors.Is(err, pull_service.ErrNotReadyToMerge) { log.Info("%-v was scheduled to automerge by an unauthorized user", pr) return } diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index 82d756dc56..b6caa494c6 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -556,7 +556,7 @@ func (g *GiteaLocalUploader) CreatePullRequests(ctx context.Context, prs ...*bas } for _, pr := range gprs { g.issues[pr.Issue.Index] = pr.Issue - pull.AddToTaskQueue(ctx, pr) + pull.StartPullRequestCheckImmediately(ctx, pr) } return nil } diff --git a/services/pull/check.go b/services/pull/check.go index b036970fbf..5d8990aa00 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -10,6 +10,7 @@ import ( "fmt" "strconv" "strings" + "time" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" @@ -25,6 +26,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/queue" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" asymkey_service "code.gitea.io/gitea/services/asymkey" notify_service "code.gitea.io/gitea/services/notify" @@ -34,27 +36,88 @@ import ( var prPatchCheckerQueue *queue.WorkerPoolQueue[string] var ( - ErrIsClosed = errors.New("pull is closed") - ErrUserNotAllowedToMerge = ErrDisallowedToMerge{} - ErrHasMerged = errors.New("has already been merged") - ErrIsWorkInProgress = errors.New("work in progress PRs cannot be merged") - ErrIsChecking = errors.New("cannot merge while conflict checking is in progress") - ErrNotMergeableState = errors.New("not in mergeable state") - ErrDependenciesLeft = errors.New("is blocked by an open dependency") + ErrIsClosed = errors.New("pull is closed") + ErrNoPermissionToMerge = errors.New("no permission to merge") + ErrNotReadyToMerge = errors.New("not ready to merge") + ErrHasMerged = errors.New("has already been merged") + ErrIsWorkInProgress = errors.New("work in progress PRs cannot be merged") + ErrIsChecking = errors.New("cannot merge while conflict checking is in progress") + ErrNotMergeableState = errors.New("not in mergeable state") + ErrDependenciesLeft = errors.New("is blocked by an open dependency") ) -// AddToTaskQueue adds itself to pull request test task queue. -func AddToTaskQueue(ctx context.Context, pr *issues_model.PullRequest) { +func markPullRequestStatusAsChecking(ctx context.Context, pr *issues_model.PullRequest) bool { pr.Status = issues_model.PullRequestStatusChecking err := pr.UpdateColsIfNotMerged(ctx, "status") if err != nil { - log.Error("AddToTaskQueue(%-v).UpdateCols.(add to queue): %v", pr, err) + log.Error("UpdateColsIfNotMerged failed, pr: %-v, err: %v", pr, err) + return false + } + pr, err = issues_model.GetPullRequestByID(ctx, pr.ID) + if err != nil { + log.Error("GetPullRequestByID failed, pr: %-v, err: %v", pr, err) + return false + } + return pr.Status == issues_model.PullRequestStatusChecking +} + +var AddPullRequestToCheckQueue = realAddPullRequestToCheckQueue + +func realAddPullRequestToCheckQueue(prID int64) { + err := prPatchCheckerQueue.Push(strconv.FormatInt(prID, 10)) + if err != nil && !errors.Is(err, queue.ErrAlreadyInQueue) { + log.Error("Error adding %v to the pull requests check queue: %v", prID, err) + } +} + +func StartPullRequestCheckImmediately(ctx context.Context, pr *issues_model.PullRequest) { + if !markPullRequestStatusAsChecking(ctx, pr) { return } - log.Trace("Adding %-v to the test pull requests queue", pr) - err = prPatchCheckerQueue.Push(strconv.FormatInt(pr.ID, 10)) - if err != nil && err != queue.ErrAlreadyInQueue { - log.Error("Error adding %-v to the test pull requests queue: %v", pr, err) + AddPullRequestToCheckQueue(pr.ID) +} + +// StartPullRequestCheckDelayable will delay the check if the pull request was not updated recently. +// When the "base" branch gets updated, all PRs targeting that "base" branch need to re-check whether +// they are mergeable. +// When there are too many stale PRs, each "base" branch update will consume a lot of system resources. +// So we can delay the checks for PRs that were not updated recently, only mark their status as +// "checking", and then next time when these PRs are updated or viewed, the real checks will run. +func StartPullRequestCheckDelayable(ctx context.Context, pr *issues_model.PullRequest) { + if !markPullRequestStatusAsChecking(ctx, pr) { + return + } + + if setting.Repository.PullRequest.DelayCheckForInactiveDays >= 0 { + if err := pr.LoadIssue(ctx); err != nil { + return + } + duration := 24 * time.Hour * time.Duration(setting.Repository.PullRequest.DelayCheckForInactiveDays) + if pr.Issue.UpdatedUnix.AddDuration(duration) <= timeutil.TimeStampNow() { + return + } + } + + AddPullRequestToCheckQueue(pr.ID) +} + +func StartPullRequestCheckOnView(ctx context.Context, pr *issues_model.PullRequest) { + // TODO: its correctness totally depends on the "unique queue" feature and the global lock. + // So duplicate "start" requests will be ignored if there is already a task in the queue or one is running. + // Ideally in the future we should decouple the "unique queue" feature from the "start" request. + if pr.Status == issues_model.PullRequestStatusChecking { + if setting.IsInTesting { + // In testing mode, there might be an "immediate" queue, which is not a real queue, everything is executed in the same goroutine + // So we can't use the global lock here, otherwise it will cause a deadlock. + AddPullRequestToCheckQueue(pr.ID) + } else { + // When a PR check starts, the task is popped from the queue and the task handler acquires the global lock + // So we need to acquire the global lock here to prevent from duplicate tasks + _, _ = globallock.TryLockAndDo(ctx, getPullWorkingLockKey(pr.ID), func(ctx context.Context) error { + AddPullRequestToCheckQueue(pr.ID) // the queue is a unique queue and won't add the same task again + return nil + }) + } } } @@ -84,7 +147,7 @@ func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *acc log.Error("Error whilst checking if %-v is allowed to merge %-v: %v", doer, pr, err) return err } else if !allowedMerge { - return ErrUserNotAllowedToMerge + return ErrNoPermissionToMerge } if mergeCheckType == MergeCheckTypeManually { @@ -105,7 +168,7 @@ func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *acc } if err := CheckPullBranchProtections(ctx, pr, false); err != nil { - if !IsErrDisallowedToMerge(err) { + if !errors.Is(err, ErrNotReadyToMerge) { log.Error("Error whilst checking pull branch protection for %-v: %v", pr, err) return err } @@ -172,10 +235,10 @@ func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer return sign, err } -// checkAndUpdateStatus checks if pull request is possible to leaving checking status, +// markPullRequestAsMergeable checks if pull request is possible to leaving checking status, // and set to be either conflict or mergeable. -func checkAndUpdateStatus(ctx context.Context, pr *issues_model.PullRequest) { - // If status has not been changed to conflict by testPatch then we are mergeable +func markPullRequestAsMergeable(ctx context.Context, pr *issues_model.PullRequest) { + // If status has not been changed to conflict by testPullRequestTmpRepoBranchMergeable then we are mergeable if pr.Status == issues_model.PullRequestStatusChecking { pr.Status = issues_model.PullRequestStatusMergeable } @@ -310,6 +373,10 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool { // InitializePullRequests checks and tests untested patches of pull requests. func InitializePullRequests(ctx context.Context) { + // If we prefer to delay the checks, then no need to do any check during startup, there should be not much difference + if setting.Repository.PullRequest.DelayCheckForInactiveDays >= 0 { + return + } prs, err := issues_model.GetPullRequestIDsByCheckStatus(ctx, issues_model.PullRequestStatusChecking) if err != nil { log.Error("Find Checking PRs: %v", err) @@ -320,24 +387,12 @@ func InitializePullRequests(ctx context.Context) { case <-ctx.Done(): return default: - log.Trace("Adding PR[%d] to the pull requests patch checking queue", prID) - if err := prPatchCheckerQueue.Push(strconv.FormatInt(prID, 10)); err != nil { - log.Error("Error adding PR[%d] to the pull requests patch checking queue %v", prID, err) - } + AddPullRequestToCheckQueue(prID) } } } -// handle passed PR IDs and test the PRs -func handler(items ...string) []string { - for _, s := range items { - id, _ := strconv.ParseInt(s, 10, 64) - testPR(id) - } - return nil -} - -func testPR(id int64) { +func checkPullRequestMergeable(id int64) { ctx := graceful.GetManager().HammerContext() releaser, err := globallock.Lock(ctx, getPullWorkingLockKey(id)) if err != nil { @@ -351,7 +406,7 @@ func testPR(id int64) { pr, err := issues_model.GetPullRequestByID(ctx, id) if err != nil { - log.Error("Unable to GetPullRequestByID[%d] for testPR: %v", id, err) + log.Error("Unable to GetPullRequestByID[%d] for checkPullRequestMergeable: %v", id, err) return } @@ -370,15 +425,15 @@ func testPR(id int64) { return } - if err := TestPatch(pr); err != nil { - log.Error("testPatch[%-v]: %v", pr, err) + if err := testPullRequestBranchMergeable(pr); err != nil { + log.Error("testPullRequestTmpRepoBranchMergeable[%-v]: %v", pr, err) pr.Status = issues_model.PullRequestStatusError if err := pr.UpdateCols(ctx, "status"); err != nil { log.Error("update pr [%-v] status to PullRequestStatusError failed: %v", pr, err) } return } - checkAndUpdateStatus(ctx, pr) + markPullRequestAsMergeable(ctx, pr) } // CheckPRsForBaseBranch check all pulls with baseBrannch @@ -387,17 +442,21 @@ func CheckPRsForBaseBranch(ctx context.Context, baseRepo *repo_model.Repository, if err != nil { return err } - for _, pr := range prs { - AddToTaskQueue(ctx, pr) + StartPullRequestCheckImmediately(ctx, pr) } - return nil } // Init runs the task queue to test all the checking status pull requests func Init() error { - prPatchCheckerQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_patch_checker", handler) + prPatchCheckerQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_patch_checker", func(items ...string) []string { + for _, s := range items { + id, _ := strconv.ParseInt(s, 10, 64) + checkPullRequestMergeable(id) + } + return nil + }) if prPatchCheckerQueue == nil { return errors.New("unable to create pr_patch_checker queue") diff --git a/services/pull/check_test.go b/services/pull/check_test.go index 6d85ac158e..fa3a676ef1 100644 --- a/services/pull/check_test.go +++ b/services/pull/check_test.go @@ -36,7 +36,7 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) { assert.NoError(t, err) pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) - AddToTaskQueue(db.DefaultContext, pr) + StartPullRequestCheckImmediately(db.DefaultContext, pr) assert.Eventually(t, func() bool { pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) diff --git a/services/pull/merge.go b/services/pull/merge.go index 9804d8aac1..256db847ef 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -518,25 +518,6 @@ func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p a return false, nil } -// ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it. -type ErrDisallowedToMerge struct { - Reason string -} - -// IsErrDisallowedToMerge checks if an error is an ErrDisallowedToMerge. -func IsErrDisallowedToMerge(err error) bool { - _, ok := err.(ErrDisallowedToMerge) - return ok -} - -func (err ErrDisallowedToMerge) Error() string { - return fmt.Sprintf("not allowed to merge [reason: %s]", err.Reason) -} - -func (err ErrDisallowedToMerge) Unwrap() error { - return util.ErrPermissionDenied -} - // CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks) func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullRequest, skipProtectedFilesCheck bool) (err error) { if err = pr.LoadBaseRepo(ctx); err != nil { @@ -556,31 +537,21 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques return err } if !isPass { - return ErrDisallowedToMerge{ - Reason: "Not all required status checks successful", - } + return util.ErrorWrap(ErrNotReadyToMerge, "Not all required status checks successful") } if !issues_model.HasEnoughApprovals(ctx, pb, pr) { - return ErrDisallowedToMerge{ - Reason: "Does not have enough approvals", - } + return util.ErrorWrap(ErrNotReadyToMerge, "Does not have enough approvals") } if issues_model.MergeBlockedByRejectedReview(ctx, pb, pr) { - return ErrDisallowedToMerge{ - Reason: "There are requested changes", - } + return util.ErrorWrap(ErrNotReadyToMerge, "There are requested changes") } if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pr) { - return ErrDisallowedToMerge{ - Reason: "There are official review requests", - } + return util.ErrorWrap(ErrNotReadyToMerge, "There are official review requests") } if issues_model.MergeBlockedByOutdatedBranch(pb, pr) { - return ErrDisallowedToMerge{ - Reason: "The head branch is behind the base branch", - } + return util.ErrorWrap(ErrNotReadyToMerge, "The head branch is behind the base branch") } if skipProtectedFilesCheck { @@ -588,9 +559,7 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques } if pb.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) { - return ErrDisallowedToMerge{ - Reason: "Changed protected files", - } + return util.ErrorWrap(ErrNotReadyToMerge, "Changed protected files") } return nil @@ -709,7 +678,7 @@ func SetMerged(ctx context.Context, pr *issues_model.PullRequest, mergedCommitID return false, fmt.Errorf("ChangeIssueStatus: %w", err) } - // We need to save all of the data used to compute this merge as it may have already been changed by TestPatch. FIXME: need to set some state to prevent TestPatch from running whilst we are merging. + // We need to save all of the data used to compute this merge as it may have already been changed by testPullRequestBranchMergeable. FIXME: need to set some state to prevent testPullRequestBranchMergeable from running whilst we are merging. if cnt, err := db.GetEngine(ctx).Where("id = ?", pr.ID). And("has_merged = ?", false). Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix, conflicted_files"). diff --git a/services/pull/merge_prepare.go b/services/pull/merge_prepare.go index 593cba550a..719cc6b965 100644 --- a/services/pull/merge_prepare.go +++ b/services/pull/merge_prepare.go @@ -23,7 +23,7 @@ import ( ) type mergeContext struct { - *prContext + *prTmpRepoContext doer *user_model.User sig *git.Signature committer *git.Signature @@ -68,8 +68,8 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque } mergeCtx = &mergeContext{ - prContext: prCtx, - doer: doer, + prTmpRepoContext: prCtx, + doer: doer, } if expectedHeadCommitID != "" { diff --git a/services/pull/patch.go b/services/pull/patch.go index 7a24237724..153e0baf87 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -67,9 +67,8 @@ var patchErrorSuffices = []string{ ": does not exist in index", } -// TestPatch will test whether a simple patch will apply -func TestPatch(pr *issues_model.PullRequest) error { - ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("TestPatch: %s", pr)) +func testPullRequestBranchMergeable(pr *issues_model.PullRequest) error { + ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("testPullRequestBranchMergeable: %s", pr)) defer finished() prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr) @@ -81,10 +80,10 @@ func TestPatch(pr *issues_model.PullRequest) error { } defer cancel() - return testPatch(ctx, prCtx, pr) + return testPullRequestTmpRepoBranchMergeable(ctx, prCtx, pr) } -func testPatch(ctx context.Context, prCtx *prContext, pr *issues_model.PullRequest) error { +func testPullRequestTmpRepoBranchMergeable(ctx context.Context, prCtx *prTmpRepoContext, pr *issues_model.PullRequest) error { gitRepo, err := git.OpenRepository(ctx, prCtx.tmpBasePath) if err != nil { return fmt.Errorf("OpenRepository: %w", err) @@ -380,7 +379,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * return false, nil } - log.Trace("PullRequest[%d].testPatch (patchPath): %s", pr.ID, patchPath) + log.Trace("PullRequest[%d].testPullRequestTmpRepoBranchMergeable (patchPath): %s", pr.ID, patchPath) // 4. Read the base branch in to the index of the temporary repository _, _, err = git.NewCommand("read-tree", "base").RunStdString(gitRepo.Ctx, &git.RunOpts{Dir: tmpBasePath}) @@ -450,7 +449,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * scanner := bufio.NewScanner(stderrReader) for scanner.Scan() { line := scanner.Text() - log.Trace("PullRequest[%d].testPatch: stderr: %s", pr.ID, line) + log.Trace("PullRequest[%d].testPullRequestTmpRepoBranchMergeable: stderr: %s", pr.ID, line) if strings.HasPrefix(line, prefix) { conflict = true filepath := strings.TrimSpace(strings.Split(line[len(prefix):], ":")[0]) diff --git a/services/pull/pull.go b/services/pull/pull.go index 13cbb40110..e3053409b2 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -96,7 +96,7 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error { } defer cancel() - if err := testPatch(ctx, prCtx, pr); err != nil { + if err := testPullRequestTmpRepoBranchMergeable(ctx, prCtx, pr); err != nil { return err } @@ -314,12 +314,12 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer pr.BaseBranch = targetBranch // Refresh patch - if err := TestPatch(pr); err != nil { + if err := testPullRequestBranchMergeable(pr); err != nil { return err } // Update target branch, PR diff and status - // This is the same as checkAndUpdateStatus in check service, but also updates base_branch + // This is the same as markPullRequestAsMergeable in check service, but also updates base_branch if pr.Status == issues_model.PullRequestStatusChecking { pr.Status = issues_model.PullRequestStatusMergeable } @@ -409,7 +409,7 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) { continue } - AddToTaskQueue(ctx, pr) + StartPullRequestCheckImmediately(ctx, pr) comment, err := CreatePushPullComment(ctx, opts.Doer, pr, opts.OldCommitID, opts.NewCommitID) if err == nil && comment != nil { notify_service.PullRequestPushCommits(ctx, opts.Doer, pr, comment) @@ -502,7 +502,7 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) { log.Error("UpdateCommitDivergence: %v", err) } } - AddToTaskQueue(ctx, pr) + StartPullRequestCheckDelayable(ctx, pr) } }) } diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index d543e3d4a3..72406482e0 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -28,7 +28,7 @@ const ( stagingBranch = "staging" // this is used for a working branch ) -type prContext struct { +type prTmpRepoContext struct { context.Context tmpBasePath string pr *issues_model.PullRequest @@ -36,7 +36,7 @@ type prContext struct { errbuf *strings.Builder // any use should be preceded by a Reset and preferably after use } -func (ctx *prContext) RunOpts() *git.RunOpts { +func (ctx *prTmpRepoContext) RunOpts() *git.RunOpts { ctx.outbuf.Reset() ctx.errbuf.Reset() return &git.RunOpts{ @@ -48,7 +48,7 @@ func (ctx *prContext) RunOpts() *git.RunOpts { // createTemporaryRepoForPR creates a temporary repo with "base" for pr.BaseBranch and "tracking" for pr.HeadBranch // it also create a second base branch called "original_base" -func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) (prCtx *prContext, cancel context.CancelFunc, err error) { +func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) (prCtx *prTmpRepoContext, cancel context.CancelFunc, err error) { if err := pr.LoadHeadRepo(ctx); err != nil { log.Error("%-v LoadHeadRepo: %v", pr, err) return nil, nil, fmt.Errorf("%v LoadHeadRepo: %w", pr, err) @@ -81,7 +81,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) } cancel = cleanup - prCtx = &prContext{ + prCtx = &prTmpRepoContext{ Context: ctx, tmpBasePath: tmpBasePath, pr: pr, diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 4671cfd6db..dae3c4ee6a 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -68,7 +68,7 @@ {{template "repo/issue/view_content/comments" .}} {{if and .Issue.IsPull (not $.Repository.IsArchived)}} - {{template "repo/issue/view_content/pull".}} + {{template "repo/issue/view_content/pull_merge_box".}} {{end}} {{if .IsSigned}} diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull_merge_box.tmpl index 064b62e128..641520247d 100644 --- a/templates/repo/issue/view_content/pull.tmpl +++ b/templates/repo/issue/view_content/pull_merge_box.tmpl @@ -1,7 +1,13 @@ {{if and .Issue.PullRequest.HasMerged (not .IsPullBranchDeletable)}} {{/* Then the merge box will not be displayed because this page already contains enough information */}} {{else}} -<div class="timeline-item comment merge box"> +<div class="timeline-item comment pull-merge-box" + data-global-init="initRepoPullMergeBox" + {{if .PullMergeBoxReloadingInterval}} + data-pull-merge-box-reloading-interval="{{.PullMergeBoxReloadingInterval}}" + data-pull-link="{{.Issue.Link}}" + {{end}} +> <div class="timeline-avatar text {{if .Issue.PullRequest.HasMerged}}purple {{- else if .Issue.IsClosed}}grey {{- else if .IsPullWorkInProgress}}grey @@ -97,7 +103,7 @@ {{template "repo/issue/view_content/update_branch_by_merge" $}} {{else if .Issue.PullRequest.IsChecking}} <div class="item"> - {{svg "octicon-sync"}} + {{svg "octicon-sync" 16 "circular-spin"}} {{ctx.Locale.Tr "repo.pulls.is_checking"}} </div> {{else if .Issue.PullRequest.IsAncestor}} @@ -191,10 +197,11 @@ </div> {{end}} {{end}} + {{template "repo/issue/view_content/update_branch_by_merge" $}} + {{if .Issue.PullRequest.IsEmpty}} <div class="divider"></div> - <div class="item"> {{svg "octicon-alert"}} {{ctx.Locale.Tr "repo.pulls.is_empty"}} @@ -216,7 +223,7 @@ const defaultMergeMessage = {{.DefaultMergeBody}}; const defaultSquashMergeMessage = {{.DefaultSquashMergeBody}}; const mergeForm = { - 'baseLink': {{.Link}}, + 'baseLink': {{.Issue.Link}}, 'textCancel': {{ctx.Locale.Tr "cancel"}}, 'textDeleteBranch': {{ctx.Locale.Tr "repo.branch.delete" .HeadTarget}}, 'textAutoMergeButtonWhenSucceed': {{ctx.Locale.Tr "repo.pulls.auto_merge_button_when_succeed"}}, @@ -318,7 +325,7 @@ {{if .IsBlockedByApprovals}} <div class="item text red"> {{svg "octicon-x"}} - {{ctx.Locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .ProtectedBranch.RequiredApprovals}} + {{ctx.Locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .ProtectedBranch.RequiredApprovals}} </div> {{else if .IsBlockedByRejection}} <div class="item text red"> @@ -377,7 +384,7 @@ */}} {{if and $.StillCanManualMerge (not $showGeneralMergeForm)}} <div class="divider"></div> - <form class="ui form form-fetch-action" action="{{.Link}}/merge" method="post">{{/* another similar form is in PullRequestMergeForm.vue*/}} + <form class="ui form form-fetch-action" action="{{.Issue.Link}}/merge" method="post">{{/* another similar form is in PullRequestMergeForm.vue*/}} {{.CsrfTokenHtml}} <div class="field"> <input type="text" name="merge_commit_id" placeholder="{{ctx.Locale.Tr "repo.pulls.merge_commit_id"}}"> diff --git a/templates/repo/issue/view_content/update_branch_by_merge.tmpl b/templates/repo/issue/view_content/update_branch_by_merge.tmpl index e0ed262f17..5d959bf0b3 100644 --- a/templates/repo/issue/view_content/update_branch_by_merge.tmpl +++ b/templates/repo/issue/view_content/update_branch_by_merge.tmpl @@ -9,7 +9,7 @@ {{if and $.UpdateAllowed $.UpdateByRebaseAllowed}} <div class="tw-inline-block"> <div id="update-pr-branch-with-base" class="ui buttons"> - <button class="ui button" data-do="{{$.Link}}/update" data-redirect="{{$.Link}}"> + <button class="ui button" data-do="{{$.Issue.Link}}/update" data-redirect="{{$.Issue.Link}}"> <span class="button-text"> {{ctx.Locale.Tr "repo.pulls.update_branch"}} </span> @@ -17,15 +17,15 @@ <div class="ui dropdown icon button"> {{svg "octicon-triangle-down"}} <div class="menu"> - <a class="item active selected" data-do="{{$.Link}}/update">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</a> - <a class="item" data-do="{{$.Link}}/update?style=rebase">{{ctx.Locale.Tr "repo.pulls.update_branch_rebase"}}</a> + <a class="item active selected" data-do="{{$.Issue.Link}}/update">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</a> + <a class="item" data-do="{{$.Issue.Link}}/update?style=rebase">{{ctx.Locale.Tr "repo.pulls.update_branch_rebase"}}</a> </div> </div> </div> </div> {{end}} {{if and $.UpdateAllowed (not $.UpdateByRebaseAllowed)}} - <form action="{{$.Link}}/update" method="post" class="ui update-branch-form"> + <form action="{{$.Issue.Link}}/update" method="post" class="ui update-branch-form"> {{$.CsrfTokenHtml}} <button class="ui compact button"> <span class="ui text">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</span> diff --git a/tests/integration/pull_status_test.go b/tests/integration/pull_status_test.go index 63ffe94320..6d42d9f62f 100644 --- a/tests/integration/pull_status_test.go +++ b/tests/integration/pull_status_test.go @@ -13,9 +13,13 @@ import ( auth_model "code.gitea.io/gitea/models/auth" git_model "code.gitea.io/gitea/models/git" + "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/services/pull" "github.com/stretchr/testify/assert" ) @@ -165,3 +169,75 @@ func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) { assert.Contains(t, text, "This branch is already included in the target branch. There is nothing to merge.") }) } + +func TestPullStatusDelayCheck(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + defer test.MockVariableValue(&setting.IsProd)() + defer test.MockVariableValue(&setting.Repository.PullRequest.DelayCheckForInactiveDays, 1)() + defer test.MockVariableValue(&pull.AddPullRequestToCheckQueue)() + + session := loginUser(t, "user2") + + run := func(t *testing.T, fn func(*testing.T)) (issue3 *issues.Issue, checkedPrID int64) { + pull.AddPullRequestToCheckQueue = func(prID int64) { + checkedPrID = prID + } + fn(t) + issue3 = unittest.AssertExistsAndLoadBean(t, &issues.Issue{RepoID: 1, Index: 3}) + _ = issue3.LoadPullRequest(t.Context()) + return issue3, checkedPrID + } + + assertReloadingInterval := func(t *testing.T, interval string) { + req := NewRequest(t, "GET", "/user2/repo1/pulls/3") + resp := session.MakeRequest(t, req, http.StatusOK) + attr := "data-pull-merge-box-reloading-interval" + if interval == "" { + assert.NotContains(t, resp.Body.String(), attr) + } else { + assert.Contains(t, resp.Body.String(), fmt.Sprintf(`%s="%v"`, attr, interval)) + } + } + + // PR issue3 is merageable at the beginning + issue3, checkedPrID := run(t, func(t *testing.T) {}) + assert.Equal(t, issues.PullRequestStatusMergeable, issue3.PullRequest.Status) + assert.Zero(t, checkedPrID) + setting.IsProd = true + assertReloadingInterval(t, "") // the PR is mergeable, so no need to reload the merge box + setting.IsProd = false + assertReloadingInterval(t, "1") // make sure dev mode always do merge box reloading, to make sure the UI logic won't break + setting.IsProd = true + + // when base branch changes, PR status should be updated, but it is inactive for long time, so no real check + issue3, checkedPrID = run(t, func(t *testing.T) { + testEditFile(t, session, "user2", "repo1", "master", "README.md", "new content 1") + }) + assert.Equal(t, issues.PullRequestStatusChecking, issue3.PullRequest.Status) + assert.Zero(t, checkedPrID) + assertReloadingInterval(t, "2000") // the PR status is "checking", so try to reload the merge box + + // view a PR with status=checking, it starts the real check + issue3, checkedPrID = run(t, func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/pulls/3") + session.MakeRequest(t, req, http.StatusOK) + }) + assert.Equal(t, issues.PullRequestStatusChecking, issue3.PullRequest.Status) + assert.Equal(t, issue3.PullRequest.ID, checkedPrID) + + // when base branch changes, still so no real check + issue3, checkedPrID = run(t, func(t *testing.T) { + testEditFile(t, session, "user2", "repo1", "master", "README.md", "new content 2") + }) + assert.Equal(t, issues.PullRequestStatusChecking, issue3.PullRequest.Status) + assert.Zero(t, checkedPrID) + + // then allow to check PRs without delay, when base branch changes, the PRs will be checked + setting.Repository.PullRequest.DelayCheckForInactiveDays = -1 + issue3, checkedPrID = run(t, func(t *testing.T) { + testEditFile(t, session, "user2", "repo1", "master", "README.md", "new content 3") + }) + assert.Equal(t, issues.PullRequestStatusChecking, issue3.PullRequest.Status) + assert.Equal(t, issue3.PullRequest.ID, checkedPrID) + }) +} diff --git a/web_src/css/base.css b/web_src/css/base.css index 204b6a1560..bf7639859d 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -29,6 +29,7 @@ --checkbox-size: 15px; /* height and width of checkbox and radio inputs */ --page-spacing: 16px; /* space between page elements */ --page-margin-x: 32px; /* minimum space on left and right side of page */ + --page-space-bottom: 64px; /* space between last page element and footer */ } @media (min-width: 768px) and (max-width: 1200px) { @@ -479,7 +480,7 @@ img.ui.avatar, .full.height { flex-grow: 1; - padding-bottom: 80px; + padding-bottom: var(--page-space-bottom); } .status-page-error { diff --git a/web_src/css/modules/animations.css b/web_src/css/modules/animations.css index 173ca73314..8edf31ddbd 100644 --- a/web_src/css/modules/animations.css +++ b/web_src/css/modules/animations.css @@ -117,6 +117,8 @@ code.language-math.is-loading::after { animation-timing-function: ease-in-out; } +/* FIXME: `octicon-sync` is counterclockwise, so this animation is also counterclockwise, it looks somewhat strange. +Ideally in the future we should use a better image for clockwise animation. */ .circular-spin { animation: circular-spin-keyframes 1s linear infinite; } diff --git a/web_src/css/repo.css b/web_src/css/repo.css index b9a63a060d..0044b6b04d 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -476,14 +476,6 @@ td .commit-summary { margin-right: 5px; } -.repository.view.issue .merge.box .branch-update.grid .row { - padding-bottom: 1rem; -} - -.repository.view.issue .merge.box .branch-update.grid .row .icon { - margin-top: 1.1rem; -} - .repository.view.issue .comment-list:not(.prevent-before-timeline)::before { display: block; content: ""; diff --git a/web_src/js/features/repo-issue-pr-form.ts b/web_src/js/features/repo-issue-pr-form.ts deleted file mode 100644 index 94a2857340..0000000000 --- a/web_src/js/features/repo-issue-pr-form.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {createApp} from 'vue'; -import PullRequestMergeForm from '../components/PullRequestMergeForm.vue'; - -export function initRepoPullRequestMergeForm() { - const el = document.querySelector('#pull-request-merge-form'); - if (!el) return; - - const view = createApp(PullRequestMergeForm); - view.mount(el); -} diff --git a/web_src/js/features/repo-issue-pr-status.ts b/web_src/js/features/repo-issue-pr-status.ts deleted file mode 100644 index 8426b389f0..0000000000 --- a/web_src/js/features/repo-issue-pr-status.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function initRepoPullRequestCommitStatus() { - for (const btn of document.querySelectorAll('.commit-status-hide-checks')) { - const panel = btn.closest('.commit-status-panel'); - const list = panel.querySelector<HTMLElement>('.commit-status-list'); - btn.addEventListener('click', () => { - list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle - btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all'); - }); - } -} diff --git a/web_src/js/features/repo-issue-pull.ts b/web_src/js/features/repo-issue-pull.ts new file mode 100644 index 0000000000..c415dad08f --- /dev/null +++ b/web_src/js/features/repo-issue-pull.ts @@ -0,0 +1,133 @@ +import {createApp} from 'vue'; +import PullRequestMergeForm from '../components/PullRequestMergeForm.vue'; +import {GET, POST} from '../modules/fetch.ts'; +import {fomanticQuery} from '../modules/fomantic/base.ts'; +import {createElementFromHTML} from '../utils/dom.ts'; + +function initRepoPullRequestUpdate(el: HTMLElement) { + const prUpdateButtonContainer = el.querySelector('#update-pr-branch-with-base'); + if (!prUpdateButtonContainer) return; + + const prUpdateButton = prUpdateButtonContainer.querySelector<HTMLButtonElement>(':scope > button'); + const prUpdateDropdown = prUpdateButtonContainer.querySelector(':scope > .ui.dropdown'); + prUpdateButton.addEventListener('click', async function (e) { + e.preventDefault(); + const redirect = this.getAttribute('data-redirect'); + this.classList.add('is-loading'); + let response: Response; + try { + response = await POST(this.getAttribute('data-do')); + } catch (error) { + console.error(error); + } finally { + this.classList.remove('is-loading'); + } + let data: Record<string, any>; + try { + data = await response?.json(); // the response is probably not a JSON + } catch (error) { + console.error(error); + } + if (data?.redirect) { + window.location.href = data.redirect; + } else if (redirect) { + window.location.href = redirect; + } else { + window.location.reload(); + } + }); + + fomanticQuery(prUpdateDropdown).dropdown({ + onChange(_text: string, _value: string, $choice: any) { + const choiceEl = $choice[0]; + const url = choiceEl.getAttribute('data-do'); + if (url) { + const buttonText = prUpdateButton.querySelector('.button-text'); + if (buttonText) { + buttonText.textContent = choiceEl.textContent; + } + prUpdateButton.setAttribute('data-do', url); + } + }, + }); +} + +function initRepoPullRequestCommitStatus(el: HTMLElement) { + for (const btn of el.querySelectorAll('.commit-status-hide-checks')) { + const panel = btn.closest('.commit-status-panel'); + const list = panel.querySelector<HTMLElement>('.commit-status-list'); + btn.addEventListener('click', () => { + list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle + btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all'); + }); + } +} + +function initRepoPullRequestMergeForm(box: HTMLElement) { + const el = box.querySelector('#pull-request-merge-form'); + if (!el) return; + + const view = createApp(PullRequestMergeForm); + view.mount(el); +} + +function executeScripts(elem: HTMLElement) { + for (const oldScript of elem.querySelectorAll('script')) { + // TODO: that's the only way to load the data for the merge form. In the future + // we need to completely decouple the page data and embedded script + // eslint-disable-next-line github/no-dynamic-script-tag + const newScript = document.createElement('script'); + for (const attr of oldScript.attributes) { + if (attr.name === 'type' && attr.value === 'module') continue; + newScript.setAttribute(attr.name, attr.value); + } + newScript.text = oldScript.text; + document.body.append(newScript); + } +} + +export function initRepoPullMergeBox(el: HTMLElement) { + initRepoPullRequestCommitStatus(el); + initRepoPullRequestUpdate(el); + initRepoPullRequestMergeForm(el); + + const reloadingIntervalValue = el.getAttribute('data-pull-merge-box-reloading-interval'); + if (!reloadingIntervalValue) return; + + const reloadingInterval = parseInt(reloadingIntervalValue); + const pullLink = el.getAttribute('data-pull-link'); + let timerId: number; + + let reloadMergeBox: () => Promise<void>; + const stopReloading = () => { + if (!timerId) return; + clearTimeout(timerId); + timerId = null; + }; + const startReloading = () => { + if (timerId) return; + setTimeout(reloadMergeBox, reloadingInterval); + }; + const onVisibilityChange = () => { + if (document.hidden) { + stopReloading(); + } else { + startReloading(); + } + }; + reloadMergeBox = async () => { + const resp = await GET(`${pullLink}/merge_box`); + stopReloading(); + if (!resp.ok) { + startReloading(); + return; + } + document.removeEventListener('visibilitychange', onVisibilityChange); + const newElem = createElementFromHTML(await resp.text()); + executeScripts(newElem); + el.replaceWith(newElem); + }; + + document.addEventListener('visibilitychange', onVisibilityChange); + startReloading(); +} diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index ed79cbfe50..bc7d4dee19 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -197,54 +197,6 @@ export function initRepoIssueCodeCommentCancel() { }); } -export function initRepoPullRequestUpdate() { - const prUpdateButtonContainer = document.querySelector('#update-pr-branch-with-base'); - if (!prUpdateButtonContainer) return; - - const prUpdateButton = prUpdateButtonContainer.querySelector<HTMLButtonElement>(':scope > button'); - const prUpdateDropdown = prUpdateButtonContainer.querySelector(':scope > .ui.dropdown'); - prUpdateButton.addEventListener('click', async function (e) { - e.preventDefault(); - const redirect = this.getAttribute('data-redirect'); - this.classList.add('is-loading'); - let response: Response; - try { - response = await POST(this.getAttribute('data-do')); - } catch (error) { - console.error(error); - } finally { - this.classList.remove('is-loading'); - } - let data: Record<string, any>; - try { - data = await response?.json(); // the response is probably not a JSON - } catch (error) { - console.error(error); - } - if (data?.redirect) { - window.location.href = data.redirect; - } else if (redirect) { - window.location.href = redirect; - } else { - window.location.reload(); - } - }); - - fomanticQuery(prUpdateDropdown).dropdown({ - onChange(_text: string, _value: string, $choice: any) { - const choiceEl = $choice[0]; - const url = choiceEl.getAttribute('data-do'); - if (url) { - const buttonText = prUpdateButton.querySelector('.button-text'); - if (buttonText) { - buttonText.textContent = choiceEl.textContent; - } - prUpdateButton.setAttribute('data-do', url); - } - }, - }); -} - export function initRepoPullRequestAllowMaintainerEdit() { const wrapper = document.querySelector('#allow-edits-from-maintainers'); if (!wrapper) return; diff --git a/web_src/js/features/repo-legacy.ts b/web_src/js/features/repo-legacy.ts index 0ff6feba2d..249d181b25 100644 --- a/web_src/js/features/repo-legacy.ts +++ b/web_src/js/features/repo-legacy.ts @@ -4,7 +4,6 @@ import { initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel, initRepoIssueCommentDelete, initRepoIssueComments, initRepoIssueReferenceIssue, initRepoIssueTitleEdit, initRepoIssueWipNewTitle, initRepoIssueWipToggle, - initRepoPullRequestUpdate, } from './repo-issue.ts'; import {initUnicodeEscapeButton} from './repo-unicode-escape.ts'; import {initRepoCloneButtons} from './repo-common.ts'; @@ -12,14 +11,13 @@ import {initCitationFileCopyContent} from './citation.ts'; import {initCompLabelEdit} from './comp/LabelEdit.ts'; import {initCompReactionSelector} from './comp/ReactionSelector.ts'; import {initRepoSettings} from './repo-settings.ts'; -import {initRepoPullRequestMergeForm} from './repo-issue-pr-form.ts'; -import {initRepoPullRequestCommitStatus} from './repo-issue-pr-status.ts'; import {hideElem, queryElemChildren, queryElems, showElem} from '../utils/dom.ts'; import {initRepoIssueCommentEdit} from './repo-issue-edit.ts'; import {initRepoMilestone} from './repo-milestone.ts'; import {initRepoNew} from './repo-new.ts'; import {createApp} from 'vue'; import RepoBranchTagSelector from '../components/RepoBranchTagSelector.vue'; +import {initRepoPullMergeBox} from './repo-issue-pull.ts'; function initRepoBranchTagSelector() { registerGlobalInitFunc('initRepoBranchTagSelector', async (elRoot: HTMLInputElement) => { @@ -69,11 +67,9 @@ export function initRepository() { initRepoIssueCommentDelete(); initRepoIssueCodeCommentCancel(); - initRepoPullRequestUpdate(); initCompReactionSelector(); - initRepoPullRequestMergeForm(); - initRepoPullRequestCommitStatus(); + registerGlobalInitFunc('initRepoPullMergeBox', initRepoPullMergeBox); } initUnicodeEscapeButton(); |