; ********************************************************************
; 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
#GdtLblRemoteStatus ; Label pour "Status :"
#GdtTxtRemoteStatus ; Texte du status (À jour, 3 en retard, etc.)
#GdtLblLastFetch ; Label pour "Dernière sync :"
#GdtTxtLastFetch ; Texte de la dernière synchronisation
#GdtLblAction ; Label pour "Action :"
#GdtTxtAction ; Texte de l'action recommandée
#GdtBtnCheckRemote
; 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 pour stocker les infos remote
Structure RemoteStatusInfo
hasRemote.b ; Remote configuré
isUpToDate.b ; À jour ou pas
ahead.i ; Commits en avance
behind.i ; Commits en retard
lastFetch.s ; Timestamp du dernier fetch
remoteUrl.s ; URL du remote
remoteBranch.s ; Branche remote
localBranch.s ; Branche locale
status.s ; Description textuelle du statut
needsAction.s ; Action recommandée
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
remoteStatus.RemoteStatusInfo ; Variable globale pour stocker le status
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
; =============================================================================
; Fonction pour récupérer le status remote
; =============================================================================
Procedure.i GetRemoteStatusInfo()
; Récupère les informations de status du remote
; Met à jour la structure globale remoteStatus
; Retourne : 1=succès, 0=échec
; Reset de la structure
main\remoteStatus\hasRemote = #False
main\remoteStatus\isUpToDate = #False
main\remoteStatus\ahead = 0
main\remoteStatus\behind = 0
main\remoteStatus\status = "Vérification..."
main\remoteStatus\needsAction = ""
; Vérifier que Git est dispo et repo initialisé
If Not main\info\isGit Or Not main\info\isInit
main\remoteStatus\status = "Repo non initialisé"
ProcedureReturn 0
EndIf
; Récupérer la branche courante
main\GitCall\args = "branch --show-current"
If RunExe(@main\GitCall) = 0
main\remoteStatus\localBranch = _SupTrim(main\GitCall\output)
Else
main\remoteStatus\localBranch = "unknown"
EndIf
; Vérifier qu'il y a un remote
main\GitCall\args = "remote get-url origin"
If RunExe(@main\GitCall) <> 0
main\remoteStatus\status = "Aucun remote configuré"
ProcedureReturn 0
EndIf
main\remoteStatus\hasRemote = #True
main\remoteStatus\remoteUrl = _SupTrim(main\GitCall\output)
main\remoteStatus\remoteBranch = "origin/" + main\remoteStatus\localBranch
; Fetch silencieux pour avoir les dernières infos
main\GitCall\args = "fetch origin --quiet"
If RunExe(@main\GitCall) <> 0
main\remoteStatus\status = "Erreur réseau"
main\remoteStatus\needsAction = "Vérifiez la connexion"
ProcedureReturn 0
EndIf
; Timestamp du fetch
main\remoteStatus\lastFetch = FormatDate("%dd/%mm/%yyyy %hh:%ii", Date())
; Vérifier si la branche remote existe
main\GitCall\args = "rev-parse --verify " + main\remoteStatus\remoteBranch
If RunExe(@main\GitCall) <> 0
main\remoteStatus\status = "Branche remote inexistante"
main\remoteStatus\needsAction = "Push pour créer"
ProcedureReturn 1
EndIf
; Comparer local vs remote
main\GitCall\args = "rev-list --left-right --count " + main\remoteStatus\localBranch + "..." + main\remoteStatus\remoteBranch
If RunExe(@main\GitCall) = 0
Protected counts.s = _SupTrim(main\GitCall\output)
main\remoteStatus\ahead = Val(StringField(counts, 1, #TAB$))
main\remoteStatus\behind = Val(StringField(counts, 2, #TAB$))
; Construire le status et les actions
If main\remoteStatus\ahead = 0 And main\remoteStatus\behind = 0
main\remoteStatus\isUpToDate = #True
main\remoteStatus\status = "À jour"
main\remoteStatus\needsAction = ""
ElseIf main\remoteStatus\ahead > 0 And main\remoteStatus\behind = 0
main\remoteStatus\status = Str(main\remoteStatus\ahead) + " en avance"
main\remoteStatus\needsAction = "Push recommandé"
ElseIf main\remoteStatus\ahead = 0 And main\remoteStatus\behind > 0
main\remoteStatus\status = Str(main\remoteStatus\behind) + " en retard"
main\remoteStatus\needsAction = "Pull nécessaire"
Else
main\remoteStatus\status = Str(main\remoteStatus\ahead) + " en avance, " + Str(main\remoteStatus\behind) + " en retard"
main\remoteStatus\needsAction = "Branches divergées"
EndIf
Else
main\remoteStatus\status = "Erreur comparaison"
ProcedureReturn 0
EndIf
ProcedureReturn 1
EndProcedure
; =============================================================================
; Fonction pour mettre à jour l'affichage dans la GUI
; =============================================================================
Procedure UpdateRemoteStatusDisplay()
; Met à jour les gadgets d'affichage du status
If main\remoteStatus\hasRemote
; Couleur selon le statut
Protected statusText.s = main\remoteStatus\status
Protected color.i = RGB(60, 60, 60) ; Gris par défaut
If main\remoteStatus\isUpToDate
color = RGB(0, 128, 0) ; Vert pour "à jour"
ElseIf main\remoteStatus\behind > 0
color = RGB(255, 140, 0) ; Orange pour "en retard"
ElseIf main\remoteStatus\ahead > 0
color = RGB(0, 100, 200) ; Bleu pour "en avance"
EndIf
SetGadgetText(#GdtTxtRemoteStatus, statusText)
; Note: SetGadgetColor() nécessite que le gadget supporte les couleurs
; Action recommandée dans un gadget séparé
If main\remoteStatus\needsAction <> ""
SetGadgetText(#GdtTxtAction, main\remoteStatus\needsAction)
Else
SetGadgetText(#GdtTxtAction, "Aucune action nécessaire")
EndIf
; Dernière synchronisation
If main\remoteStatus\lastFetch <> ""
SetGadgetText(#GdtTxtLastFetch, main\remoteStatus\lastFetch)
Else
SetGadgetText(#GdtTxtLastFetch, "Jamais")
EndIf
Else
SetGadgetText(#GdtTxtRemoteStatus, "Pas de remote")
SetGadgetText(#GdtTxtAction, "-")
SetGadgetText(#GdtTxtLastFetch, "-")
EndIf
EndProcedure
Procedure CheckRemoteThread(null)
; Thread pour vérifier le status remote en arrière-plan
GetRemoteStatusInfo()
UpdateRemoteStatusDisplay()
EndProcedure
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