Files
GitCompanion/main.pb
2025-08-24 18:32:37 +02:00

2239 lines
82 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

; ********************************************************************
; 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 = ~"<!DOCTYPE html>" + #LF$ +
~"<html lang=\"fr\">" + #LF$ +
~"<head>" + #LF$ +
~" <meta charset=\"UTF-8\">" + #LF$ +
~" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" + #LF$ +
~" <title>Aide .gitignore</title>" + #LF$ +
~" <style>" + #LF$ +
~" body {" + #LF$ +
~" font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;" + #LF$ +
~" margin: 0;" + #LF$ +
~" padding: 20px;" + #LF$ +
~" background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);" + #LF$ +
~" color: #333;" + #LF$ +
~" line-height: 1.6;" + #LF$ +
~" }" + #LF$ +
~" .container {" + #LF$ +
~" max-width: 900px;" + #LF$ +
~" margin: 0 auto;" + #LF$ +
~" background: white;" + #LF$ +
~" border-radius: 15px;" + #LF$ +
~" padding: 30px;" + #LF$ +
~" box-shadow: 0 20px 40px rgba(0,0,0,0.1);" + #LF$ +
~" }" + #LF$ +
~" h1 {" + #LF$ +
~" color: #2c3e50;" + #LF$ +
~" text-align: center;" + #LF$ +
~" margin-bottom: 30px;" + #LF$ +
~" font-size: 2.5em;" + #LF$ +
~" background: linear-gradient(45deg, #667eea, #764ba2);" + #LF$ +
~" -webkit-background-clip: text;" + #LF$ +
~" -webkit-text-fill-color: transparent;" + #LF$ +
~" background-clip: text;" + #LF$ +
~" }" + #LF$ +
~" h2 {" + #LF$ +
~" color: #34495e;" + #LF$ +
~" border-left: 4px solid #667eea;" + #LF$ +
~" padding-left: 15px;" + #LF$ +
~" margin-top: 30px;" + #LF$ +
~" font-size: 1.4em;" + #LF$ +
~" }" + #LF$ +
~" .syntax-box {" + #LF$ +
~" background: #f8f9fa;" + #LF$ +
~" border: 1px solid #e9ecef;" + #LF$ +
~" border-radius: 8px;" + #LF$ +
~" padding: 15px;" + #LF$ +
~" margin: 15px 0;" + #LF$ +
~" font-family: 'Consolas', 'Monaco', monospace;" + #LF$ +
~" font-size: 0.9em;" + #LF$ +
~" overflow-x: auto;" + #LF$ +
~" }" + #LF$ +
~" .example {" + #LF$ +
~" background: linear-gradient(135deg, #667eea20, #764ba220);" + #LF$ +
~" border-left: 4px solid #667eea;" + #LF$ +
~" padding: 15px;" + #LF$ +
~" margin: 15px 0;" + #LF$ +
~" border-radius: 0 8px 8px 0;" + #LF$ +
~" }" + #LF$ +
~" .tip {" + #LF$ +
~" background: #e8f5e8;" + #LF$ +
~" border: 1px solid #4caf50;" + #LF$ +
~" border-radius: 8px;" + #LF$ +
~" padding: 15px;" + #LF$ +
~" margin: 15px 0;" + #LF$ +
~" }" + #LF$ +
~" .warning {" + #LF$ +
~" background: #fff3e0;" + #LF$ +
~" border: 1px solid #ff9800;" + #LF$ +
~" border-radius: 8px;" + #LF$ +
~" padding: 15px;" + #LF$ +
~" margin: 15px 0;" + #LF$ +
~" }" + #LF$ +
~" code {" + #LF$ +
~" background: #f1f3f4;" + #LF$ +
~" padding: 2px 6px;" + #LF$ +
~" border-radius: 4px;" + #LF$ +
~" font-family: 'Consolas', 'Monaco', monospace;" + #LF$ +
~" color: #d73a49;" + #LF$ +
~" }" + #LF$ +
~" ul {" + #LF$ +
~" padding-left: 20px;" + #LF$ +
~" }" + #LF$ +
~" li {" + #LF$ +
~" margin: 8px 0;" + #LF$ +
~" }" + #LF$ +
~" .pattern {" + #LF$ +
~" display: inline-block;" + #LF$ +
~" background: #667eea;" + #LF$ +
~" color: white;" + #LF$ +
~" padding: 3px 8px;" + #LF$ +
~" border-radius: 4px;" + #LF$ +
~" font-family: 'Consolas', 'Monaco', monospace;" + #LF$ +
~" font-size: 0.9em;" + #LF$ +
~" margin: 2px;" + #LF$ +
~" }" + #LF$ +
~" </style>" + #LF$ +
~"</head>" + #LF$ +
~"<body>" + #LF$ +
~" <div class=\"container\">" + #LF$ +
~" <h1>📁 Guide .gitignore</h1>" + #LF$ +
~" " + #LF$ +
~" <p>Le fichier <code>.gitignore</code> permet de spécifier quels fichiers et dossiers Git doit ignorer lors du versioning.</p>" + #LF$ +
~" " + #LF$ +
~" <h2>🎯 Syntaxe de base</h2>" + #LF$ +
~" " + #LF$ +
~" <div class=\"example\">" + #LF$ +
~" <strong>Ignorer des fichiers spécifiques :</strong>" + #LF$ +
~" <div class=\"syntax-box\">" + #LF$ +
~"config.txt # Ignore le fichier config.txt<br>" + #LF$ +
~"*.log # Ignore tous les fichiers .log<br>" + #LF$ +
~"temp/ # Ignore le dossier temp et tout son contenu" + #LF$ +
~" </div>" + #LF$ +
~" </div>" + #LF$ +
~" " + #LF$ +
~" <h2>🔧 Patterns et wildcards</h2>" + #LF$ +
~" " + #LF$ +
~" <ul>" + #LF$ +
~" <li><span class=\"pattern\">*</span> : correspond à n'importe quelle chaîne (sauf <code>/</code>)</li>" + #LF$ +
~" <li><span class=\"pattern\">**</span> : correspond à n'importe quel nombre de répertoires</li>" + #LF$ +
~" <li><span class=\"pattern\">?</span> : correspond à un seul caractère</li>" + #LF$ +
~" <li><span class=\"pattern\">[abc]</span> : correspond à a, b ou c</li>" + #LF$ +
~" </ul>" + #LF$ +
~" " + #LF$ +
~" <h2>🚫 Négation avec !</h2>" + #LF$ +
~" " + #LF$ +
~" <div class=\"syntax-box\">" + #LF$ +
~"*.log<br>" + #LF$ +
~"!important.log # Inclut ce fichier malgré la règle précédente" + #LF$ +
~" </div>" + #LF$ +
~" " + #LF$ +
~" <h2>📍 Chemins</h2>" + #LF$ +
~" " + #LF$ +
~" <div class=\"example\">" + #LF$ +
~" <div class=\"syntax-box\">" + #LF$ +
~"/build # Ignore build à la racine uniquement<br>" + #LF$ +
~"build/ # Ignore tous les dossiers build<br>" + #LF$ +
~"docs/**/*.pdf # Ignore tous les PDF dans docs et ses sous-dossiers" + #LF$ +
~" </div>" + #LF$ +
~" </div>" + #LF$ +
~" " + #LF$ +
~" <h2>💡 Exemples courants</h2>" + #LF$ +
~" " + #LF$ +
~" <div class=\"syntax-box\">" + #LF$ +
~"# Fichiers de build<br>" + #LF$ +
~"dist/<br>" + #LF$ +
~"build/<br>" + #LF$ +
~"<br>" + #LF$ +
~"# Dépendances<br>" + #LF$ +
~"node_modules/<br>" + #LF$ +
~"vendor/<br>" + #LF$ +
~"<br>" + #LF$ +
~"# Configuration locale<br>" + #LF$ +
~".env<br>" + #LF$ +
~"config.local.json<br>" + #LF$ +
~"<br>" + #LF$ +
~"# Logs et caches<br>" + #LF$ +
~"*.log<br>" + #LF$ +
~".cache/<br>" + #LF$ +
~"<br>" + #LF$ +
~"# Fichiers d'IDE<br>" + #LF$ +
~".vscode/<br>" + #LF$ +
~".idea/<br>" + #LF$ +
~"<br>" + #LF$ +
~"# Fichiers système<br>" + #LF$ +
~".DS_Store<br>" + #LF$ +
~"Thumbs.db" + #LF$ +
~" </div>" + #LF$ +
~" " + #LF$ +
~" <div class=\"tip\">" + #LF$ +
~" <strong>💡 Conseil :</strong> Utilisez des commentaires avec <code>#</code> pour documenter vos règles." + #LF$ +
~" </div>" + #LF$ +
~" " + #LF$ +
~" <div class=\"warning\">" + #LF$ +
~" <strong>⚠️ Important :</strong> Le .gitignore ne s'applique qu'aux fichiers non-trackés. Pour ignorer des fichiers déjà versionnés, utilisez <code>git rm --cached &lt;fichier&gt;</code>." + #LF$ +
~" </div>" + #LF$ +
~" " + #LF$ +
~" <h2>🔍 Tester vos règles</h2>" + #LF$ +
~" " + #LF$ +
~" <div class=\"syntax-box\">" + #LF$ +
~"git check-ignore -v &lt;fichier&gt; # Vérifie si un fichier est ignoré<br>" + #LF$ +
~"git status --ignored # Affiche les fichiers ignorés" + #LF$ +
~" </div>" + #LF$ +
~" " + #LF$ +
~" </div>" + #LF$ +
~"</body>" + #LF$ +
~"</html>"
; =============================================================================
;-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
; ---- Helpers ---------------------------------------------------------------
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<score> path<tab>origPath
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␠<filename>
; 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 dun 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 dun enregistrement
start = FindString(out$, rs$, pos)
If start = 0 : Break : EndIf
start + 1
; chercher le prochain RS (fin de lenregistrement)
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
; Sassure 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 nest 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
; -- Sassurer que .gitignore existe
If FileSize(gitignore$) = -1
create = MessageRequester("Git .gitignore", "Le fichier .gitignore nexiste 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$+"” nest pas ignoré."+#LF$+"Lajouter à .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 lOS
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 litem 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()
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 lURL 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 dutilisateur 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 daide
#PanelW = 920
#PanelH = 500
#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, #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
Protected PanelW.l=900
; ---- 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 dinfo 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()
; ============================================================
; 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()
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 = 2191
; FirstLine = 2180
; Folding = ----------
; Optimizer
; EnableThread
; EnableXP
; DPIAware
; CompileSourceDirectory
; EnablePurifier