From b5245bc93374aea205354f97d5e888ce3b9467ab Mon Sep 17 00:00:00 2001 From: Victor Tran Date: Fri, 26 Jul 2024 21:53:58 +1000 Subject: [PATCH] Implement branch changing --- .../translations/bg/translation.json | 3 ++ .../translations/cs-CZ/translation.json | 3 ++ .../translations/da/translation.json | 3 ++ .../translations/de/translation.json | 3 ++ .../translations/en/translation.json | 9 ++-- .../translations/es/translation.json | 3 ++ .../translations/fr-CA/translation.json | 3 ++ .../translations/he-IL/translation.json | 3 ++ .../translations/nl/translation.json | 3 ++ .../translations/pt-BR/translation.json | 3 ++ .../translations/ro-RO/translation.json | 3 ++ .../translations/uk/translation.json | 3 ++ .../translations/vi/translation.json | 3 ++ .../src/components/modals/ErrorModal.tsx | 20 +++++--- Parlance.ClientApp/src/interfaces/error.ts | 25 ++++++++++ Parlance.ClientApp/src/interfaces/projects.ts | 2 + .../src/interfaces/versionControl.ts | 2 + .../Projects/ChangeBranchModal.tsx | 48 +++++++++++++++++++ .../pages/Administration/Projects/Project.tsx | 35 ++++++++++++++ .../GitVersionControlService.cs | 48 ++++++++++++++++++- .../VersionControl/IVersionControlService.cs | 23 +++++---- Parlance/Controllers/ProjectsController.cs | 44 ++++++++++++++++- Parlance/Helpers/ControllerExtensions.cs | 1 + Parlance/Services/Projects/IProjectService.cs | 1 + Parlance/Services/Projects/ProjectService.cs | 5 ++ 25 files changed, 278 insertions(+), 21 deletions(-) create mode 100644 Parlance.ClientApp/src/interfaces/error.ts create mode 100644 Parlance.ClientApp/src/pages/Administration/Projects/ChangeBranchModal.tsx diff --git a/Parlance.ClientApp/public/resources/translations/bg/translation.json b/Parlance.ClientApp/public/resources/translations/bg/translation.json index 52960393..896a5293 100644 --- a/Parlance.ClientApp/public/resources/translations/bg/translation.json +++ b/Parlance.ClientApp/public/resources/translations/bg/translation.json @@ -133,6 +133,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "Включи 2-во фаркурно удостоверяване", "ERROR": "Грешка", "ERROR_BAD_TOKEN_REQUEST_TYPE": "Този метод за влизане не е наличен за този потребител.", + "ERROR_BRANCH_NOT_FOUND": "", "ERROR_DIRTY_WORKING_TREE": "", "ERROR_GENERIC": "", "ERROR_INVALID_BASE_FILE_TEXT": "", @@ -239,6 +240,7 @@ "PREFERRED_NAME": "", "PRINT_BACKUP_CODES": "", "PROJECTS": "Проекти", + "PROJECT_CHANGE_BRANCH": "", "PROJECT_DELETE": "Изтриване на проекта", "PROJECT_DELETE_CONFIRM_PROMPT": "", "PROJECT_DELETE_PROMPT": "", @@ -249,6 +251,7 @@ "PROJECT_MAINTAINERS_PROMPT": "", "PROJECT_MAINTAINER_REMOVE": "", "PROJECT_NAME": "Име на проекта", + "PROJECT_SOURCE_REPOSITORY": "", "PROMOTE": "", "PROMOTE_NO_USER_PROMPT": "", "PROMOTE_PROMPT_1": "", diff --git a/Parlance.ClientApp/public/resources/translations/cs-CZ/translation.json b/Parlance.ClientApp/public/resources/translations/cs-CZ/translation.json index 52154701..3df36676 100644 --- a/Parlance.ClientApp/public/resources/translations/cs-CZ/translation.json +++ b/Parlance.ClientApp/public/resources/translations/cs-CZ/translation.json @@ -131,6 +131,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "", "ERROR": "", "ERROR_BAD_TOKEN_REQUEST_TYPE": "", + "ERROR_BRANCH_NOT_FOUND": "", "ERROR_DIRTY_WORKING_TREE": "", "ERROR_GENERIC": "", "ERROR_INVALID_BASE_FILE_TEXT": "", @@ -236,6 +237,7 @@ "PREFERRED_NAME": "", "PRINT_BACKUP_CODES": "", "PROJECTS": "", + "PROJECT_CHANGE_BRANCH": "", "PROJECT_DELETE": "", "PROJECT_DELETE_CONFIRM_PROMPT": "", "PROJECT_DELETE_PROMPT": "", @@ -246,6 +248,7 @@ "PROJECT_MAINTAINERS_PROMPT": "", "PROJECT_MAINTAINER_REMOVE": "", "PROJECT_NAME": "", + "PROJECT_SOURCE_REPOSITORY": "", "PROMOTE": "", "PROMOTE_NO_USER_PROMPT": "", "PROMOTE_PROMPT_1": "", diff --git a/Parlance.ClientApp/public/resources/translations/da/translation.json b/Parlance.ClientApp/public/resources/translations/da/translation.json index 244796a9..990f2079 100644 --- a/Parlance.ClientApp/public/resources/translations/da/translation.json +++ b/Parlance.ClientApp/public/resources/translations/da/translation.json @@ -130,6 +130,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "Slå tofaktorgodkendelse til", "ERROR": "Fejl", "ERROR_BAD_TOKEN_REQUEST_TYPE": "Den login-metode er ikke tilgængelig for denne bruger.", + "ERROR_BRANCH_NOT_FOUND": "", "ERROR_DIRTY_WORKING_TREE": "Ucommittede ændringer eksisterer i repositoriet. Commit eller smid ændringerne ud først.", "ERROR_GENERIC": "Beklager, der var en fejl.", "ERROR_INVALID_BASE_FILE_TEXT": "Den specificerede hovedfil i .parlance.json-filen er ugyldig", @@ -235,6 +236,7 @@ "PREFERRED_NAME": "", "PRINT_BACKUP_CODES": "", "PROJECTS": "", + "PROJECT_CHANGE_BRANCH": "", "PROJECT_DELETE": "", "PROJECT_DELETE_CONFIRM_PROMPT": "", "PROJECT_DELETE_PROMPT": "", @@ -245,6 +247,7 @@ "PROJECT_MAINTAINERS_PROMPT": "", "PROJECT_MAINTAINER_REMOVE": "", "PROJECT_NAME": "", + "PROJECT_SOURCE_REPOSITORY": "", "PROMOTE": "", "PROMOTE_NO_USER_PROMPT": "", "PROMOTE_PROMPT_1": "", diff --git a/Parlance.ClientApp/public/resources/translations/de/translation.json b/Parlance.ClientApp/public/resources/translations/de/translation.json index 70b98316..d19295ee 100644 --- a/Parlance.ClientApp/public/resources/translations/de/translation.json +++ b/Parlance.ClientApp/public/resources/translations/de/translation.json @@ -130,6 +130,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "Aktiviere Zwei Faktor Authentifizierung", "ERROR": "Fehler", "ERROR_BAD_TOKEN_REQUEST_TYPE": "Diese Login Methode ist für den User nicht verfügbar", + "ERROR_BRANCH_NOT_FOUND": "", "ERROR_DIRTY_WORKING_TREE": "Es existieren nicht committete Änderungen im Repository. Committe sie oder verwerfe die Änderungen.", "ERROR_GENERIC": "Tut uns leid, aber es ist uns ein Fehler unterlaufen.", "ERROR_INVALID_BASE_FILE_TEXT": "Die spezifizierte Basisdatei in der .parlance.json ist ungültig", @@ -236,6 +237,7 @@ "PREFERRED_NAME": "", "PRINT_BACKUP_CODES": "Backup-Codes ausdrucken", "PROJECTS": "Projekte", + "PROJECT_CHANGE_BRANCH": "", "PROJECT_DELETE": "Projekt löschen", "PROJECT_DELETE_CONFIRM_PROMPT": "Wenn du {{project}} löschst, wird es auf Parlance nicht verfügbar sein, bis du es wieder hinzufügst.", "PROJECT_DELETE_PROMPT": "Wenn du {{project}} nicht mehr brauchst, kannst du es hier löschen.", @@ -246,6 +248,7 @@ "PROJECT_MAINTAINERS_PROMPT": "", "PROJECT_MAINTAINER_REMOVE": "", "PROJECT_NAME": "Projekt Name", + "PROJECT_SOURCE_REPOSITORY": "", "PROMOTE": "Hochstufen", "PROMOTE_NO_USER_PROMPT": "Gib bitten einen Benutzer ein, der Hochgestuft werden soll.", "PROMOTE_PROMPT_1": "Wenn {{user}} hochgestuft wird, wird er vollen Zugriff auf den Parlance Server kriegen. Er kann dich auch runterstufen.", diff --git a/Parlance.ClientApp/public/resources/translations/en/translation.json b/Parlance.ClientApp/public/resources/translations/en/translation.json index d4caa877..f43b1bb5 100644 --- a/Parlance.ClientApp/public/resources/translations/en/translation.json +++ b/Parlance.ClientApp/public/resources/translations/en/translation.json @@ -139,6 +139,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "Enable Two Factor Authentication", "ERROR": "Error", "ERROR_BAD_TOKEN_REQUEST_TYPE": "That login method is not available for this user.", + "ERROR_BRANCH_NOT_FOUND": "The branch {{branch}} was not found.", "ERROR_DIRTY_WORKING_TREE": "Uncommitted changes exist in the repository. Commit or discard the changes first.", "ERROR_GENERIC": "Sorry, there was an issue.", "ERROR_INVALID_BASE_FILE_TEXT": "The specified base file in the .parlance.json file is invalid", @@ -245,6 +246,7 @@ "PREFERRED_NAME": "Preferred Name", "PRINT_BACKUP_CODES": "Print Backup Codes", "PROJECTS": "Projects", + "PROJECT_CHANGE_BRANCH": "Change Branch", "PROJECT_DELETE": "Delete Project", "PROJECT_DELETE_CONFIRM_PROMPT": "Deleting {{project}} will make it unavailable within Parlance until you add it again.", "PROJECT_DELETE_PROMPT": "If you don't need {{project}} any more, you can delete it here.", @@ -255,6 +257,7 @@ "PROJECT_MAINTAINERS_PROMPT": "Project Maintainers have permissions to manage the project. They will be able to control the Git repository and will be notified when a source string is flagged for review.", "PROJECT_MAINTAINER_REMOVE": "Remove as Project Maintainer", "PROJECT_NAME": "Project Name", + "PROJECT_SOURCE_REPOSITORY": "Source Repository", "PROMOTE": "Promote", "PROMOTE_NO_USER_PROMPT": "Please enter a user to promote.", "PROMOTE_PROMPT_1": "Promoting {{user}} to a superuser will allow them to have full control of the Parlance server, including being able to remove you as a superuser.", @@ -268,9 +271,6 @@ "RELOAD": "Reload", "RESET_PASSWORD": "Reset Password", "RESET_PASSWORD_PROMPT": "You need to reset your password.", - "SIGNALR_CONNECTED": "Connected", - "SIGNALR_CONNECTING": "Connecting", - "SIGNALR_DISCONNECTED": "Disconnected", "SEARCH": "Search", "SEARCH_GLOSSARY": "Search Glossary", "SECURITY_KEY_ADD": "Register New Passkey", @@ -322,6 +322,9 @@ "SERVER_SSH_KEY_INVALID_PROMPT": "In order to access repositories over SSH, generate an SSH key and provide the repository host with the generated key.", "SERVER_SSH_KEY_VALID_PROMPT": "Parlance will present servers with this SSH key when accessing repositories over SSH. In order to ensure Parlance has access to remote repositories, you will need to provide the repository host with this key.", "SHOW_PLACEHOLDERS": "Show Placeholders", + "SIGNALR_CONNECTED": "Connected", + "SIGNALR_CONNECTING": "Connecting", + "SIGNALR_DISCONNECTED": "Disconnected", "SSH": "SSH", "SSH_KEYS": "SSH Keys", "START_NEW_TRANSLATION": "Start New Translation", diff --git a/Parlance.ClientApp/public/resources/translations/es/translation.json b/Parlance.ClientApp/public/resources/translations/es/translation.json index 79b8ebe0..97346e4e 100644 --- a/Parlance.ClientApp/public/resources/translations/es/translation.json +++ b/Parlance.ClientApp/public/resources/translations/es/translation.json @@ -130,6 +130,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "", "ERROR": "", "ERROR_BAD_TOKEN_REQUEST_TYPE": "", + "ERROR_BRANCH_NOT_FOUND": "", "ERROR_DIRTY_WORKING_TREE": "", "ERROR_GENERIC": "", "ERROR_INVALID_BASE_FILE_TEXT": "", @@ -235,6 +236,7 @@ "PREFERRED_NAME": "", "PRINT_BACKUP_CODES": "", "PROJECTS": "", + "PROJECT_CHANGE_BRANCH": "", "PROJECT_DELETE": "", "PROJECT_DELETE_CONFIRM_PROMPT": "", "PROJECT_DELETE_PROMPT": "", @@ -245,6 +247,7 @@ "PROJECT_MAINTAINERS_PROMPT": "", "PROJECT_MAINTAINER_REMOVE": "", "PROJECT_NAME": "", + "PROJECT_SOURCE_REPOSITORY": "", "PROMOTE": "", "PROMOTE_NO_USER_PROMPT": "", "PROMOTE_PROMPT_1": "", diff --git a/Parlance.ClientApp/public/resources/translations/fr-CA/translation.json b/Parlance.ClientApp/public/resources/translations/fr-CA/translation.json index a56b937e..6fd5a51a 100644 --- a/Parlance.ClientApp/public/resources/translations/fr-CA/translation.json +++ b/Parlance.ClientApp/public/resources/translations/fr-CA/translation.json @@ -131,6 +131,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "", "ERROR": "Erreur", "ERROR_BAD_TOKEN_REQUEST_TYPE": "", + "ERROR_BRANCH_NOT_FOUND": "", "ERROR_DIRTY_WORKING_TREE": "", "ERROR_GENERIC": "", "ERROR_INVALID_BASE_FILE_TEXT": "", @@ -237,6 +238,7 @@ "PREFERRED_NAME": "", "PRINT_BACKUP_CODES": "", "PROJECTS": "", + "PROJECT_CHANGE_BRANCH": "", "PROJECT_DELETE": "", "PROJECT_DELETE_CONFIRM_PROMPT": "", "PROJECT_DELETE_PROMPT": "", @@ -247,6 +249,7 @@ "PROJECT_MAINTAINERS_PROMPT": "", "PROJECT_MAINTAINER_REMOVE": "", "PROJECT_NAME": "", + "PROJECT_SOURCE_REPOSITORY": "", "PROMOTE": "", "PROMOTE_NO_USER_PROMPT": "", "PROMOTE_PROMPT_1": "", diff --git a/Parlance.ClientApp/public/resources/translations/he-IL/translation.json b/Parlance.ClientApp/public/resources/translations/he-IL/translation.json index 3d21416e..809f0763 100644 --- a/Parlance.ClientApp/public/resources/translations/he-IL/translation.json +++ b/Parlance.ClientApp/public/resources/translations/he-IL/translation.json @@ -135,6 +135,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "הפעל אימות דו-גורמי", "ERROR": "שגיאה", "ERROR_BAD_TOKEN_REQUEST_TYPE": "שיטת התחברות זו אינה זמינה עבור משתמש זה.", + "ERROR_BRANCH_NOT_FOUND": "", "ERROR_DIRTY_WORKING_TREE": "ישנם קיימים שינויים שלא בוצעו כקומיטים במאגר. בצע קומיט או מחוק את שינוייך קודם.", "ERROR_GENERIC": "מצטערים, קרתה בעיה.", "ERROR_INVALID_BASE_FILE_TEXT": "קובץ הבסיס שצוין ב-.parlance.json אינו חוקי", @@ -241,6 +242,7 @@ "PREFERRED_NAME": "שם מועדף", "PRINT_BACKUP_CODES": "הדפס קודי גיבוי", "PROJECTS": "פרוייקטים", + "PROJECT_CHANGE_BRANCH": "", "PROJECT_DELETE": "מחק פרוייקט", "PROJECT_DELETE_CONFIRM_PROMPT": "הסרת {{project}} יעשה אותו לא זמין ב-Parlance עד שתוסיף אותו שוב.", "PROJECT_DELETE_PROMPT": "אם אינך זקוק ל-{{project}} יותר, אתה יכול להסיר אותו פה.", @@ -251,6 +253,7 @@ "PROJECT_MAINTAINERS_PROMPT": "למנהלי פרויקטים יש רשות לנהל את הפרויקט. הם יכולים לשלוט על מאגר ה-Git והם יקבלו הודעה כאשר מחרוזת מקור מסומנת לבדיקה.", "PROJECT_MAINTAINER_REMOVE": "הסר כמנהל פרויקט", "PROJECT_NAME": "שם פרוייקט", + "PROJECT_SOURCE_REPOSITORY": "", "PROMOTE": "קדם", "PROMOTE_NO_USER_PROMPT": "הזן את שם המשתמש של השמתמש כדי לקדם אותו.", "PROMOTE_PROMPT_1": "קידום {{user}} למשתמש-על יאפשר להם לקבל שליטה מלאה על שרת ה-Parlance, כולל האפשרות להסיר אותך כמשתמש-על.", diff --git a/Parlance.ClientApp/public/resources/translations/nl/translation.json b/Parlance.ClientApp/public/resources/translations/nl/translation.json index 0b7735e0..13baa706 100644 --- a/Parlance.ClientApp/public/resources/translations/nl/translation.json +++ b/Parlance.ClientApp/public/resources/translations/nl/translation.json @@ -131,6 +131,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "Tweefactorauthenticatie aanzetten", "ERROR": "Fout", "ERROR_BAD_TOKEN_REQUEST_TYPE": "DIe loginmethode is niet beschikbaar voor deze gebruiker.", + "ERROR_BRANCH_NOT_FOUND": "", "ERROR_DIRTY_WORKING_TREE": "Er zijn nog niet gecommitte wijzigingen in deze repository. Commit de wijzigingen of gooi ze weg.", "ERROR_GENERIC": "Sorry, er ging iets mis.", "ERROR_INVALID_BASE_FILE_TEXT": "Het gespecificeerde basisbestand in het .parlance.json-bestand is ongeldig", @@ -237,6 +238,7 @@ "PREFERRED_NAME": "Voorkeursnaam", "PRINT_BACKUP_CODES": "Backup-codes Afdrukken", "PROJECTS": "Projecten", + "PROJECT_CHANGE_BRANCH": "", "PROJECT_DELETE": "Project Verwijderen", "PROJECT_DELETE_CONFIRM_PROMPT": "Door {{project}} te verwijderen wordt het onbeschikbaar in Parlance totdat je het weer toevoegt.", "PROJECT_DELETE_PROMPT": "Als je {{project}} niet meer nodig hebt, kun je het hier verwijderen.", @@ -247,6 +249,7 @@ "PROJECT_MAINTAINERS_PROMPT": "Project Maintainers hebben rechten om het project te beheren. Ze hebben controle over de Git-repository en krijgen bericht als er een bronstring wordt gemarkeerd voor een review.", "PROJECT_MAINTAINER_REMOVE": "Verwijderen als Project Maintainer", "PROJECT_NAME": "Projectnaam", + "PROJECT_SOURCE_REPOSITORY": "", "PROMOTE": "Promoveren", "PROMOTE_NO_USER_PROMPT": "Vul een gebruiker in om te promoveren.", "PROMOTE_PROMPT_1": "Door {{user}} te promoveren naar een supergebruiker krijgt diegene volledige controle over de Parlance-server, inclusief de mogelijkheid om jou te degraderen als supergebruiker.", diff --git a/Parlance.ClientApp/public/resources/translations/pt-BR/translation.json b/Parlance.ClientApp/public/resources/translations/pt-BR/translation.json index 9070f70e..770003ac 100644 --- a/Parlance.ClientApp/public/resources/translations/pt-BR/translation.json +++ b/Parlance.ClientApp/public/resources/translations/pt-BR/translation.json @@ -130,6 +130,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "Habilitar autenticação em dois fatores", "ERROR": "Erro", "ERROR_BAD_TOKEN_REQUEST_TYPE": "Esse método de log-in não está disponível para esse usuário.", + "ERROR_BRANCH_NOT_FOUND": "", "ERROR_DIRTY_WORKING_TREE": "Mudanças que não sofreram commit existem no repositório. Faça commit ou descarte as mudanças primeiro.", "ERROR_GENERIC": "Desculpa, houve um problema.", "ERROR_INVALID_BASE_FILE_TEXT": "O arquivo de base especificado no arquivo .parlance.json é invalido", @@ -236,6 +237,7 @@ "PREFERRED_NAME": "Nome preferencial", "PRINT_BACKUP_CODES": "Imprimir códigos de reserva", "PROJECTS": "Projetos", + "PROJECT_CHANGE_BRANCH": "", "PROJECT_DELETE": "Deletar projeto", "PROJECT_DELETE_CONFIRM_PROMPT": "Deletar {{project}} o tornará indispónivel em Parlance até que você o adicione novamente.", "PROJECT_DELETE_PROMPT": "Se você não precisa mais de {{project}}, você pode deletá-lo aqui.", @@ -246,6 +248,7 @@ "PROJECT_MAINTAINERS_PROMPT": "", "PROJECT_MAINTAINER_REMOVE": "", "PROJECT_NAME": "Nome do projeto", + "PROJECT_SOURCE_REPOSITORY": "", "PROMOTE": "Promover", "PROMOTE_NO_USER_PROMPT": "Escolha um usuário para promover.", "PROMOTE_PROMPT_1": "Promover {{user}} para um superuser permitirá que tenham controle total do servidor do Parlance, inclusive remover o seu superuser.", diff --git a/Parlance.ClientApp/public/resources/translations/ro-RO/translation.json b/Parlance.ClientApp/public/resources/translations/ro-RO/translation.json index 13fc7ae7..c0fb66d1 100644 --- a/Parlance.ClientApp/public/resources/translations/ro-RO/translation.json +++ b/Parlance.ClientApp/public/resources/translations/ro-RO/translation.json @@ -132,6 +132,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "Activează autentificarea prin doi pași", "ERROR": "Eroare", "ERROR_BAD_TOKEN_REQUEST_TYPE": "Metoda asta de autentificare nu-i disponibilă pentru utilizatorul acesta.", + "ERROR_BRANCH_NOT_FOUND": "", "ERROR_DIRTY_WORKING_TREE": "Schimbări necomise există în repozitoriu. Comite sau revocă schimbările mai întâi.", "ERROR_GENERIC": "Scuze, a apărut o problemă.", "ERROR_INVALID_BASE_FILE_TEXT": "Fișierul de bază specificat în .parlance.json este invalid", @@ -238,6 +239,7 @@ "PREFERRED_NAME": "Nume preferat", "PRINT_BACKUP_CODES": "Printează codurile de backup", "PROJECTS": "Proiecte", + "PROJECT_CHANGE_BRANCH": "", "PROJECT_DELETE": "Șterge proiectul", "PROJECT_DELETE_CONFIRM_PROMPT": "Ștergerea proiectului {{project}} îl va face indisponibil din Parlance până la readăugarea acestuia.", "PROJECT_DELETE_PROMPT": "Dacă nu mai ai nevoie de proiectul {{project}}, îl poți șterge de-aici.", @@ -248,6 +250,7 @@ "PROJECT_MAINTAINERS_PROMPT": "Întreținătorilor de proiect le este permis să gestioneze proiectul. Ei pot controla repozitoriul Git și vor fi notificați când un șir de sursă este marcat pentru revizuire.", "PROJECT_MAINTAINER_REMOVE": "Elimină întreținătorul de proiect", "PROJECT_NAME": "Nume proiect", + "PROJECT_SOURCE_REPOSITORY": "", "PROMOTE": "Promovare", "PROMOTE_NO_USER_PROMPT": "Introdu un nume de utilizator pentru a-l promova.", "PROMOTE_PROMPT_1": "Promovarea utilizatorului {{user}} la gradul de superutilizator îi va oferi control deplin al serverului Parlance, inclusiv abilitatea de a te retrograda pe tine de la gradul de superutilizator.", diff --git a/Parlance.ClientApp/public/resources/translations/uk/translation.json b/Parlance.ClientApp/public/resources/translations/uk/translation.json index 8fe933f4..7d8711b6 100644 --- a/Parlance.ClientApp/public/resources/translations/uk/translation.json +++ b/Parlance.ClientApp/public/resources/translations/uk/translation.json @@ -130,6 +130,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "", "ERROR": "", "ERROR_BAD_TOKEN_REQUEST_TYPE": "", + "ERROR_BRANCH_NOT_FOUND": "", "ERROR_DIRTY_WORKING_TREE": "", "ERROR_GENERIC": "", "ERROR_INVALID_BASE_FILE_TEXT": "", @@ -236,6 +237,7 @@ "PREFERRED_NAME": "", "PRINT_BACKUP_CODES": "", "PROJECTS": "", + "PROJECT_CHANGE_BRANCH": "", "PROJECT_DELETE": "", "PROJECT_DELETE_CONFIRM_PROMPT": "", "PROJECT_DELETE_PROMPT": "", @@ -246,6 +248,7 @@ "PROJECT_MAINTAINERS_PROMPT": "", "PROJECT_MAINTAINER_REMOVE": "", "PROJECT_NAME": "", + "PROJECT_SOURCE_REPOSITORY": "", "PROMOTE": "", "PROMOTE_NO_USER_PROMPT": "", "PROMOTE_PROMPT_1": "", diff --git a/Parlance.ClientApp/public/resources/translations/vi/translation.json b/Parlance.ClientApp/public/resources/translations/vi/translation.json index d1146f80..0056d8b3 100644 --- a/Parlance.ClientApp/public/resources/translations/vi/translation.json +++ b/Parlance.ClientApp/public/resources/translations/vi/translation.json @@ -131,6 +131,7 @@ "ENABLE_TWO_FACTOR_AUTHENTICATION": "Bật Xác Minh Hai Bước", "ERROR": "Lỗi", "ERROR_BAD_TOKEN_REQUEST_TYPE": "Không thể đăng nhập theo cách đó cho người dùng này.", + "ERROR_BRANCH_NOT_FOUND": "", "ERROR_DIRTY_WORKING_TREE": "Có thay đổi chưa được vào lần chuyển giao. Vui lòng tạo lần chuyển giao hoặc bỏ các thay đổi trước.", "ERROR_GENERIC": "Rất tiếc, đã xảy ra lỗi.", "ERROR_INVALID_BASE_FILE_TEXT": "Tệp cơ sở chỉ định trong tệp .parlance.json không hợp lệ", @@ -237,6 +238,7 @@ "PREFERRED_NAME": "Tên ưu tiên", "PRINT_BACKUP_CODES": "In mã dự phòng", "PROJECTS": "Dự án", + "PROJECT_CHANGE_BRANCH": "", "PROJECT_DELETE": "Xóa dự án", "PROJECT_DELETE_CONFIRM_PROMPT": "Xóa {{project}} sẽ làm cho nó không được khả dụng trong Parlance cho đến khi bạn thêm lại.", "PROJECT_DELETE_PROMPT": "Nếu bạn không cần {{project}} nữa, bạn có thể xóa nó tại đây", @@ -247,6 +249,7 @@ "PROJECT_MAINTAINERS_PROMPT": "", "PROJECT_MAINTAINER_REMOVE": "Xóa tư cách Người bảo trì dự án", "PROJECT_NAME": "Tên dự án", + "PROJECT_SOURCE_REPOSITORY": "", "PROMOTE": "Thăng cấp", "PROMOTE_NO_USER_PROMPT": "Vui lòng điền vào một người dùng để thăng cấp.", "PROMOTE_PROMPT_1": "Việc thăng cấp {{user}} cho người dùng quản trị sẽ cho phép họ có toàn quyền kiểm soát máy chủ Parlance, bao gồm việc có thể xuống cấp bạn khỏi người dùng quản trị.", diff --git a/Parlance.ClientApp/src/components/modals/ErrorModal.tsx b/Parlance.ClientApp/src/components/modals/ErrorModal.tsx index 5a70d426..3fdf5b7a 100644 --- a/Parlance.ClientApp/src/components/modals/ErrorModal.tsx +++ b/Parlance.ClientApp/src/components/modals/ErrorModal.tsx @@ -2,6 +2,7 @@ import { useTranslation } from "react-i18next"; import Modal from "../Modal"; import { VerticalLayout } from "../Layouts"; import { ReactElement, ReactNode, useEffect, useState } from "react"; +import { ParlanceError } from "@/interfaces/error"; interface ErrorModalProps { error: any; @@ -27,15 +28,13 @@ export default function ErrorModal({ try { if (!error) return; - let json = await error.json(); - let jsonError = json.error; - - if (specialRenderings && specialRenderings[jsonError]) { - setSpecialRendering(specialRenderings[jsonError]); + let json = (await error.json()) as ParlanceError; + if (specialRenderings && specialRenderings[json.error]) { + setSpecialRendering(specialRenderings[json.error]); return; } - switch (jsonError) { + switch (json.error) { case "UnknownUser": setMessage(t("ERROR_UNKNOWN_USER")); return; @@ -69,12 +68,19 @@ export default function ErrorModal({ case "BadTokenRequestType": setMessage(t("ERROR_BAD_TOKEN_REQUEST_TYPE")); return; + case "BranchNotFound": + setMessage( + t("ERROR_BRANCH_NOT_FOUND", { + branch: json.extraData.branch, + }), + ); + return; } } catch {} })(); }, [error]); - if (specialRendering) return <>specialRendering; + if (specialRendering) return <>{specialRendering}; return ( void; +}) { + const { t } = useTranslation(); + const [branch, setBranch] = useState(initialBranch); + + return ( + { + onChangeBranch(branch); + }, + }, + ]} + > + +
+ {t("What is the name of the branch you want to checkout?")} + + + setBranch((e.target as HTMLInputElement).value) + } + /> +
+ {t( + "Changing branches will discard any changes that have not been pushed to the remote.", + )} +
+
+
+ ); +} diff --git a/Parlance.ClientApp/src/pages/Administration/Projects/Project.tsx b/Parlance.ClientApp/src/pages/Administration/Projects/Project.tsx index 97b9c1c2..14dd33ba 100644 --- a/Parlance.ClientApp/src/pages/Administration/Projects/Project.tsx +++ b/Parlance.ClientApp/src/pages/Administration/Projects/Project.tsx @@ -15,6 +15,7 @@ import { useEffect, useState } from "react"; import LineEdit from "../../../components/LineEdit"; import ModalList from "../../../components/ModalList"; import { ProjectResponse } from "@/interfaces/projects"; +import { ChangeBranchModal } from "@/pages/Administration/Projects/ChangeBranchModal"; export default function Project() { const [projectInfo, setProjectInfo] = useState( @@ -112,6 +113,28 @@ export default function Project() { ); }; + const changeBranch = () => { + Modal.mount( + { + Modal.mount(); + try { + await Fetch.post(`/api/projects/${project}/branch`, { + branch: branch, + }); + await updateProjectInfo(); + Modal.unmount(); + } catch (error) { + Modal.mount(); + } + }} + />, + ); + }; + const addMaintainer = async () => { if (addingUser === "") return; @@ -160,6 +183,18 @@ export default function Project() { + + + + {t("PROJECT_SOURCE_REPOSITORY")} + + {projectInfo.versionControlInformation?.upstreamUrl}@ + {projectInfo.versionControlInformation?.branch} + + {t("PROJECT_CHANGE_BRANCH")} + + + {t("PROJECT_DELETE")} diff --git a/Parlance.VersionControl/Services/VersionControl/GitVersionControlService.cs b/Parlance.VersionControl/Services/VersionControl/GitVersionControlService.cs index 322927b5..cb964d53 100644 --- a/Parlance.VersionControl/Services/VersionControl/GitVersionControlService.cs +++ b/Parlance.VersionControl/Services/VersionControl/GitVersionControlService.cs @@ -72,6 +72,11 @@ await projectMetadataFileChangedEventPublisher.PublishAsync(new() } } + public Task CheckoutBranch(Database.Models.Project project, string branch) + { + return Task.Run(() => CheckoutBranchCore(project, branch)); + } + public VersionControlStatus VersionControlStatus(Database.Models.Project project) { using var repo = new Repository(project.VcsDirectory); @@ -82,7 +87,9 @@ public VersionControlStatus VersionControlStatus(Database.Models.Project project LatestRemoteCommit = new VersionControlCommit(repo.Head.TrackedBranch.Tip), Ahead = repo.Head.TrackingDetails.AheadBy.GetValueOrDefault(), Behind = repo.Head.TrackingDetails.BehindBy.GetValueOrDefault(), - ChangedFiles = repo.RetrieveStatus().Select(x => x.FilePath) + ChangedFiles = repo.RetrieveStatus().Select(x => x.FilePath), + Branch = repo.Head.FriendlyName, + UpstreamUrl = repo.Network.Remotes["origin"].Url }; } @@ -292,6 +299,45 @@ private void PublishSavedChangesToSourceCore(Database.Models.Project project) }); } + private void CheckoutBranchCore(Database.Models.Project project, string branch) + { + // Fetch the remote in order to get up to date information about branches + UpdateVersionControlMetadataCore(project); + + var repo = new Repository(project.VcsDirectory); + var currentBranch = repo.Head; + if (currentBranch.CanonicalName == $"refs/heads/{branch}") + { + throw new InvalidOperationException("Unable to checkout the same branch"); + } + + var remoteBranch = repo.Branches.SingleOrDefault(b => b.IsRemote && b.CanonicalName == $"refs/remotes/origin/{branch}"); + if (remoteBranch is null) + { + throw new BranchNotFoundException() + { + Branch = branch + }; + } + + // Delete any local branches with that name + var existingLocalBranch = repo.Branches.SingleOrDefault(b => b.CanonicalName == $"refs/heads/{branch}"); + if (existingLocalBranch is not null) + { + repo.Branches.Remove(existingLocalBranch); + } + + // Create a local branch that tracks the upstream branch + var localBranch = repo.CreateBranch(branch, remoteBranch.Tip); + repo.Branches.Update(localBranch, b => b.TrackedBranch = remoteBranch.CanonicalName); + + // Checkout the new local branch + Commands.Checkout(repo, localBranch); + + // Delete the old branch + repo.Branches.Remove(currentBranch); + } + public Task DeleteUnpublishedChanges(Database.Models.Project project) { using var repo = new Repository(project.VcsDirectory); diff --git a/Parlance.VersionControl/Services/VersionControl/IVersionControlService.cs b/Parlance.VersionControl/Services/VersionControl/IVersionControlService.cs index 1f8d7393..5c546a50 100644 --- a/Parlance.VersionControl/Services/VersionControl/IVersionControlService.cs +++ b/Parlance.VersionControl/Services/VersionControl/IVersionControlService.cs @@ -3,12 +3,13 @@ namespace Parlance.VersionControl.Services.VersionControl; -public class MergeConflictException : Exception -{ -} +public class MergeConflictException : Exception; + +public class DirtyWorkingTreeException : Exception; -public class DirtyWorkingTreeException : Exception +public class BranchNotFoundException : InvalidOperationException { + public required string Branch { get; set; } } public class VersionControlCommit(Commit commit) @@ -19,11 +20,14 @@ public class VersionControlCommit(Commit commit) public class VersionControlStatus { - public VersionControlCommit LatestLocalCommit { get; init; } - public VersionControlCommit LatestRemoteCommit { get; set; } - public int Ahead { get; init; } - public int Behind { get; init; } - public IEnumerable ChangedFiles { get; init; } + public required VersionControlCommit LatestLocalCommit { get; init; } + public required VersionControlCommit LatestRemoteCommit { get; set; } + public required int Ahead { get; init; } + public required int Behind { get; init; } + public required IEnumerable ChangedFiles { get; init; } + public required string Branch { get; init; } + + public required string UpstreamUrl { get; set; } } public interface IVersionControlService @@ -37,4 +41,5 @@ public interface IVersionControlService VersionControlStatus VersionControlStatus(Database.Models.Project project); string CloneUrl(Database.Models.Project project); string CloneUrl(IParlanceProject project); + Task CheckoutBranch(Database.Models.Project project, string branch); } \ No newline at end of file diff --git a/Parlance/Controllers/ProjectsController.cs b/Parlance/Controllers/ProjectsController.cs index b2c900ae..a4869228 100644 --- a/Parlance/Controllers/ProjectsController.cs +++ b/Parlance/Controllers/ProjectsController.cs @@ -22,7 +22,9 @@ using Parlance.Services.Permissions; using Parlance.Services.ProjectMaintainers; using Parlance.Services.Projects; +using Parlance.Services.Superuser; using Parlance.VersionControl.Services.PendingEdits; +using Parlance.VersionControl.Services.VersionControl; using Parlance.Vicr123Accounts.Authentication; using Parlance.Vicr123Accounts.Services; using Tmds.DBus; @@ -42,6 +44,8 @@ public class ProjectsController( IProjectMaintainersService projectMaintainersService, IGlossaryService glossaryService, ICommentsService commentsService, + ISuperuserService superuserService, + IVersionControlService versionControlService, IAsyncPublisher translationSubmitEventPublisher, IHubContext translatorHubContext) : Controller @@ -203,6 +207,11 @@ public async Task GetSubprojects(string project) var indexResults = await indexingService.OverallResults(proj); + VersionControlStatus? versionControlInformation = null; + if (username is not null && await superuserService.IsSuperuser(username)) + { + versionControlInformation = versionControlService.VersionControlStatus(p); + } return Json(new { @@ -219,7 +228,8 @@ public async Task GetSubprojects(string project) subproject.SystemName, subproject.Name }; })), - CanManage = await permissionsService.HasManageProjectPermission(username, project) + CanManage = await permissionsService.HasManageProjectPermission(username, project), + VersionControlInformation = versionControlInformation }); } catch (ParlanceJsonFileParseException) @@ -237,6 +247,33 @@ public async Task GetSubprojects(string project) } } + [Authorize(Policy = "Superuser")] + [Route("{project}/branch")] + [HttpPost] + public async Task ChangeProjectBranch(string project, [FromBody] ChangeProjectBranchRequestData data) + { + try + { + var p = await projectService.ProjectBySystemName(project); + await projectService.ChangeBranch(p, data.Branch); + return NoContent(); + } + catch (ProjectNotFoundException) + { + return NotFound(); + } + catch (BranchNotFoundException ex) + { + return this.ClientError(ParlanceClientError.BranchNotFound, new + { + ex.Branch + }); + } + catch (InvalidOperationException ex) + { + return this.ClientError(ParlanceClientError.GitError, ex.Message); + } + } [HttpPost] [Authorize(Policy = "ProjectManager")] @@ -730,4 +767,9 @@ public class SearchGlossaryRequestData { public required string? SearchTerm { get; set; } } + + public class ChangeProjectBranchRequestData + { + public required string Branch { get; set; } + } } \ No newline at end of file diff --git a/Parlance/Helpers/ControllerExtensions.cs b/Parlance/Helpers/ControllerExtensions.cs index 837df8e9..406b9e96 100644 --- a/Parlance/Helpers/ControllerExtensions.cs +++ b/Parlance/Helpers/ControllerExtensions.cs @@ -7,6 +7,7 @@ public enum ParlanceClientError UnknownUser, PermissionAlreadyGranted, GitError, + BranchNotFound, UsernameAlreadyExists, TwoFactorIsDisabled, TwoFactorAlreadyEnabled, diff --git a/Parlance/Services/Projects/IProjectService.cs b/Parlance/Services/Projects/IProjectService.cs index 7f2bc0ce..b0c9d00c 100644 --- a/Parlance/Services/Projects/IProjectService.cs +++ b/Parlance/Services/Projects/IProjectService.cs @@ -11,4 +11,5 @@ public interface IProjectService public Task> Projects(); public Task ProjectBySystemName(string systemName); public Task RemoveProject(Database.Models.Project project); + Task ChangeBranch(Database.Models.Project project, string branch); } \ No newline at end of file diff --git a/Parlance/Services/Projects/ProjectService.cs b/Parlance/Services/Projects/ProjectService.cs index 06ea26d2..25f2bc8c 100644 --- a/Parlance/Services/Projects/ProjectService.cs +++ b/Parlance/Services/Projects/ProjectService.cs @@ -92,4 +92,9 @@ public async Task RemoveProject(Database.Models.Project project) Directory.Delete(project.VcsDirectory, true); } + + public async Task ChangeBranch(Database.Models.Project project, string branch) + { + await versionControlService.CheckoutBranch(project, branch); + } } \ No newline at end of file