; ********************************************************************
; 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$ +
~"
📁 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$ +
~" - * : correspond à n'importe quelle chaîne (sauf
/
) " + #LF$ +
~" - ** : correspond à n'importe quel nombre de répertoires
" + #LF$ +
~" - ? : correspond à un seul caractère
" + #LF$ +
~" - [abc] : correspond à a, b ou c
" + #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