commit b6c33b1f0a3098f9f5b5af518f6fcbef0bc420fc Author: Thyphoon Date: Sat Aug 23 15:11:59 2025 +0200 First commit diff --git a/main.pb b/main.pb new file mode 100644 index 0000000..e74676c --- /dev/null +++ b/main.pb @@ -0,0 +1,1758 @@ +ï»ż; ******************************************************************** +; 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 +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) + +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 + +Procedure Max(nb1, nb2) + If nb1 > nb2 + Result = nb1 + Else + Result = nb2 + EndIf + + ProcedureReturn Result +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 "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() + ; Vider la liste existante + ClearList(main\listFilesGit()) + + ; 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 filename + ; X = index status, Y = working tree status + If Len(line$) >= 3 + AddElement(main\listFilesGit()) + + + ; Extraire le status (2 premiers caractĂšres) + main\listFilesGit()\status = Left(line$, 2) + main\listFilesGit()\indexStatus = Left(line$, 1) + main\listFilesGit()\workingTreeStatus = Mid(line$, 2, 1) + + ; Extraire le nom du fichier (Ă  partir du 4Ăšme caractĂšre) + main\listFilesGit()\name = Mid(line$, 4) + + ; Obtenir la description du status + main\listFilesGit()\statusDescription = GetStatusDescription(main\listFilesGit()\status) + EndIf + EndIf + Next + + Debug "RĂ©cupĂ©ration des status Git rĂ©ussie. " + Str(ListSize(main\listFilesGit())) + " fichiers trouvĂ©s." + 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 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) + Else + MessageRequester("Git config", "Échec: " + #LF$ + main\Gitcall\errors, #PB_MessageRequester_Error) + 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 + " --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" + 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 + ;DoLsFilesByType("C") + ;DoLsFilesByType("D") + ;DoLsFilesByType("O") + ;DoLsFilesByType("I") + ;DoStatus() + + GetGitStatus() + + 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) +EndProcedure + +Procedure UpdateHelp(txt.s) + SetGadgetItemText(#GdtHelp,#PB_Web_HtmlCode,txt.s) +EndProcedure + +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 = 930 ; plus haut pour la zone d’aide + #PanelX = 15 + #PanelY = 15 + #PanelW = 920 + #PanelH = 700 + #InPad = 15 ; marge interne au Panel + #FrmGap = 10 ; espace vertical entre frames + #RowH = 26 + #BtnH = 30 + + If OpenWindow(#WinMain, 0, 0, #WinW, #WinH, "Git helpmate", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) + + PanelGadget(#gdtPnl, #PanelX, #PanelY, #PanelW, #PanelH) + + ; ============================================================ + ; Onglet 1 : DĂ©pĂŽt + ; ============================================================ + AddGadgetItem(#gdtPnl, -1, T("#gdtPnl-Repo","DĂ©pĂŽt")) + + ; ---- Cadre "DĂ©pĂŽt local" ---- + FrameGadget(#GdtFrmLocal, #InPad, #InPad, #PanelW - 2*#InPad, 100, T("GdtFrmLocal","DĂ©pĂŽt local")) + TextGadget(#GdtLblRepo, #InPad + 10, #InPad + 30, 60, 22, T("GdtLblRepo","DĂ©pĂŽt :")) + StringGadget(#GgtFieldRepo, #InPad + 75, #InPad + 28, #PanelW - 2*#InPad - 75 - 105, #RowH, repoDir$) + ButtonGadget(#GdtBtnBrowseRepo, #PanelW - #InPad - 95, #InPad + 28, 95, #RowH, T("GdtBtnBrowseRepo","Parcourir
")) + ButtonGadget(#GdtBtnInit, #InPad + 10, #InPad + 60, 105, #BtnH, T("GdtBtnInit","Init DĂ©pĂŽt")) + ButtonGadget(#GdtBtnRefresh, #InPad + 120, #InPad + 60, 105, #BtnH, T("GdtBtnRefresh","RafraĂźchir")) + + ; ---- Cadre "Distant" ---- + Define yRemote = #InPad + 100 + #FrmGap + FrameGadget(#GdtFrmRemote, #InPad, yRemote, #PanelW - 2*#InPad, 100, T("GdtFrmRemote","Distant (remote / branche)")) + TextGadget(#GdtLblRemote, #InPad + 10, yRemote + 30, 60, 22, T("GdtLblRemote","Remote :")) + StringGadget(#GdtFieldRemote, #InPad + 75, yRemote + 28, 220, #RowH, "") + TextGadget(#GdtLblBranch, #InPad + 305, yRemote + 30, 70, 22, T("GdtLblBranch","Branche :")) + ComboBoxGadget(#GdtSlctBranch, #InPad + 375, yRemote + 28, 220, #RowH, #PB_ComboBox_Editable) + ButtonGadget(#GdtBtnNewBranch, #PanelW - #InPad - 130, yRemote + 26, 130, #BtnH, T("GdtBtnNewBranch","New branch")) + ButtonGadget(#GdtBtnClone, #InPad + 10, yRemote + 60, 100, #BtnH, T("GdtBtnClone","Clone")) + ButtonGadget(#GdtBtnPull, #InPad + 120, yRemote + 60, 100, #BtnH, T("GdtBtnPull","Pull")) + ButtonGadget(#GdtBtnPush, #InPad + 230, yRemote + 60, 100, #BtnH, T("GdtBtnPush","Push")) + + ; ---- Cadre "Fichiers & modifications" ---- + Define yFiles = yRemote + 100 + #FrmGap + Define hFiles = #PanelH - yFiles - #InPad + FrameGadget(#GdtFrmFiles, #InPad, yFiles, #PanelW - 2*#InPad, hFiles, T("GdtFrmFiles","Fichiers & modifications")) + + ; Liste des fichiers — hauteur auto (pas de nouvelle variable) + Define listTop = yFiles + 25 + Define listH = hFiles - (25 + 10 + 2*#BtnH + 20) + If listH < 120 : listH = 120 : EndIf + ListIconGadget(#GdtListStatus, #InPad + 10, listTop, #PanelW - 2*#InPad - 20, listH, 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 = listTop + listH + 10 + 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, #RowH, "") + ButtonGadget(#GdtBtnCommit, #PanelW - #InPad - 100, yMsg - 2, 100, #BtnH, T("GdtBtnCommit","Commit")) + + ; ============================================================ + ; 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, #RowH, "") + TextGadget(#GdtLblUserEmail, #InPad + 10, #InPad + 70, 90, 22, "user.email") + StringGadget(#GdtFieldUserEmail, #InPad + 110, #InPad + 68, #PanelW - 2*#InPad - 120, #RowH, "") + TextGadget(#GdtLblScope, #InPad + 10, #InPad + 105, 90, 22, T("GdtLblScope","PortĂ©e")) + ComboBoxGadget(#GdtSlctScope, #InPad + 110, #InPad + 103, 180, #RowH) + 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() + + ; ============================================================ + ; Zone d’aide (plus haute grĂące Ă  #PanelH rĂ©duit) + ; ============================================================ + Define helpY = #PanelY + #PanelH + 10 + ;FrameGadget(#GdtFrmHelp, #PanelX, helpY, #PanelW, #WinH - helpY - #PanelX, T("GdtFrmHelp","Aide")) + WebViewGadget(#GdtHelp, #PanelX + 10, helpY + 25, #PanelW - 20, #WinH - helpY - #PanelX - 35) + + + + SetGadgetText(#GgtFieldRepo,main\GitCall\workdir) + CreateThread(@RefreshFileList(),0) + Debug"____" + GetBranchesList() + GetCommitHistory() + ; =================== 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 + 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 + DoInit() + CreateThread(@RefreshFileList(),0) + Case #GdtBtnRefresh + CreateThread(@RefreshFileList(),0) + Case #GgtFieldRepo + If EventType()=#PB_EventType_Change + main\GitCall\workdir=GetGadgetText(#GgtFieldRepo) + EndIf + + Case #GdtBtnBrowseRepo + Protected path.s=PathRequester("Select Folder",main\GitCall\workdir,WindowID(#WinMain)) + If path<>"" And FileSize(path)=-2 + main\GitCall\workdir=path + EndIf + Case #GdtListStatus + Case #GdtBtnIgnore + ToggleGitIgnoreForSelection() + Case #GdtBtnSaveGitIgnore + Protected hf.i=CreateFile(#PB_Any,".gitignore") + If hf + WriteString(hf,GetGadgetText(#GdtTxtGitIgnore)) + CloseFile(hf) + EndIf + + Case #GdtBtnCommit + DoCommit() + Case #GdtFieldRemote + + Case #GdtListHistory + If EventType() = #PB_EventType_Change + ShowSelectedCommitInfo() + EndIf + + Case #GdtSlctBranch + Case #GdtSlctScope + GetGitIdentity() + Case #GdtBtnSaveCfg + SetGitIdentity() + + + EndSelect + EndSelect + Until Quit=#True + End + EndIf +EndProcedure +OpenGUI() + + + + +; IDE Options = PureBasic 6.21 (Windows - x64) +; CursorPosition = 1698 +; FirstLine = 1656 +; Folding = ------ +; Optimizer +; EnableThread +; EnableXP +; DPIAware +; CompileSourceDirectory +; EnablePurifier \ No newline at end of file