; ******************************************************************** ; Program: Git Companion ; Description: Help using git in your purebasic projects with a GUI ; Author: Yann Lebrun ; Date: August, 2025 ; License: Free, unrestricted, credit ; appreciated but not required. ; ; Note: Please share improvement ! ; ******************************************************************** #EnableDebug=#True Global GitIgnoreHelpHTML.s = ~"" + #LF$ + ~"" + #LF$ + ~"" + #LF$ + ~" " + #LF$ + ~" " + #LF$ + ~" Aide .gitignore" + #LF$ + ~" " + #LF$ + ~"" + #LF$ + ~"" + #LF$ + ~"
" + #LF$ + ~"

📁 Guide .gitignore

" + #LF$ + ~" " + #LF$ + ~"

Le fichier .gitignore permet de spécifier quels fichiers et dossiers Git doit ignorer lors du versioning.

" + #LF$ + ~" " + #LF$ + ~"

🎯 Syntaxe de base

" + #LF$ + ~" " + #LF$ + ~"
" + #LF$ + ~" Ignorer des fichiers spécifiques :" + #LF$ + ~"
" + #LF$ + ~"config.txt # Ignore le fichier config.txt
" + #LF$ + ~"*.log # Ignore tous les fichiers .log
" + #LF$ + ~"temp/ # Ignore le dossier temp et tout son contenu" + #LF$ + ~"
" + #LF$ + ~"
" + #LF$ + ~" " + #LF$ + ~"

🔧 Patterns et wildcards

" + #LF$ + ~" " + #LF$ + ~" " + #LF$ + ~" " + #LF$ + ~"

🚫 Négation avec !

" + #LF$ + ~" " + #LF$ + ~"
" + #LF$ + ~"*.log
" + #LF$ + ~"!important.log # Inclut ce fichier malgré la règle précédente" + #LF$ + ~"
" + #LF$ + ~" " + #LF$ + ~"

📍 Chemins

" + #LF$ + ~" " + #LF$ + ~"
" + #LF$ + ~"
" + #LF$ + ~"/build # Ignore build à la racine uniquement
" + #LF$ + ~"build/ # Ignore tous les dossiers build
" + #LF$ + ~"docs/**/*.pdf # Ignore tous les PDF dans docs et ses sous-dossiers" + #LF$ + ~"
" + #LF$ + ~"
" + #LF$ + ~" " + #LF$ + ~"

💡 Exemples courants

" + #LF$ + ~" " + #LF$ + ~"
" + #LF$ + ~"# Fichiers de build
" + #LF$ + ~"dist/
" + #LF$ + ~"build/
" + #LF$ + ~"
" + #LF$ + ~"# Dépendances
" + #LF$ + ~"node_modules/
" + #LF$ + ~"vendor/
" + #LF$ + ~"
" + #LF$ + ~"# Configuration locale
" + #LF$ + ~".env
" + #LF$ + ~"config.local.json
" + #LF$ + ~"
" + #LF$ + ~"# Logs et caches
" + #LF$ + ~"*.log
" + #LF$ + ~".cache/
" + #LF$ + ~"
" + #LF$ + ~"# Fichiers d'IDE
" + #LF$ + ~".vscode/
" + #LF$ + ~".idea/
" + #LF$ + ~"
" + #LF$ + ~"# Fichiers système
" + #LF$ + ~".DS_Store
" + #LF$ + ~"Thumbs.db" + #LF$ + ~"
" + #LF$ + ~" " + #LF$ + ~"
" + #LF$ + ~" 💡 Conseil : Utilisez des commentaires avec # pour documenter vos règles." + #LF$ + ~"
" + #LF$ + ~" " + #LF$ + ~"
" + #LF$ + ~" ⚠️ Important : Le .gitignore ne s'applique qu'aux fichiers non-trackés. Pour ignorer des fichiers déjà versionnés, utilisez git rm --cached <fichier>." + #LF$ + ~"
" + #LF$ + ~" " + #LF$ + ~"

🔍 Tester vos règles

" + #LF$ + ~" " + #LF$ + ~"
" + #LF$ + ~"git check-ignore -v <fichier> # Vérifie si un fichier est ignoré
" + #LF$ + ~"git status --ignored # Affiche les fichiers ignorés" + #LF$ + ~"
" + #LF$ + ~" " + #LF$ + ~"
" + #LF$ + ~"" + #LF$ + ~"" ; ============================================================================= ;-Enumeration Gadget ; ============================================================================= Enumeration ; --- existants (inchangés) --- #gdtPnl #WinMain #GdtLblRepo #GgtFieldRepo #GdtBtnBrowseRepo #GdtBtnInit #GdtBtnRefresh #GdtListStatus ; Dépôt (cadres) #GdtFrmLocal #GdtFrmRemote #GdtFrmFiles ; Distants au-dessus de la liste #GdtLblRemote #GdtFieldRemote #GdtLblBranch #GdtSlctBranch #GdtBtnNewBranch #GdtBtnClone #GdtBtnPull #GdtBtnPush ; Liste + actions locales directement en dessous #GdtBtnRestore #GdtBtnRename #GdtBtnDelete #GdtBtnIgnore ; Commit (1 ligne large) #GdtLblMessage #GdtFieldMessage #GdtBtnCommit ; Aide sous le panel #GdtFrmHelp #GdtHelp ;Onglet History #GdtListHistory #GdtTxtCommitInfo #GdtBtnRestoreCommit ; Onglet .gitIgnore #GdtTxtGitIgnore #GdtBtnSaveGitIgnore ; Onglet Config #GdtFrmConfig #GdtLblUserName #GdtFieldUserName #GdtLblUserEmail #GdtFieldUserEmail #GdtLblScope #GdtSlctScope #GdtBtnSaveCfg EndEnumeration ; ============================================================================= ;-Module Translate ; ============================================================================= DeclareModule Translate Declare.s T(key.s, DefaultLng.s = "") Declare LoadLanguage(filename.s) Declare SaveLanguage(filename.s) EndDeclareModule Module Translate ; =============================================== ; Système Multilingue Minimaliste pour PureBasic ; =============================================== ; Map global pour stocker les traductions Global NewMap Translations.s() ; =============================================== ; Fonction principale de traduction ; =============================================== Procedure.s T(key.s, DefaultLng.s = "") ; Retourner la traduction si elle existe If FindMapElement(Translations(), key) ProcedureReturn Translations(key) Else If DefaultLng <> "" Translations(key) = DefaultLng Else Translations(key) = "!!!"+Key EndIf EndIf ProcedureReturn Translations(key) EndProcedure ; =============================================== ; Chargement d'un fichier de langue ; Format: clé=valeur (une par ligne) ; =============================================== Procedure LoadLanguage(filename.s) Protected file, line$, pos, key$, value$ file = ReadFile(#PB_Any, filename) If file While Eof(file) = 0 line$ = Trim(ReadString(file)) ; Ignorer les lignes vides et les commentaires If line$ <> "" And Left(line$, 1) <> ";" And Left(line$, 1) <> "#" pos = FindString(line$, "=", 1) If pos > 0 key$ = Trim(Left(line$, pos - 1)) value$ = Trim(Right(line$, Len(line$) - pos)) ; Gérer les caractères d'échappement simples value$ = ReplaceString(value$, "\n", Chr(10)) value$ = ReplaceString(value$, "\t", Chr(9)) Translations(key$) = value$ EndIf EndIf Wend CloseFile(file) ProcedureReturn #True Else ProcedureReturn #False EndIf EndProcedure ; =============================================== ; Sauvegarde des traductions actuelles ; =============================================== Procedure SaveLanguage(filename.s) Protected file, key$ file = CreateFile(#PB_Any, filename) If file WriteStringN(file, "; Fichier de traduction généré automatiquement") WriteStringN(file, "; Format: clé=valeur") WriteStringN(file, "") ; Parcourir toutes les traductions ForEach Translations() key$ = MapKey(Translations()) WriteStringN(file, key$ + "=" + Translations()) Next CloseFile(file) ProcedureReturn #True Else ProcedureReturn #False EndIf EndProcedure EndModule ; ============================================================================= ;-STRUCTURES / STRUCTURES ; ============================================================================= Structure RunProgramCall exec.s ; program to execute / Programme à executer args.s ; Command arguments / Arguments de la commande workdir.s ; Working directory / Répertoire de travail output.s ; Standard output / Sortie standard errors.s ; Error output / Sortie d'erreur exitcode.i ; Exit code / Code de sortie EndStructure Structure GitBranchInfo oid.s ; SHA du commit actuel head.s ; Nom de la branche ou "(detached)" upstream.s ; Branche upstream ahead.l ; Commits en avance behind.l ; Commits en retard EndStructure Structure GitFileEntry type.c ; Type: '1'=normal, '2'=rename/copy, 'u'=unmerged, '?'=untracked, '!'=ignored statusIndex.c ; Statut index (X): '.', 'M', 'A', 'D', 'R', 'C', 'T', 'U' statusWorktree.c; Statut worktree (Y): '.', 'M', 'A', 'D', 'R', 'C', 'T', 'U', '?', '!' submodule.c ; Info submodule: 'N'=normal, 'S'=submodule, etc. modeHead.s ; Mode fichier HEAD (ex: "100644") modeIndex.s ; Mode fichier index modeWorktree.s ; Mode fichier worktree hashHead.s ; SHA HEAD hashIndex.s ; SHA index hashStage1.s ; SHA stage 1 (pour conflits) hashStage2.s ; SHA stage 2 (pour conflits) hashStage3.s ; SHA stage 3 (pour conflits) similarity.s ; Score similarité pour R/C (ex: "R100") path.s ; Chemin fichier originalPath.s ; Chemin original (pour renommages) EndStructure Structure listFilesGit name.s status.s statusDescription.s indexStatus.s workingTreeStatus.s importance.i EndStructure Structure GitStatus branchInfo.GitBranchInfo List files.GitFileEntry() EndStructure Structure GitCommitRow fullHash.s shortHash.s date.s authorName.s authorEmail.s refs.s subject.s filesSummary.s EndStructure Structure info isGit.b isInit.b EndStructure Structure main info.info GitCall.RunProgramCall GitStatus.GitStatus currentPath.s CurrentBranch.s List GitHistory.GitCommitRow() List listFilesGit.listFilesGit() EndStructure ;-Global Global main.main main\GitCall\exec="git" ;If FileSize(main\GitCall\exec)=-1 ; MessageRequester("ERROR Git","NO FIND GIT") ; End ;EndIf main\CurrentPath=GetCurrentDirectory() main\CurrentBranch="main" UseModule Translate ; ============================================================================= ;-FUNCTION DECLARATIONS / DÉCLARATIONS DE FONCTIONS ; ============================================================================= Declare RefreshFileList(null.i) Declare.i StatusImportance(status.s) Procedure.i RunExe(*call.RunProgramCall) Protected prg.i, line$, lineError$, out$, err$ If FileSize(*call\workdir)<>-2 ; Correct bad git detect if bad workdir *call\workdir=GetCurrentDirectory() EndIf Debug "[RunExe] "+*call\exec+" "+*call\args prg = RunProgram(*call\exec, *call\args, *call\workdir, #PB_Program_Open | #PB_Program_Read | #PB_Program_Error | #PB_Program_Hide) If prg = 0 *call\output = "" *call\errors = "Impossible de lancer '" + *call\exec+"'." *call\exitcode = -1 ProcedureReturn *call\exitcode EndIf ; Read output while program is running / Lire la sortie pendant l'exécution While ProgramRunning(prg) While AvailableProgramOutput(prg) line$ = ReadProgramString(prg) If line$ <> "" : out$ + line$ + #LF$ : EndIf lineError$ = ReadProgramError(prg) If lineError$ <> "" : err$ + lineError$ + #LF$ : EndIf Wend Delay(5) Wend ; Read remaining output / Lire le reste de la sortie While AvailableProgramOutput(prg) line$ = ReadProgramString(prg) If line$ <> "" : out$ + line$ + #LF$ : EndIf Wend ; Read remaining errors / Lire le reste des erreurs Repeat line$ = ReadProgramError(prg) If line$ = "" : Break : EndIf err$ + line$ + #LF$ ForEver *call\output = out$ *call\errors = err$ *call\exitcode = ProgramExitCode(prg) CloseProgram(prg) If #EnableDebug Debug "[Exec] code=" + Str(*call\exitcode) If *call\output <> "" : Debug "[std]" + *call\output : EndIf If *call\errors <> "" : Debug "[stderr] " + *call\errors : EndIf EndIf ProcedureReturn *call\exitcode EndProcedure ; ---- Helpers --------------------------------------------------------------- ; --- Importance par statut Gitpour le trie de Procedure.i StatusImportance(status.s) Protected x.s = Left(status, 1) Protected y.s = Mid(status, 2, 1) Protected score.i = 0 ; Conflits d'abord Select status Case "DD","AU","UD","UA","DU","AA","UU" ProcedureReturn 1000 EndSelect If x = "U" Or y = "U" : ProcedureReturn 1000 : EndIf ; Cas simples If status = "??" : ProcedureReturn 300 : EndIf ; Non suivis If status = "!!" : ProcedureReturn 50 : EndIf ; Ignorés If status = " " : ProcedureReturn 0 : EndIf ; Propres ; Index > Worktree If x <> " " : score + 700 : EndIf If y <> " " And y <> "?" And y <> "!" : score + 600 : EndIf ; Raffinement (X puis Y) Select x Case "D" : score + 80 Case "R" : score + 70 Case "A" : score + 60 Case "M" : score + 50 Case "C" : score + 40 Case "T" : score + 30 EndSelect Select y Case "D" : score + 40 Case "R" : score + 35 Case "A" : score + 30 Case "M" : score + 25 Case "C" : score + 20 Case "T" : score + 15 EndSelect ProcedureReturn score EndProcedure Procedure.s _SupTrim(text.s) If text = "" : ProcedureReturn "" : EndIf Protected i.i = 1 Protected j.i = Len(text) Protected c.l ; Trim gauche : avancer tant que <= 32 While i <= j c = Asc(Mid(text, i, 1)) If c <= 32 i + 1 Else Break EndIf Wend ; Trim droite : reculer tant que <= 32 While j >= i c = Asc(Mid(text, j, 1)) If c <= 32 j - 1 Else Break EndIf Wend If j < i ProcedureReturn "" Else ProcedureReturn Mid(text, i, j - i + 1) EndIf EndProcedure Procedure.s _JoinPath(base$, sub$) Protected out$ = Trim(base$) If out$ = "" : ProcedureReturn sub$ : EndIf If Right(out$, 1) <> "\" And Right(out$, 1) <> "/" : out$ + "/" : EndIf ProcedureReturn out$ + sub$ EndProcedure Procedure.b _RepoIsInitialized(workdir$) ProcedureReturn Bool(FileSize(_JoinPath(workdir$, ".git")) = -2) EndProcedure Procedure.i _CountCheckedItems(listID) Protected i, n = CountGadgetItems(listID), c For i = 0 To n - 1 If GetGadgetItemState(listID, i) & #PB_ListIcon_Checked c + 1 EndIf Next ProcedureReturn c EndProcedure Procedure.i _CountSelectedItems(listID) Protected i, n = CountGadgetItems(listID), c For i = 0 To n - 1 If GetGadgetItemState(listID, i) & #PB_ListIcon_Selected c + 1 EndIf Next ProcedureReturn c EndProcedure Procedure.b _HasRemote() ProcedureReturn Bool(Trim(GetGadgetText(#GdtFieldRemote)) <> "") EndProcedure Procedure Max(nb1, nb2) If nb1 > nb2 Result = nb1 Else Result = nb2 EndIf ProcedureReturn Result EndProcedure ; ------------------------------------------------------------------ ; Récupération récursive de TOUS les fichiers du workdir ; Remplit main\listFilesGit() avec un statut " " (clean/unmodified) ; ------------------------------------------------------------------ ; --- Helper interne : scanne un dossier et alimente la liste Procedure _ScanAndFillAllFiles(dir$, root$) Protected did.i = ExamineDirectory(#PB_Any, dir$, "*") If did = 0 : ProcedureReturn : EndIf While NextDirectoryEntry(did) Protected name$ = DirectoryEntryName(did) If name$ = "." Or name$ = ".." : Continue : EndIf Protected full$ = dir$ + name$ Debug full$ If DirectoryEntryType(did) = #PB_DirectoryEntry_Directory ; Ignorer le dépôt interne If LCase(name$) <> ".git" If Right(full$, 1) <> #PS$ : full$ + #PS$ : EndIf _ScanAndFillAllFiles(full$, root$) EndIf Else ; Fichier : on ajoute une ligne dans main\listFilesGit() Protected rel$ = Mid(full$, Len(root$) + 1) rel$ = ReplaceString(rel$, "\", "/") ; chemins normalisés AddElement(main\listFilesGit()) main\listFilesGit()\name = rel$ main\listFilesGit()\status = " " ; 2 espaces = clean main\listFilesGit()\indexStatus = " " main\listFilesGit()\workingTreeStatus = " " main\listFilesGit()\statusDescription = "Unmodified" EndIf Wend FinishDirectory(did) EndProcedure ; --- API publique : appelle ceci pour remplir la liste Procedure.i FillAllFilesRecursively() root$ = GetCurrentDirectory() ; Normalise avec un séparateur de fin If Right(root$, 1) <> "\" And Right(root$, 1) <> "/" : root$ + "/" : EndIf ; On n'efface pas ici pour laisser le choix à l'appelant _ScanAndFillAllFiles(root$, root$) ProcedureReturn ListSize(main\listFilesGit()) EndProcedure Procedure.i EnsureGitAvailable() Debug "EnsureGitAvailable()" main\GitCall\args = "--version" Debug main\GitCall\output If RunExe(@main\GitCall) = 0 And FindString(main\GitCall\output, "git version", 1) main\info\isGit=#True ProcedureReturn 1 EndIf main\info\isGit=#False MessageRequester("PBIDE-GitTool", T("NoGitDeteced"), #PB_MessageRequester_Warning) ProcedureReturn 0 EndProcedure ; X (1er caractère) = État dans l'index (staging area) ; Y (2ème caractère) = État dans le working directory (répertoire de travail) ; ; Valeurs possibles pour chaque position ; Caractères de base : ; ; . = aucun changement ; M = modifié (Modified) ; A = ajouté (Added) - nouveau fichier ; D = supprimé (Deleted) ; R = renommé (Renamed) ; C = copié (Copied) ; T = type changé (Type changed) - ex: fichier → lien symbolique ; U = non mergé (Unmerged) - conflit de merge ; ? = non suivi (Untracked) - uniquement en position Y ; ! = ignoré (Ignored) - uniquement en position Y ;-status Procedure.s ExtractField(line.s, position.l) ; Extrait un champ à une position donnée (séparé par espaces) Protected count.l = CountString(line, " ") If position <= count + 1 ProcedureReturn StringField(line, position, " ") EndIf ProcedureReturn "" EndProcedure Procedure ParseBranchHeader(line.s, *status.GitStatus) ; Parse les headers de branche (lignes commençant par #) Protected parts.s, key.s, value.s ; Supprimer le "# " du début line = Mid(line, 3) ; Séparer clé et valeur Protected spacePos.l = FindString(line, " ") If spacePos > 0 key = Left(line, spacePos - 1) value = Mid(line, spacePos + 1) Select key Case "branch.oid" *status\branchInfo\oid = value Case "branch.head" *status\branchInfo\head = value Case "branch.upstream" *status\branchInfo\upstream = value Case "branch.ab" ; Format: "+X -Y" Protected ahead$ = StringField(value, 1, " ") Protected behind$ = StringField(value, 2, " ") If Left(ahead$, 1) = "+" *status\branchInfo\ahead = Val(Mid(ahead$, 2)) EndIf If Left(behind$, 1) = "-" *status\branchInfo\behind = Val(Mid(behind$, 2)) EndIf EndSelect EndIf EndProcedure Procedure ParseFileEntry(line.s, *status.GitStatus) ; Parse une entrée de fichier Protected newEntry.GitFileEntry Protected tabPos.l, parts.s ; Type d'entrée (premier caractère) newEntry\type = Asc(Left(line, 1)) Select newEntry\type Case '1' ; Fichier normal ; Format: 1 XY sub mH mI mW hH hI path newEntry\statusIndex = Asc(Mid(line, 3, 1)) newEntry\statusWorktree = Asc(Mid(line, 4, 1)) newEntry\submodule = Asc(Mid(line, 6, 1)) newEntry\modeHead = ExtractField(line, 4) newEntry\modeIndex = ExtractField(line, 5) newEntry\modeWorktree = ExtractField(line, 6) newEntry\hashHead = ExtractField(line, 7) newEntry\hashIndex = ExtractField(line, 8) newEntry\path = ExtractField(line, 9) Case '2' ; Renommage/Copie ; Format: 2 XY sub mH mI mW hH hI X pathorigPath newEntry\statusIndex = Asc(Mid(line, 3, 1)) newEntry\statusWorktree = Asc(Mid(line, 4, 1)) newEntry\submodule = Asc(Mid(line, 6, 1)) newEntry\modeHead = ExtractField(line, 4) newEntry\modeIndex = ExtractField(line, 5) newEntry\modeWorktree = ExtractField(line, 6) newEntry\hashHead = ExtractField(line, 7) newEntry\hashIndex = ExtractField(line, 8) newEntry\similarity = ExtractField(line, 9) ; Chemins séparés par une tabulation parts = ExtractField(line, 10) tabPos = FindString(parts, #TAB$) If tabPos > 0 newEntry\originalPath = Left(parts, tabPos - 1) newEntry\path = Mid(parts, tabPos + 1) Else newEntry\path = parts EndIf Case 'u' ; Conflit non résolu ; Format: u XY sub m1 m2 m3 mW h1 h2 h3 path newEntry\statusIndex = Asc(Mid(line, 3, 1)) newEntry\statusWorktree = Asc(Mid(line, 4, 1)) newEntry\submodule = Asc(Mid(line, 6, 1)) newEntry\modeHead = ExtractField(line, 4) ; stage 1 newEntry\modeIndex = ExtractField(line, 5) ; stage 2 newEntry\modeWorktree = ExtractField(line, 6) ; stage 3 ; Mode working tree à la position 7 newEntry\hashStage1 = ExtractField(line, 8) newEntry\hashStage2 = ExtractField(line, 9) newEntry\hashStage3 = ExtractField(line, 10) newEntry\path = ExtractField(line, 11) Case '?' ; Fichier non suivi newEntry\statusWorktree = '?' newEntry\path = Mid(line, 3) ; Tout après "? " Case '!' ; Fichier ignoré newEntry\statusWorktree = '!' newEntry\path = Mid(line, 3) ; Tout après "! " EndSelect ; Ajouter l'entrée à la liste AddElement(*status\files()) *status\files() = newEntry EndProcedure Procedure.s GetStatusDescription(status.s) Select status Case " " ProcedureReturn "Unmodified" ;Non modifié Case "M " ProcedureReturn "Modified in index";Modifié dans index seulement Case " M" ProcedureReturn "Modified in working tree";Modifié dans working tree seulement Case "MM" ProcedureReturn "Modified in index and working tree";Modifié dans index ET working tree Case "A " ProcedureReturn "Added to index";Ajouté à l'index Case "AM" ProcedureReturn "Added to index, modified in working tree";Ajouté à l'index, modifié dans working tree Case " D" ProcedureReturn "Deleted from working tree";Supprimé du working tree Case "D " ProcedureReturn "Deleted from index";Supprimé de l'index Case "AD" ProcedureReturn "Added to index, deleted from working tree";Ajouté à l'index, supprimé du working tree Case " R" ProcedureReturn "Renamed in working tree";Renommé dans working tree Case "R " ProcedureReturn "Renamed in index";Renommé dans index Case " C" ProcedureReturn "Copied in working tree";Copié dans working tree Case "C " ProcedureReturn "Copied in index";Copié dans index Case "DD" ProcedureReturn "Deleted by both (conflict)";Supprimé dans les deux (conflit) Case "AU" ProcedureReturn "Added by us (conflict)";Ajouté par eux (conflit) Case "UD" ProcedureReturn "Deleted by them (conflict)";Supprimé par eux (conflit) Case "UA" ProcedureReturn "Added by them (conflict)";Ajouté par eux (conflit) Case "DU" ProcedureReturn "Deleted by us (conflict)";Supprimé par nous (conflit) Case "AA" ProcedureReturn "Added by both (conflict)";Ajouté des deux côtés (conflit) Case "UU" ProcedureReturn "Modified by both (conflict)";Modifié des deux côtés (conflit) Case "??" ProcedureReturn "Untracked" ;Non suivi Case "!!" ProcedureReturn "Ignored" ;Ignoré Default ProcedureReturn "Unknown status ->"+status EndSelect EndProcedure Procedure GetGitStatus() ; Ne PAS vider la liste ici : on veut pouvoir mettre à jour des entrées existantes ; ClearList(main\listFilesGit()) ; <-- laissé intentionnellement commenté ; Configuration pour git status --porcelain --ignored main\Gitcall\args = "status --porcelain --ignored" code = RunExe(@main\Gitcall) If code = 0 ; Parser la sortie ligne par ligne output$ = main\Gitcall\output ; Diviser en lignes Dim lines.s(0) lineCount = 0 currentPos = 1 ; Compter les lignes For i = 1 To Len(output$) If Mid(output$, i, 1) = #LF$ Or Mid(output$, i, 1) = #CR$ lineCount + 1 EndIf Next ; Redimensionner le tableau If lineCount > 0 ReDim lines.s(lineCount) ; Remplir le tableau avec les lignes lineIndex = 0 startPos = 1 For i = 1 To Len(output$) If Mid(output$, i, 1) = #LF$ Or Mid(output$, i, 1) = #CR$ If i > startPos lines(lineIndex) = Mid(output$, startPos, i - startPos) lineIndex + 1 EndIf startPos = i + 1 ; Gérer CRLF If Mid(output$, i, 1) = #CR$ And i < Len(output$) And Mid(output$, i + 1, 1) = #LF$ i + 1 startPos = i + 1 EndIf EndIf Next ; Traiter la dernière ligne si elle n'a pas de retour à la ligne If startPos <= Len(output$) lines(lineIndex) = Mid(output$, startPos) EndIf EndIf ; Parser chaque ligne For i = 0 To ArraySize(lines()) line$ = lines(i) If line$ <> "" ; Le format est : XY␠ ; X = index status, Y = working tree status If Len(line$) >= 3 status$ = Left(line$, 2) index$ = Left(line$, 1) worktree$ = Mid(line$, 2, 1) name$ = Mid(line$, 4) ; chemin tel que renvoyé par Git name$ = ReplaceString(name$, "\", "/") ; normalisation (par sécurité) ; ----- MODIF: chercher si l'entrée existe déjà ----- found.b = #False ForEach main\listFilesGit() If main\listFilesGit()\name = name$ found = #True ; Mise à jour uniquement main\listFilesGit()\status = status$ main\listFilesGit()\indexStatus = index$ main\listFilesGit()\workingTreeStatus = worktree$ main\listFilesGit()\statusDescription = GetStatusDescription(status$) Break EndIf Next ; Si non trouvée, on l'ajoute If Not found AddElement(main\listFilesGit()) main\listFilesGit()\name = name$ main\listFilesGit()\status = status$ main\listFilesGit()\indexStatus = index$ main\listFilesGit()\workingTreeStatus = worktree$ main\listFilesGit()\statusDescription = GetStatusDescription(status$) EndIf ; ----- FIN MODIF ----- EndIf EndIf Next Debug "Récupération des status Git réussie. " + Str(ListSize(main\listFilesGit())) + " fichiers (maj/ajout)." ProcedureReturn #True Else Debug "Erreur Git ("+Str(code)+") "+main\GitCall\errors ProcedureReturn #False EndIf EndProcedure ; Récupère le nombre immédiatement avant un mot-clé dans une ligne Procedure.i ExtractNumberBefore(word.s, line.s) Protected pos = FindString(line, word, 1) If pos = 0 : ProcedureReturn -1 : EndIf Protected i = pos - 1 ; remonter jusqu'au début du bloc de chiffres While i > 0 And Mid(line, i, 1) = " " i - 1 Wend Protected j = i While j > 0 And Mid(line, j, 1) >= "0" And Mid(line, j, 1) <= "9" j - 1 Wend Protected num$ = Mid(line, j + 1, i - j) If num$ <> "" : ProcedureReturn Val(num$) : EndIf ProcedureReturn -1 EndProcedure ; Construit "N fichiers, +X/-Y" à partir d’un bloc courtstat Procedure.s BuildFilesSummary(statBlock.s) Protected files = -1, ins = -1, del = -1, i, n, line$ n = CountString(statBlock, #LF$) + 1 For i = 1 To n line$ = Trim(StringField(statBlock, i, #LF$)) If FindString(line$, "changed", 1) files = ExtractNumberBefore("file", line$) If files = -1 : files = ExtractNumberBefore("files", line$) : EndIf ins = ExtractNumberBefore("insertion", line$) del = ExtractNumberBefore("deletion", line$) Break EndIf Next Protected out$ If files >= 0 out$ = Str(files) + " fichiers" If ins >= 0 Or del >= 0 out$ + ", " If ins >= 0 : out$ + "+" + Str(ins) : EndIf If del >= 0 : out$ + "/-" + Str(del) : EndIf EndIf EndIf If out$ = "" : out$ = "-" : EndIf ProcedureReturn out$ EndProcedure ; Remplit #GdtListHistory avec l'historique Git ; MaxCount : nombre max de commits (par défaut 200) Procedure.i GetCommitHistory(MaxCount.i = 200) Protected code.i, args.s, out$, rec$, headerLine$, rest$, idx.i Protected fs.s, header.s, date$, author$, refs$, subj$ Protected full$, short$, an$, ae$, rs$ = Chr(30), us$ = Chr(31) Protected start.i, stop.i, pos.i If EnsureGitAvailable() = 0 MessageRequester("Git log", "Git introuvable.", #PB_MessageRequester_Error) ProcedureReturn 0 EndIf DisableGadget(#GdtListHistory, #True) ClearGadgetItems(#GdtListHistory) ClearList(main\GitHistory()) ; -- Commande log : ; - séparateur enregistrements RS (0x1E) en tête de chaque commit ; - séparateur champs US (0x1F) ; - --shortstat pour "N files changed, X insertions, Y deletions" args = "log --max-count=" + Str(MaxCount) + " --decorate=short --shortstat " + "--date=format-local:" + Chr(34) + "%Y-%m-%d %H:%M" + Chr(34) + " " + "--pretty=format:" + Chr(34) + "%x1e%H%x1f%h%x1f%ad%x1f%an%x1f%ae%x1f%s%x1f%D" + Chr(34) main\GitCall\args = args code = RunExe(@main\GitCall) If code <> 0 MessageRequester("Git log", "Échec: " + #LF$ + main\GitCall\errors, #PB_MessageRequester_Error) DisableGadget(#GdtListHistory, #False) ProcedureReturn 0 EndIf out$ = main\GitCall\output If out$ = "" DisableGadget(#GdtListHistory, #False) ProcedureReturn 1 EndIf ; On ajoute un RS final pour faciliter le découpage out$ + rs$ pos = 1 While pos <= Len(out$) ; chercher le début d’un enregistrement start = FindString(out$, rs$, pos) If start = 0 : Break : EndIf start + 1 ; chercher le prochain RS (fin de l’enregistrement) stop = FindString(out$, rs$, start) If stop = 0 : Break : EndIf rec$ = Mid(out$, start, stop - start) pos = stop If rec$ = "" : Continue : EndIf ; La première ligne = les champs formatés ; le reste = bloc shortstat headerLine$ = StringField(rec$, 1, #LF$) rest$ = Mid(rec$, Len(headerLine$) + 2) ; Découper les champs (US = 0x1F) full$ = StringField(headerLine$, 1, us$) short$ = StringField(headerLine$, 2, us$) date$ = StringField(headerLine$, 3, us$) an$ = StringField(headerLine$, 4, us$) ae$ = StringField(headerLine$, 5, us$) subj$ = StringField(headerLine$, 6, us$) refs$ = StringField(headerLine$, 7, us$) header = short$ If Trim(refs$) <> "" : header + " (" + refs$ + ")" : EndIf author$ = an$ + " <" + ae$ + ">" fs = BuildFilesSummary(rest$) ; Mémoriser + insérer la ligne dans la ListIcon AddElement(main\GitHistory()) main\GitHistory()\fullHash = full$ main\GitHistory()\shortHash = short$ main\GitHistory()\date = date$ main\GitHistory()\authorName = an$ main\GitHistory()\authorEmail = ae$ main\GitHistory()\refs = refs$ main\GitHistory()\subject = subj$ main\GitHistory()\filesSummary = fs idx = CountGadgetItems(#GdtListHistory) AddGadgetItem(#GdtListHistory, idx, header + Chr(10) + date$ + Chr(10) + author$ + Chr(10) + fs + Chr(10) + subj$) ; On stocke un pointeur sur la ligne pour un usage futur (ex: bouton Restore) SetGadgetItemData(#GdtListHistory, idx, @main\GitHistory()) If #EnableDebug Debug "[LOG] " + short$ + " | " + date$ + " | " + author + " | " + fs + " | " + subj$ EndIf Wend DisableGadget(#GdtListHistory, #False) ProcedureReturn 1 EndProcedure ; ============================================================================= ;-.gitignore ; ============================================================================= Procedure.s NormalizeGitIgnoreEntry(entry.s) Protected e$ = Trim(entry) e$ = ReplaceString(e$, "\", "/") ; Git préfère les / If Left(e$, 2) = "./" : e$ = Mid(e$, 3) : EndIf ProcedureReturn e$ EndProcedure Procedure.b GitIgnoreHasEntry(filePath.s, entry.s) Protected f, line$ entry = NormalizeGitIgnoreEntry(entry) f = ReadFile(#PB_Any, filePath) If f While Eof(f) = 0 line$ = Trim(ReadString(f)) If line$ <> "" And Left(line$, 1) <> "#" If NormalizeGitIgnoreEntry(line$) = entry CloseFile(f) ProcedureReturn #True EndIf EndIf Wend CloseFile(f) EndIf ProcedureReturn #False EndProcedure Procedure.i GitIgnoreAddEntry(filePath.s, entry.s) Protected f entry = NormalizeGitIgnoreEntry(entry) f = OpenFile(#PB_Any, filePath, #PB_File_Append) If f ; S’assure que le fichier finit bien par un saut de ligne If Lof(f) > 0 : WriteStringN(f, "") : EndIf WriteStringN(f, entry) CloseFile(f) ProcedureReturn #True EndIf ProcedureReturn #False EndProcedure Procedure.i GitIgnoreRemoveEntry(filePath.s, entry.s) Protected fin, fout, line$, tmp$ entry = NormalizeGitIgnoreEntry(entry) tmp$ = filePath + ".tmp" fin = ReadFile(#PB_Any, filePath) If fin = 0 : ProcedureReturn #False : EndIf fout = CreateFile(#PB_Any, tmp$) If fout = 0 CloseFile(fin) ProcedureReturn #False EndIf While Eof(fin) = 0 line$ = ReadString(fin) If NormalizeGitIgnoreEntry(Trim(line$)) <> entry WriteStringN(fout, line$) EndIf Wend CloseFile(fin) : CloseFile(fout) DeleteFile(filePath) If RenameFile(tmp$, filePath) = 0 ; rollback si nécessaire RenameFile(tmp$, filePath) ProcedureReturn #False EndIf ProcedureReturn #True EndProcedure ; ============================================================================= ; Action principale : toggle pour l’élément sélectionné (ou les éléments cochés) ; ============================================================================= Procedure.i ToggleGitIgnoreForSelection() Protected gitignore$, i.i, count.i, path$, added.i, removed.i, create.i Protected nbSelected.i = 0, idx.i gitignore$ = main\GitCall\workdir + ".gitignore" count = CountGadgetItems(#GdtListStatus) ; -- Récupère toutes les lignes sélectionnées (multi ou simple) Dim selected.i(Max(count-1, 0)) For i = 0 To count - 1 If GetGadgetItemState(#GdtListStatus, i) & #PB_ListIcon_Selected selected(nbSelected) = i nbSelected + 1 EndIf Next ; fallback si le gadget n’est pas en multi-sélection If nbSelected = 0 idx = GetGadgetState(#GdtListStatus) If idx >= 0 ReDim selected.i(0) selected(0) = idx nbSelected = 1 EndIf EndIf If nbSelected = 0 MessageRequester("Git .gitignore", "Sélectionne au moins un fichier dans la liste.", #PB_MessageRequester_Warning) ProcedureReturn 0 EndIf ; -- S’assurer que .gitignore existe If FileSize(gitignore$) = -1 create = MessageRequester("Git .gitignore", "Le fichier .gitignore n’existe pas."+#LF$+"Le créer maintenant ?", #PB_MessageRequester_YesNo) If create = #PB_MessageRequester_Yes Protected hf.i = CreateFile(#PB_Any, gitignore$) If hf : CloseFile(hf) : Else MessageRequester("Git .gitignore", "Impossible de créer .gitignore.", #PB_MessageRequester_Error) ProcedureReturn 0 EndIf Else ProcedureReturn 0 EndIf EndIf ; -- Traite chaque fichier sélectionné For i = 0 To nbSelected - 1 path$ = StringField(GetGadgetItemText(#GdtListStatus, selected(i)), 1, Chr(10)) path$ = NormalizeGitIgnoreEntry(path$) If GitIgnoreHasEntry(gitignore$, path$) If MessageRequester("Git .gitignore", "“"+path$+"” est déjà ignoré."+#LF$+"Le retirer de .gitignore ?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes If GitIgnoreRemoveEntry(gitignore$, path$) : removed + 1 : EndIf EndIf Else If MessageRequester("Git .gitignore", "“"+path$+"” n’est pas ignoré."+#LF$+"L’ajouter à .gitignore ?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes If GitIgnoreAddEntry(gitignore$, path$) : added + 1 : EndIf EndIf EndIf Next If added Or removed MessageRequester("Git .gitignore", "Ajoutées : "+Str(added)+#LF$+"Retirées : "+Str(removed), #PB_MessageRequester_Info) CreateThread(@RefreshFileList(), 0) ProcedureReturn 1 EndIf ProcedureReturn 0 EndProcedure Procedure ReadGitIgnorefile() If FileSize(".gitignore")<>-1 DisableGadget(#GdtTxtGitIgnore,#False) Protected hf.i=OpenFile(#PB_Any,".gitignore") Protected gitignore.s="" If hf While Eof(hf) = 0 ; Boucle tant que la fin du fichier n'est pas atteinte. (Eof = 'End Of File') gitignore=gitignore+ReadString(hf)+#LF$ ; Affiche du fichier Wend CloseFile(hf) SetGadgetText(#GdtTxtGitIgnore,gitignore) EndIf Else DisableGadget(#GdtTxtGitIgnore,#True) EndIf EndProcedure ;-Suite Procedure AddRemoteRepo(Url.s,name.s="origin") Url=_SupTrim(Url) name=_SupTrim(name) Protected add.b=#False ;Check if this remote already exists main\Gitcall\args = "remote get-url "+name If RunExe(@main\Gitcall) = 0 ;if yes we remove it If Url<>_SupTrim(main\Gitcall\output) main\Gitcall\args = "remote remove "+name RunExe(@main\Gitcall) add=#True EndIf Else add=#True EndIf ; We add a new remote If add=#True main\Gitcall\args = "remote add "+name+" "+Url If RunExe(@main\Gitcall) = 0 ;MessageRequester("Git Remote", "OK:" + #LF$ + main\Gitcall\output, #PB_MessageRequester_Info) ProcedureReturn #True Else MessageRequester("Git config", "Échec: " + #LF$ + main\Gitcall\errors, #PB_MessageRequester_Error) ProcedureReturn #False EndIf ProcedureReturn #True EndIf EndProcedure Procedure GetRemoteUrl(name.s="origin") main\Gitcall\args = "remote get-url "+name If RunExe(@main\Gitcall) = 0 SetGadgetText(#GdtFieldRemote,_SupTrim(main\GitCall\output)) Else SetGadgetText(#GdtFieldRemote,"") EndIf EndProcedure Procedure DoPush() If Trim(GetGadgetText(#GdtFieldRemote))="" MessageRequester("Git Push", "Échec: " + #LF$ + "You Must tu have a remote", #PB_MessageRequester_Error) EndIf AddRemoteRepo(Trim(GetGadgetText(#GdtFieldRemote))) main\Gitcall\args = "push -u origin "+GetGadgetText(#GdtSlctBranch) If RunExe(@main\Gitcall) = 0 MessageRequester("Git Push", "OK:" + #LF$ + main\Gitcall\output, #PB_MessageRequester_Info) ProcedureReturn #True Else MessageRequester("Git Push", "Échec: " + #LF$ + main\Gitcall\errors, #PB_MessageRequester_Error) ProcedureReturn #False EndIf EndProcedure Procedure DoPull() If Trim(GetGadgetText(#GdtFieldRemote))="" MessageRequester("Git Pull", "Échec: " + #LF$ + "You Must tu have a remote", #PB_MessageRequester_Error) EndIf AddRemoteRepo(Trim(GetGadgetText(#GdtFieldRemote))) main\Gitcall\args = "pull origin "+GetGadgetText(#GdtSlctBranch)+" --allow-unrelated-histories" If RunExe(@main\Gitcall) = 0 MessageRequester("Git Pull", "OK:" + #LF$ + main\Gitcall\output, #PB_MessageRequester_Info) ProcedureReturn #True Else MessageRequester("Git Pull", "Échec: " + #LF$ + main\Gitcall\errors, #PB_MessageRequester_Error) ProcedureReturn #False EndIf EndProcedure Procedure.s ExtractRepoNameFromUrl(url.s) ; helper for Doclone ; Extraire le nom du dépôt depuis l'URL Protected repoName.s, parts.i, i.i ; Supprimer .git à la fin si présent If Right(url, 4) = ".git" url = Left(url, Len(url) - 4) EndIf ; Remplacer les \ par des / pour uniformiser url = ReplaceString(url, "\", "/") ; Compter le nombre de parties séparées par / parts = CountString(url, "/") ; Extraire la dernière partie If parts > 0 repoName = StringField(url, parts + 1, "/") Else repoName = url EndIf ProcedureReturn repoName EndProcedure Procedure.s GetParentPath(path.s) Protected parentPath.s ; Supprimer le séparateur final si présent If Right(Path, 1) = "\" Or Right(Path, 1) = "/" Path = Left(Path, Len(Path) - 1) EndIf ; Utiliser GetPathPart() qui retourne le chemin sans le fichier/dossier final parentPath = GetPathPart(path) ProcedureReturn parentPath EndProcedure Procedure DoClone() Protected remoteUrl.s, repoName.s, targetFolder.s, currentDir.s, choice.i remoteUrl = Trim(GetGadgetText(#GdtFieldRemote)) If remoteUrl = "" MessageRequester("Git Clone", "Échec: " + #LF$ + "You Must have a remote URL", #PB_MessageRequester_Error) ProcedureReturn #False EndIf ; Extraire le nom du dépôt depuis l'URL Debug "remoteUrl="+remoteUrl repoName = ExtractRepoNameFromUrl(remoteUrl) Debug "repoName="+repoName ; Obtenir le chemin complet du répertoire courant currentDir = GetCurrentDirectory() ; Demander à l'utilisateur où cloner choice = MessageRequester("Git Clone - Destination", "Où voulez-vous cloner le dépôt ?" + #LF$ + #LF$ + "OUI: Créer un répertoire '" + repoName + "' dans le répertoire courant" + #LF$ + " → " + currentDir + repoName + "/" + #LF$ + #LF$ + "NON: Cloner directement dans le répertoire courant" + #LF$ + " → " + currentDir + #LF$ + #LF$ + "ANNULER: Annuler l'opération", #PB_MessageRequester_YesNoCancel) Select choice Case #PB_MessageRequester_Yes ; Créer un nouveau répertoire avec le nom du dépôt targetFolder = repoName main\Gitcall\args = "clone " + remoteUrl + " " + targetFolder Case #PB_MessageRequester_No ; Cloner directement dans le répertoire courant (doit être vide) SetCurrentDirectory(GetParentPath(GetGadgetText(#GgtFieldRepo))) targetFolder = "." main\Gitcall\args = "clone " + remoteUrl + " " + targetFolder Case #PB_MessageRequester_Cancel ; Annuler l'opération ProcedureReturn #False EndSelect ; Exécuter la commande git clone If RunExe(@main\Gitcall) = 0 If targetFolder = "." MessageRequester("Git Clone", "Succès:" + #LF$ + "Dépôt cloné dans le répertoire courant" + #LF$ + #LF$ + main\Gitcall\output, #PB_MessageRequester_Info) Else MessageRequester("Git Clone", "Succès:" + #LF$ + "Dépôt cloné dans le répertoire: " + targetFolder + "/" + #LF$ + #LF$ + main\Gitcall\output, #PB_MessageRequester_Info) EndIf AddRemoteRepo(remoteUrl) SetCurrentDirectory(GetGadgetText(#GgtFieldRepo)) CreateThread(@RefreshFileList(),0) ProcedureReturn #True Else MessageRequester("Git Clone", "Échec: " + #LF$ + main\Gitcall\errors, #PB_MessageRequester_Error) ProcedureReturn #False EndIf EndProcedure Procedure DoStatus() Protected *status.GitStatus=@main\GitStatus main\Gitcall\args = "status --porcelain=v2 --ignored" If RunExe(@main\Gitcall) <> 0 ProcedureReturn 0 EndIf ; Parser ligne par ligne Protected n.l = CountString(main\Gitcall\output, #LF$) + 1 Protected i.l, line.s For i = 1 To n line = StringField(main\Gitcall\output, i, #LF$) line = Trim(line) If Len(line) = 0 Continue ; Ignorer les lignes vides EndIf ; Parser selon le type de ligne If Left(line, 1) = "#" ; Header de branche ParseBranchHeader(line, *status) Else ; Entrée de fichier ParseFileEntry(line, *status) EndIf Next ; Exemple d'utilisation des données parsées Debug "=== Informations de branche ===" Debug "Branche: " + *status\branchInfo\head Debug "Commit: " + *status\branchInfo\oid Debug "Upstream: " + *status\branchInfo\upstream Debug "En avance: " + Str(*status\branchInfo\ahead) Debug "En retard: " + Str(*status\branchInfo\behind) Debug "=== Fichiers ===" ForEach *status\files() Protected find.b=#False ForEach main\listFilesGit() If main\listFilesGit()\name=*status\files()\path: find=#True Debug "> "+GetFilePart(main\listFilesGit()\name)+" "+main\listFilesGit()\status+"<>"+*status\files()\statusIndex+Chr(*status\files()\statusWorktree) main\listFilesGit()\status=Chr(*status\files()\statusIndex)+Chr(*status\files()\statusWorktree) If Len(main\listFilesGit()\status)=1 main\listFilesGit()\status="?"+main\listFilesGit()\status EndIf Break EndIf Next Debug "Type: >" + Chr(*status\files()\type)+"<" Debug "Statut Index/Worktree: " + Chr(*status\files()\statusIndex) + "/" + Chr(*status\files()\statusWorktree) Debug "Chemin: " + *status\files()\path If *status\files()\originalPath <> "" Debug "Chemin original: " + *status\files()\originalPath EndIf If *status\files()\similarity <> "" Debug "Similarité: " + *status\files()\similarity EndIf Debug "---" Next ProcedureReturn 1 EndProcedure Procedure DoInit() main\Gitcall\args="init --initial-branch="+main\Currentbranch If RunExe(@main\Gitcall)=0 MessageRequester("Git config", "OK:" + #LF$ + main\Gitcall\output, #PB_MessageRequester_Info) ProcedureReturn #True Else MessageRequester("Git config", "Échec: " + #LF$ + main\Gitcall\errors, #PB_MessageRequester_Error) ProcedureReturn #False EndIf EndProcedure Procedure DoLsFilesByType( includeType.s = "CDOI") ; Récupère les fichiers par type spécifique ; includeType: "C"=cached, "D"=deleted, "O"=others, "I"=ignored Protected args.s = "ls-files" Protected status.s="" ; Construire les arguments selon les types demandés If FindString(includeType, "C") > 0 args + " -s --cached --stage" status =".." EndIf If FindString(includeType, "D") > 0 args + " --deleted" status=".D" EndIf If FindString(includeType, "O") > 0 args + " --others" status="??" EndIf If FindString(includeType, "I") > 0 args + " --ignored --exclude-standard" status="!!" EndIf Debug args main\Gitcall\args = args If RunExe(@main\Gitcall) <> 0 ProcedureReturn 0 EndIf ; Parser les résultats Protected n.l = CountString(main\Gitcall\output, #LF$) + 1 Protected i.l, line.s For i = 1 To n line = StringField(main\Gitcall\output, i, #LF$) line = Trim(line) If Len(line) = 0 Continue EndIf AddElement(main\listFilesGit()) ; Format avec métadonnées (fichiers cachés avec --stage) If CountString(line, " ") >= 2 And FindString(line, #TAB$) > 0 ;Debug "mode :"+StringField(line, 1, " ") ;Debug "hash :"+StringField(line, 2, " ") ;Debug "stageAndPath :" + StringField(line, 3, " ") Protected tabPos.l = FindString(StringField(line, 3, " "), #TAB$) If tabPos > 0 ;Debug "-stage :"+ Left(StringField(line, 3, " "), tabPos - 1) main\listFilesGit()\name=Mid(StringField(line, 3, " "), tabPos + 1) EndIf Else ; Format simple: juste le chemin main\listFilesGit()\name=line EndIf main\listFilesGit()\status=status Next EndProcedure Procedure.i DoCommit() Protected code.i,nb.l=0 main\GitCall\args = "add" For i = 0 To CountGadgetItems(#GdtListStatus) - 1 If GetGadgetItemState(#GdtListStatus, i) & #PB_ListIcon_Checked nb+1 main\GitCall\args+" "+Chr(34)+StringField(GetGadgetItemText(#GdtListStatus, i),1,Chr(10))+Chr(34) EndIf Next i If nb=0 MessageRequester("Git add", "Échec: Vous devez selectionnez des fichiers à ajouter au commit", #PB_MessageRequester_Error) ProcedureReturn 0 EndIf If Trim(GetGadgetText(#GdtFieldMessage))="" MessageRequester("Git add", "Échec: Vous devez mettre un message", #PB_MessageRequester_Error) ProcedureReturn 0 EndIf code = RunExe(@main\GitCall) If code <> 0 MessageRequester("Git add", "Échec: " + #LF$ + main\GitCall\errors, #PB_MessageRequester_Error) ProcedureReturn 0 EndIf ; Commit with message / Valider avec un message main\GitCall\args = "commit -m " + Chr(34) + GetGadgetText(#GdtFieldMessage) + Chr(34) code = RunExe(@main\GitCall) If code <> 0 MessageRequester("Git commit", "Échec ou rien à valider: " + #LF$ + main\GitCall\errors + #LF$ + main\GitCall\output, #PB_MessageRequester_Warning) ProcedureReturn 0 Else MessageRequester("Git commit", "OK:" + #LF$ + main\GitCall\output, #PB_MessageRequester_Info) EndIf ProcedureReturn 1 EndProcedure Procedure.s GetBranchesList() ClearGadgetItems(#GdtSlctBranch) main\Gitcall\args = "branch -a" If RunExe(@main\Gitcall) <> 0 ProcedureReturn "" EndIf ; Parser ligne par ligne Protected n.l = CountString(main\Gitcall\output, #LF$) + 1 Protected i.l, line.s For i = 1 To n line = StringField(main\Gitcall\output, i, #LF$) line = Trim(line) If Len(line) = 0 Continue ; Ignorer les lignes vides EndIf selectbranch.b=#False If Left(line,1)="*" selectbranch=#True line=Trim(StringField(line,2," ")) EndIf AddGadgetItem(#GdtSlctBranch,i-1,line) If selectbranch=#True SetGadgetState(#GdtSlctBranch,i-1) EndIf Next EndProcedure ; ============================================================================= ;-Identity ; ============================================================================= Procedure SetGitIdentity() If main\info\isGit=#True ; check Git is detected Protected userName.s = Trim(GetGadgetText(#GdtFieldUserName)) Protected userEmail.s = Trim(GetGadgetText(#GdtFieldUserEmail)) Protected scope.s = LCase(GetGadgetText(#GdtSlctScope)) Protected result.s="" Protected errors.s="" ; Définir le nom d'utilisateur If userName <> "" main\Gitcall\args = "config --" + scope + " user.name " + Chr(34) + userName + Chr(34) Debug "Setting username: " + main\Gitcall\args If RunExe(@main\Gitcall) <> 0 errors=errors+ main\Gitcall\errors+ #LF$ Else result=result+ main\Gitcall\output + #LF$ EndIf Else Debug "Nom d'utilisateur vide, ignoré" EndIf ; Définir l'email If userEmail <> "" main\Gitcall\args = "config --" + scope + " user.email " + Chr(34) + userEmail + Chr(34) Debug "Setting email: " + main\Gitcall\args If RunExe(@main\Gitcall) <> 0 errors=errors+ main\Gitcall\errors Else result=result+ main\Gitcall\output EndIf Else Debug "Email vide, ignoré" EndIf If errors="" And Not result="" MessageRequester("Git config", "OK:" + #LF$ + result, #PB_MessageRequester_Info) ProcedureReturn #False Else MessageRequester("Git config", "Échec: " + #LF$ + errors, #PB_MessageRequester_Error) ProcedureReturn #True EndIf Else Debug "Git non détecté" ProcedureReturn #False EndIf EndProcedure Procedure GetGitIdentity() If main\info\isGit=#True ; check Git is detected main\Gitcall\args = "config --get --"+LCase(GetGadgetText(#GdtSlctScope))+" user.name" Debug main\Gitcall\args If RunExe(@main\Gitcall) = 0 SetGadgetText(#GdtFieldUserName,Trim(main\GitCall\output)) Else SetGadgetText(#GdtFieldUserName,"") EndIf main\Gitcall\args = "config --get --"+LCase(GetGadgetText(#GdtSlctScope))+" user.email" If RunExe(@main\Gitcall) = 0 SetGadgetText(#GdtFieldUserEmail,Trim(main\GitCall\output)) Else SetGadgetText(#GdtFieldUserEmail,"") EndIf EndIf EndProcedure ; ============================================================================= ;-History Commit ; ============================================================================= ; Retrouver le full hash à partir du short hash Procedure.s FindFullHashByShort(short$) ForEach main\GitHistory() If LCase(main\GitHistory()\shortHash) = LCase(short$) ProcedureReturn main\GitHistory()\fullHash EndIf Next ProcedureReturn "" EndProcedure ; Affiche les infos (en-tête + fichiers + shortstat) dans l'éditeur Procedure.i ShowCommitInfo(hash$) Protected code.i, args.s, out$ If hash$ = "" SetGadgetText(#GdtTxtCommitInfo, "") ProcedureReturn 0 EndIf ; Sortie compacte et lisible (sans patch) ; - --decorate=short pour refs ; - --name-status pour statut par fichier ; - --stat pour le récap ; - --date=format-local pour respecter l’OS args = "show --no-color --decorate=short " + "--date=format-local:" + Chr(34) + "%Y-%m-%d %H:%M" + Chr(34) + " " + "--pretty=format:" + Chr(34) + "commit %H (%h)%nAuthor: %an <%ae>%nDate: %ad%nRefs: %D%n" + "%n %s%n%n%b" + Chr(34) + " " + "--name-status --stat " + hash$ main\GitCall\args = args code = RunExe(@main\GitCall) If code <> 0 SetGadgetText(#GdtTxtCommitInfo, "Erreur git show:" + #LF$ + main\GitCall\errors) ProcedureReturn 0 EndIf out$ = main\GitCall\output If out$ = "" : out$ = "(Aucune information renvoyée par git pour " + hash$ + ")" : EndIf SetGadgetText(#GdtTxtCommitInfo, out$) If #EnableDebug Debug "[ShowCommitInfo] " + hash$ EndIf ProcedureReturn 1 EndProcedure ; À appeler quand la sélection de #GdtListHistory change Procedure.i ShowSelectedCommitInfo() Protected row.i = GetGadgetState(#GdtListHistory) Protected *rowPtr.GitCommitRow, header$, short$, full$ If row < 0 SetGadgetText(#GdtTxtCommitInfo, "") ProcedureReturn 0 EndIf ; 1) On tente via le pointeur stocké dans l’item data *rowPtr = GetGadgetItemData(#GdtListHistory, row) If *rowPtr full$ = *rowPtr\fullHash EndIf ; 2) Fallback : on re-déduit via le texte de colonne 0 (short hash au début) If full$ = "" header$ = GetGadgetItemText(#GdtListHistory, row, 0) ; ex: "abc1234 (HEAD -> main)" short$ = StringField(header$, 1, " ") ; -> "abc1234" full$ = FindFullHashByShort(short$) EndIf ProcedureReturn ShowCommitInfo(full$) EndProcedure Procedure RefreshFileList(null.i) DisableGadget(#GdtListStatus,#True) DisableGadget(#GdtBtnRefresh,#True) ClearGadgetItems(#GdtListStatus) ClearList(main\listFilesGit()) ;-Init File List FillAllFilesRecursively() ;DoLsFilesByType("D") ;DoLsFilesByType("O") ;DoLsFilesByType("I") ;DoStatus() GetGitStatus() ForEach main\listFilesGit() main\listFilesGit()\importance = StatusImportance(main\listFilesGit()\status) Next ; --- Tri par importance (du plus important au moins important) SortStructuredList(main\listFilesGit(), #PB_Sort_Descending, OffsetOf(listFilesGit\importance), #PB_Integer) Protected n.l=n-1 ForEach main\listFilesGit() n=n+1 AddGadgetItem(#GdtListStatus,n,main\listFilesGit()\name+Chr(10)+main\listFilesGit()\status+Chr(10)+GetStatusDescription(main\listFilesGit()\status)) If Right(main\listFilesGit()\status,1)="M" SetGadgetItemState(#GdtListStatus,n,#PB_ListIcon_Checked) EndIf Next DisableGadget(#GdtListStatus,#False) DisableGadget(#GdtBtnRefresh,#False) GetBranchesList() GetRemoteUrl() GetCommitHistory() EndProcedure Procedure UpdateHelp(txt.s) SetGadgetItemText(#GdtHelp,#PB_Web_HtmlCode,txt.s) EndProcedure Procedure _SetTooltips() ; Dépôt GadgetToolTip(#GdtBtnInit, T("tip.init", "Initialiser un dépôt Git ici")) GadgetToolTip(#GdtBtnRefresh, T("tip.refresh", "Rafraîchir la liste des fichiers et l’état Git")) GadgetToolTip(#GgtFieldRepo, T("tip.repo.path", "Chemin du dossier projet (workdir)")) ; Remote / Branch GadgetToolTip(#GdtFieldRemote, T("tip.remote", "URL du remote (ex.: https://... ou git@host:org/repo.git)")) GadgetToolTip(#GdtSlctBranch, T("tip.branch.select","Choisir la branche active")) GadgetToolTip(#GdtBtnNewBranch,T("tip.branch.new", "Créer une nouvelle branche")) GadgetToolTip(#GdtBtnClone, T("tip.clone", "Cloner depuis l’URL remote")) GadgetToolTip(#GdtBtnPull, T("tip.pull", "Récupérer et fusionner depuis le remote")) GadgetToolTip(#GdtBtnPush, T("tip.push", "Envoyer vos commits sur le remote")) ; Fichiers & actions locales GadgetToolTip(#GdtListStatus, T("tip.files.list", "Fichiers du dépôt : cochez pour préparer un commit, sélectionnez pour agir")) GadgetToolTip(#GdtBtnRestore, T("tip.restore", "Restaurer les fichiers sélectionnés")) GadgetToolTip(#GdtBtnRename, T("tip.rename", "Renommer les fichiers sélectionnés")) GadgetToolTip(#GdtBtnDelete, T("tip.delete", "Supprimer les fichiers sélectionnés")) GadgetToolTip(#GdtBtnIgnore, T("tip.ignore", "Ajouter/retirer les fichiers sélectionnés dans .gitignore")) ; Commit GadgetToolTip(#GdtFieldMessage,T("tip.message", "Message du commit")) GadgetToolTip(#GdtBtnCommit, T("tip.commit", "Committer tous les fichiers cochés avec le message")) ; History GadgetToolTip(#GdtListHistory, T("tip.history", "Historique des commits")) GadgetToolTip(#GdtBtnRestoreCommit,T("tip.history.restore","Restaurer/checkout le commit sélectionné")) ; .gitignore GadgetToolTip(#GdtTxtGitIgnore, T("tip.gitignore.edit","Éditeur du .gitignore")) GadgetToolTip(#GdtBtnSaveGitIgnore,T("tip.gitignore.save","Sauvegarder le .gitignore")) ; Config GadgetToolTip(#GdtFieldUserName, T("tip.cfg.username","Nom d’utilisateur Git (user.name)")) GadgetToolTip(#GdtFieldUserEmail,T("tip.cfg.useremail","Email Git (user.email)")) GadgetToolTip(#GdtSlctScope, T("tip.cfg.scope", "Portée de la configuration (Local/Global/System)")) GadgetToolTip(#GdtBtnSaveCfg, T("tip.cfg.save", "Enregistrer la configuration Git")) EndProcedure ; ---- Procédure principale --------------------------------------------------- Procedure InitGadget() Protected hasGit.b = main\info\isGit Protected workdir$ = Trim(GetGadgetText(#GgtFieldRepo)) Protected repoDirOK.b = Bool(workdir$ <> "" And FileSize(workdir$) = -2) Protected isInit.b = Bool(repoDirOK And _RepoIsInitialized(workdir$)) Protected hasRemote.b = _HasRemote() Protected selCount.i = _CountSelectedItems(#GdtListStatus) Protected checkedCount.i = _CountCheckedItems(#GdtListStatus) Protected haveHistorySel.b = Bool(GetGadgetState(#GdtListHistory) >= 0) Protected userName$ = Trim(GetGadgetText(#GdtFieldUserName)) Protected userEmail$ = Trim(GetGadgetText(#GdtFieldUserEmail)) ; Mémoriser l’état init global main\info\isInit = isInit ; ---- Zone Dépôt ---------------------------------------------------------- DisableGadget(#GdtBtnInit, Bool(Not (hasGit And repoDirOK And Not isInit))) DisableGadget(#GdtBtnRefresh, Bool(Not (hasGit And repoDirOK))) DisableGadget(#GgtFieldRepo, Bool(Not hasGit)) ; ---- Remote / Branch ----------------------------------------------------- DisableGadget(#GdtFieldRemote, Bool(Not (hasGit And repoDirOK))) DisableGadget(#GdtSlctBranch, Bool(Not (hasGit And isInit))) DisableGadget(#GdtBtnNewBranch,Bool(Not (hasGit And isInit))) ; Règle demandée : Clone/Pull/Push uniquement si une remote est définie DisableGadget(#GdtBtnClone, Bool(Not (hasGit And repoDirOK And hasRemote))) DisableGadget(#GdtBtnPull, Bool(Not (hasGit And isInit And hasRemote))) DisableGadget(#GdtBtnPush, Bool(Not (hasGit And isInit And hasRemote))) ; ---- Fichiers & actions locales ----------------------------------------- ; Restaurer / Renommer / Supprimer / Ignorer : uniquement si des fichiers sont sélectionnés DisableGadget(#GdtBtnRestore, Bool(selCount = 0)) DisableGadget(#GdtBtnRename, Bool(selCount = 0)) DisableGadget(#GdtBtnDelete, Bool(selCount = 0)) DisableGadget(#GdtBtnIgnore, Bool(selCount = 0)) ; Commit : uniquement si des fichiers sont cochés DisableGadget(#GdtBtnCommit, Bool(Not (hasGit And isInit And checkedCount > 0))) ; ---- Onglet History ------------------------------------------------------ DisableGadget(#GdtBtnRestoreCommit, Bool(Not (hasGit And isInit And haveHistorySel))) ; ---- .gitignore ---------------------------------------------------------- ; On autorise la sauvegarde si le repo est initialisé (fichier créé si besoin) DisableGadget(#GdtBtnSaveGitIgnore, Bool(Not (hasGit And isInit))) ; ---- Config -------------------------------------------------------------- ; Le bouton "Enregistrer" actif si au moins un champ non vide DisableGadget(#GdtBtnSaveCfg, Bool(userName$ = "" And userEmail$ = "")) EndProcedure Macro RightGadget(GDT) GadgetX(GDT)+GadgetWidth(GDT) EndMacro Macro DownGadget(GDT) GadgetY(GDT)+GadgetHeight(GDT) EndMacro Procedure OpenGUI() Protected argCount.l=CountProgramParameters(), w.l,a$ If CountProgramParameters() <> 0 ; Parse command line arguments / Analyser les arguments de ligne de commande For w = 0 To argCount - 1 a$ = ProgramParameter(w) Select LCase(a$) Case "--project" : If w + 1 < argCount : main\GitCall\workdir = ProgramParameter(w + 1) : EndIf EndSelect Next w EndIf ;Init Defaut Workdir if no project If main\GitCall\workdir="" ;main\GitCall\workdir="C:\Users\413\Documents\Amstrad CPC\PBIDE-GitTool\" main\GitCall\workdir=GetCurrentDirectory() EndIf If EnsureGitAvailable()=0 Debug T("?GITINIT","WOULD YOU INITIALISE THIS FOLDER") End EndIf ; --- Dimensions générales --- #WinW = 950 #WinH = 720 ; plus haut pour la zone d’aide #PanelW = 920 #PanelH = 500 #InPad = 10 ; marge interne au Panel #InFrmY=30 #InMargin = 10 ; espace vertical entre frames ;#RowH = 26 #BtnH = 30 #BtnW = 95 If OpenWindow(#WinMain, 0, 0, #WinW, #WinH, "Git helpmate", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) PanelGadget(#GdtPnl, #InPad,#InPad, #PanelW, #PanelH) ; ============================================================ ; Onglet 1 : Dépôt ; ============================================================ AddGadgetItem(#gdtPnl, -1, T("#gdtPnl-Repo","Dépôt")) Protected PanelH.l=WindowHeight(#WinMain)- 2*#InPad ; ---- Cadre "Dépôt local" ---- Protected Width.l=#PanelW - #InPad*2 FrameGadget(#GdtFrmLocal, #InPad, #InPad, Width, #InFrmY+#BtnH*2+#InMargin*2, T("GdtFrmLocal","Dépôt local"),#PB_Frame_Container) TextGadget(#GdtLblRepo, #InPad, #InFrmY, 60, #BtnH , T("GdtLblRepo","Dépôt :")) width.l=GadgetWidth(#GdtFrmLocal)-#InPad*3-GadgetWidth(#GdtLblRepo)-#BtnW StringGadget(#GgtFieldRepo, RightGadget(#GdtLblRepo), #InFrmY, width, #BtnH, repoDir$) ButtonGadget(#GdtBtnBrowseRepo, RightGadget(#GgtFieldRepo)+#InPad, #InFrmY, #BtnW, #BtnH, T("GdtBtnBrowseRepo","Parcourir…")) ButtonGadget(#GdtBtnInit, #InPad , DownGadget(#GdtBtnBrowseRepo)+ #InMargin,#BtnW, #BtnH, T("GdtBtnInit","Init Dépôt")) ButtonGadget(#GdtBtnRefresh, RightGadget(#GdtBtnInit)+#InPad,GadgetY(#GdtBtnInit), #BtnW, #BtnH, T("GdtBtnRefresh","Rafraîchir")) CloseGadgetList() ; ---- Cadre "Distant" ---- Define yRemote = DownGadget(#GdtFrmLocal)+ #InPad FrameGadget(#GdtFrmRemote, #InPad, yRemote, #PanelW - 2*#InPad, #InFrmY+#BtnH*2+#InMargin*2, T("GdtFrmRemote","Distant (remote / branche)"),#PB_Frame_Container) TextGadget(#GdtLblRemote, #InPad , #InFrmY, 60, #BtnH, T("GdtLblRemote","Remote :")) StringGadget(#GdtFieldRemote, RightGadget(#GdtLblRemote)+#InMargin, #InFrmY, 420, #BtnH, "") TextGadget(#GdtLblBranch, RightGadget(#GdtFieldRemote)+ #InMargin, #InFrmY, 70, #BtnH, T("GdtLblBranch","Branche :")) ComboBoxGadget(#GdtSlctBranch, RightGadget(#GdtLblBranch), #InFrmY, 220, #BtnH, #PB_ComboBox_Editable) ButtonGadget(#GdtBtnNewBranch, RightGadget(#GdtSlctBranch), #InFrmY, 130, #BtnH, T("GdtBtnNewBranch","New branch")) ButtonGadget(#GdtBtnClone, #InPad,DownGadget(#GdtLblRemote)+#InMargin, #BtnW,#BtnH, T("GdtBtnClone","Clone")) ButtonGadget(#GdtBtnPull, RightGadget(#GdtBtnClone)+#InPad,GadgetY(#GdtBtnClone), #BtnW, #BtnH, T("GdtBtnPull","Pull")) ButtonGadget(#GdtBtnPush, RightGadget(#GdtBtnPull)+#InPad,GadgetY(#GdtBtnClone), #BtnW, #BtnH, T("GdtBtnPush","Push")) CloseGadgetList() ; ---- Cadre "Fichiers & modifications" ---- Define yFiles = DownGadget(#GdtFrmRemote)+ #InPad Define hFiles = #PanelH - yFiles - #InPad FrameGadget(#GdtFrmFiles, #InPad, yFiles, #PanelW - 2*#InPad, hFiles, T("GdtFrmFiles","Fichiers & modifications"),#PB_Frame_Container) ListIconGadget(#GdtListStatus, #InPad, #InFrmY, GadgetWidth(#GdtFrmFiles)-2*#InPad, 120, T("GdtListStatus-Path","Path"), 300, #PB_ListIcon_CheckBoxes | #PB_ListIcon_MultiSelect | #PB_ListIcon_FullRowSelect |#PB_ListIcon_AlwaysShowSelection) AddGadgetColumn(#GdtListStatus, 1, T("GdtListStatus-Status","Status"), 50) AddGadgetColumn(#GdtListStatus, 2, T("GdtListStatus-Desc","Description"), 300) ; Actions locales Define yLocalActions = DownGadget(#GdtListStatus)+#InMargin ButtonGadget(#GdtBtnRestore, #InPad + 10, yLocalActions, 110, #BtnH, T("GdtBtnRestore","Restaurer")) ButtonGadget(#GdtBtnRename, #InPad + 130, yLocalActions, 110, #BtnH, T("GdtBtnRename","Renommer")) ButtonGadget(#GdtBtnDelete, #InPad + 250, yLocalActions, 110, #BtnH, T("GdtBtnDelete","Supprimer")) ButtonGadget(#GdtBtnIgnore, #InPad + 370, yLocalActions, 110, #BtnH, T("GdtBtnIgnore","Ignorer")) ; Message de commit Define yMsg = yLocalActions + #BtnH + 10 TextGadget(#GdtLblMessage, #InPad + 10, yMsg + 4, 80, 22, T("GdtLblMessage","Message :")) StringGadget(#GdtFieldMessage, #InPad + 95, yMsg, #PanelW - 2*#InPad - 95 - 110, #BtnH, "") ButtonGadget(#GdtBtnCommit, #PanelW - #InPad - 100, yMsg - 2, 100, #BtnH, T("GdtBtnCommit","Commit")) CloseGadgetList() ; ============================================================ ; Onglet 2 : History ; ============================================================ AddGadgetItem(#gdtPnl, -1, T("#gdtPnl-History","History")) ; ListIconGadget (colonnes: Header, Date, Auteur, Fichiers, Message) ListIconGadget(#GdtListHistory, #InPad, #InPad, #PanelW - 2*#InPad, #PanelH - 2*#InPad - #BtnH - 10 - 150 - 10, T("GdtListHistory-Header","Header"), 220, #PB_ListIcon_FullRowSelect) AddGadgetColumn(#GdtListHistory, 1, T("GdtListHistory-Date","Date"), 150) AddGadgetColumn(#GdtListHistory, 2, T("GdtListHistory-Author","Auteur"), 180) AddGadgetColumn(#GdtListHistory, 3, T("GdtListHistory-Files","Fichiers"), 120) AddGadgetColumn(#GdtListHistory, 4, T("GdtListHistory-Message","Message"), (#PanelW - 2*#InPad) - (220 + 150 + 180 + 120) - 20) ; Bouton Restore This Commit (sous la liste) ButtonGadget(#GdtBtnRestoreCommit, #InPad, #PanelH - #InPad - #BtnH - 150 - 10, 180, #BtnH, T("GdtBtnRestoreCommit","Restore This Commit")) ; Zone d’info du commit (Editor, lecture seule) EditorGadget(#GdtTxtCommitInfo, #InPad, #PanelH - #InPad - 150, #PanelW - 2*#InPad, 150, #PB_Editor_ReadOnly) ; ============================================================ ; Onglet 3 : .gitignore file ; ============================================================ AddGadgetItem(#gdtPnl, -1, T("#gdtPnl-gitignore",".gitignore file")) EditorGadget(#GdtTxtGitIgnore,#InPad, #InPad,#PanelW - 2*#InPad,#PanelH - 4*#InPad-#BtnH) ButtonGadget(#GdtBtnSaveGitIgnore,#InPad,GadgetY(#GdtTxtGitIgnore)+GadgetHeight(#GdtTxtGitIgnore)+#InPad,100,#BtnH,T("GdtBtnSaveGitIgnore","Save File")) ; ============================================================ ; Onglet 4 : Config ; ============================================================ AddGadgetItem(#gdtPnl, -1, T("#gdtPnl-Config","Config")) FrameGadget(#GdtFrmConfig, #InPad, #InPad, #PanelW - 2*#InPad, 170, T("GdtFrmConfig","Configuration Git")) TextGadget(#GdtLblUserName, #InPad + 10, #InPad + 35, 90, 22, "user.name") StringGadget(#GdtFieldUserName, #InPad + 110, #InPad + 33, #PanelW - 2*#InPad - 120, #BtnH, "") TextGadget(#GdtLblUserEmail, #InPad + 10, #InPad + 70, 90, 22, "user.email") StringGadget(#GdtFieldUserEmail, #InPad + 110, #InPad + 68, #PanelW - 2*#InPad - 120, #BtnH, "") TextGadget(#GdtLblScope, #InPad + 10, #InPad + 105, 90, 22, T("GdtLblScope","Portée")) ComboBoxGadget(#GdtSlctScope, #InPad + 110, #InPad + 103, 180, #BtnH) AddGadgetItem(#GdtSlctScope, -1, "Local") AddGadgetItem(#GdtSlctScope, -1, "System") AddGadgetItem(#GdtSlctScope, -1, "Global") SetGadgetState(#GdtSlctScope, 0) GetGitIdentity() ButtonGadget(#GdtBtnSaveCfg, #PanelW - #InPad - 110, #InPad + 100, 110, #BtnH, T("GdtBtnSaveCfg","Enregistrer")) ; --- Fin des onglets --- CloseGadgetList() ; ============================================================ ; Aide à droite du Panel (sans splitter) ; ============================================================ ; On garde #PanelW / #PanelH tels quels Define helpGap = 10 ; espace entre panel et aide Define helpX = #InPad + #PanelW + helpGap ; à droite du panel Define helpY = #InPad Define helpW = WindowWidth(#WinMain)-GadgetWidth(#gdtPnl)- #InPad*4 ; reste de largeur jusqu'à la marge droite Define helpH = #PanelH ; même hauteur que le panel ; (Optionnel) borne minimale si besoin If helpW < 100 : helpW = 100 : EndIf ; Soit en direct : WebViewGadget(#GdtHelp, helpX, helpY, helpW, helpH) ; — ou, si tu veux un cadre : ; FrameGadget(#GdtFrmHelp, helpX, helpY, helpW, helpH, T("GdtFrmHelp","Aide")) ; WebViewGadget(#GdtHelp, helpX + 10, helpY + 25, helpW - 20, helpH - 35) SetGadgetText(#GgtFieldRepo,main\GitCall\workdir) CreateThread(@RefreshFileList(),0) InitGadget() _SetTooltips() If FileSize(".git")=-2:main\info\isInit=#True:EndIf ;If main\info\isInit=#True ; GetRemoteUrl("origin") ; GetCommitHistory() ;;EndIf GetBranchesList() ; =================== EVENT LOOP / BOUCLE ÉVÉNEMENTS =================== Quit=#False Repeat Protected ev.i = WaitWindowEvent() Select ev Case #PB_Event_CloseWindow Quit=#True Case #PB_Event_Gadget Select EventGadget() Case #GdtPnl Debug "Click>#gdtPnl" If EventType()=#PB_EventType_Change ReadGitIgnorefile() Select GetGadgetState(#GdtPnl) EndSelect UpdateHelp(GitIgnoreHelpHTML) ;SetGadgetText(#GdtHelp, "file://" + #PB_Compiler_Home + "examples/sources/Data/WebView/webview.html") EndIf Case #GdtBtnInit Debug "Click>#GdtBtnInit" DoInit() CreateThread(@RefreshFileList(),0) Case #GdtBtnRefresh Debug "Click>#GdtBtnRefresh" CreateThread(@RefreshFileList(),0) Case #GgtFieldRepo If EventType()=#PB_EventType_LostFocus Debug "Click>#GgtFieldRepo" main\GitCall\workdir=GetGadgetText(#GgtFieldRepo) CreateThread(@RefreshFileList(),0) EndIf Case #GdtBtnBrowseRepo Debug "Click>#GdtBtnBrowseRepo" Protected path.s=PathRequester("Select Folder",main\GitCall\workdir,WindowID(#WinMain)) If path<>"" And FileSize(path)=-2 main\GitCall\workdir=path SetCurrentDirectory(main\GitCall\workdir) SetGadgetText(#GgtFieldRepo,main\GitCall\workdir) CreateThread(@RefreshFileList(),0) EndIf Case #GdtFieldRemote ;If EventType()=#PB_EventType_LostFocus ; AddRemoteRepo(GetGadgetText(#GdtFieldRemote),"origin") ; EndIf Case #GdtBtnClone Debug "Click>#GdtBtnClone" DoClone() Case #GdtBtnPush Debug "Click>#GdtBtnPush" DoPush() Case #GdtBtnPull Debug "Click>#GdtBtnPull" DoPull() Case #GdtListStatus Debug "Click>#GdtListStatus" Case #GdtBtnIgnore Debug "Click>#GdtBtnIgnore" ToggleGitIgnoreForSelection() Case #GdtBtnSaveGitIgnore Debug "Click>#GdtBtnSaveGitIgnore" Protected hf.i=CreateFile(#PB_Any,".gitignore") If hf WriteString(hf,GetGadgetText(#GdtTxtGitIgnore)) CloseFile(hf) EndIf Case #GdtBtnCommit Debug "Click>#GdtBtnCommit" DoCommit() SetGadgetText(#GdtFieldMessage,"") CreateThread(@RefreshFileList(),0) Case #GdtFieldRemote Debug "Click>#GdtFieldRemote" Case #GdtListHistory Debug "Click>#GdtListHistory" If EventType() = #PB_EventType_Change ShowSelectedCommitInfo() EndIf Case #GdtSlctBranch Debug "Click>#GdtSlctBranch" Case #GdtSlctScope Debug "Click>#GdtSlctScope" GetGitIdentity() Case #GdtBtnSaveCfg Debug "Click>#GdtBtnSaveCfg" SetGitIdentity() EndSelect InitGadget() EndSelect Until Quit=#True End EndIf EndProcedure OpenGUI() ; IDE Options = PureBasic 6.21 (Windows - x64) ; CursorPosition = 2040 ; FirstLine = 2021 ; Folding = ---------- ; Optimizer ; EnableThread ; EnableXP ; DPIAware ; CompileSourceDirectory ; EnablePurifier