Version 0.1

This commit is contained in:
Yann LEBRUN
2025-08-14 22:04:43 +02:00
commit ef0568f846

555
PBIDE-GitTool.pb Normal file
View File

@@ -0,0 +1,555 @@
; PBIDE-GitTool.pb — Outil externe Git pour lIDE PureBasic
; Gère init, status, add+commit, push/pull avec paramètres simples
; PureBasic 6.x, multiplateforme
; ------------------------------------------------------------
EnableExplicit
; ====== Debug global ======
#EnableDebug = #True
; ====== Séparateur de chemin (portable) ======
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
#PathSep$ = "\"
; Chemin Git forcé pour Windows (proposé) :
#GitExe$ = "C:\Program Files\Git\cmd\git.exe"
CompilerElse
#PathSep$ = "/"
; Sur macOS/Linux on garde 'git' dans le PATH
#GitExe$ = "git"
CompilerEndIf
; ====== Structure d'appel Git ======
Structure GitCall
args.s ; ex: "status --porcelain"
workdir.s ; répertoire de travail
output.s ; stdout complet
errors.s ; stderr complet
exitcode.i ; code retour
EndStructure
; ====== Déclarations ======
Declare.s TrimNewlines(text$)
Declare.i RunGit(*call.GitCall)
Declare.s DetectRepoRoot(startDir$)
Declare.i LoadPrefs(prefsPath$, remote$, branch$)
Declare.i SavePrefs(prefsPath$, remote$, branch$)
Declare.i EnsureGitAvailable()
Declare.i OpenGUI(initialDir$, prefsPath$)
Declare.s DirFromArgOrFallback()
Declare.i DoInitRepo(repoDir$)
Declare.i DoStatus(repoDir$, out$)
Declare.i DoCommit(repoDir$, message$, doPush.i, remote$, branch$)
Declare.i DoPush(repoDir$, remote$, branch$)
Declare.i DoPull(repoDir$, remote$, branch$)
Declare.i ListBranches(repoDir$, List branchesList.s())
; ====== Utils ======
Procedure.s TrimNewlines(text$)
; Supprime \r et \n en fin de chaîne
Protected s$ = text$
While Right(s$, 1) = #LF$ Or Right(s$, 1) = #CR$
s$ = Left(s$, Len(s$)-1)
Wend
ProcedureReturn s$
EndProcedure
; Exécute git avec une structure d'appel passée par pointeur
; Remplit *call\output, *call\errors, *call\exitcode
Procedure.i RunGit(*call.GitCall)
Protected prg.i, line$
Protected out$, err$
If #EnableDebug
Debug "[RunGit] exe=" + #GitExe$ + " args=" + *call\args + " wd=" + *call\workdir
EndIf
prg = RunProgram(#GitExe$, *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 '" + #GitExe$ + "'."
*call\exitcode = -1
ProcedureReturn *call\exitcode
EndIf
While ProgramRunning(prg)
While AvailableProgramOutput(prg)
line$ = ReadProgramString(prg)
If line$ <> ""
out$ + line$ + #LF$
EndIf
Wend
; ReadProgramError() renvoie "" s'il n'y a rien (non bloquant)
line$ = ReadProgramError(prg)
If line$ <> ""
err$ + line$ + #LF$
EndIf
Delay(5)
Wend
; Vidange finale stdout
While AvailableProgramOutput(prg)
line$ = ReadProgramString(prg)
If line$ <> ""
out$ + line$ + #LF$
EndIf
Wend
; Vidange finale stderr (jusqu'à chaîne vide)
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 "[RunGit] code=" + Str(*call\exitcode)
If *call\output <> "" : Debug *call\output : EndIf
If *call\errors <> "" : Debug "[stderr] " + *call\errors : EndIf
EndIf
ProcedureReturn *call\exitcode
EndProcedure
Procedure.s DetectRepoRoot(startDir$)
; Renvoie le toplevel git si présent, sinon startDir$
Protected gc.GitCall
gc\args = "rev-parse --show-toplevel"
gc\workdir = startDir$
If RunGit(@gc) = 0
ProcedureReturn TrimNewlines(gc\output)
EndIf
ProcedureReturn startDir$
EndProcedure
Procedure.i LoadPrefs(prefsPath$, remote$, branch$)
If OpenPreferences(prefsPath$)
PreferenceGroup("git")
remote$ = ReadPreferenceString("remote", "origin")
branch$ = ReadPreferenceString("branch", "main")
ClosePreferences()
If #EnableDebug
Debug "[LoadPrefs] " + prefsPath$ + " remote=" + remote$ + " branch=" + branch$
EndIf
ProcedureReturn 1
EndIf
remote$ = "origin"
branch$ = "main"
ProcedureReturn 0
EndProcedure
Procedure.i SavePrefs(prefsPath$, remote$, branch$)
If CreatePreferences(prefsPath$)
PreferenceGroup("git")
WritePreferenceString("remote", remote$)
WritePreferenceString("branch", branch$)
ClosePreferences()
If #EnableDebug
Debug "[SavePrefs] " + prefsPath$ + " remote=" + remote$ + " branch=" + branch$
EndIf
ProcedureReturn 1
EndIf
ProcedureReturn 0
EndProcedure
Procedure.i EnsureGitAvailable()
Protected gc.GitCall
gc\args = "--version"
If RunGit(@gc) = 0 And FindString(LCase(gc\output), "git version", 1)
ProcedureReturn 1
EndIf
MessageRequester("PBIDE-GitTool", "Git n'est pas détecté à l'emplacement prévu : " + #GitExe$, #PB_MessageRequester_Warning)
ProcedureReturn 0
EndProcedure
Procedure.s DirFromArgOrFallback()
; 1) --project "<dir>" si fourni (via %PROJECT)
; 2) PB_TOOL_Project -> GetPathPart()
; 3) --repo "<dir>" ou 1er argument libre
; 4) dossier de l'exécutable
Protected n.i = CountProgramParameters()
Protected gotNextProject.i = 0
Protected gotNextRepo.i = 0
Protected dir$ = ""
Protected i.i, p$
For i = 0 To n - 1
p$ = ProgramParameter(i)
If gotNextProject
dir$ = p$
gotNextProject = 0
ElseIf gotNextRepo
dir$ = p$
gotNextRepo = 0
ElseIf LCase(p$) = "--project"
gotNextProject = 1
ElseIf LCase(p$) = "--repo"
gotNextRepo = 1
ElseIf Left(p$, 1) <> "-"
If dir$ = "" : dir$ = p$ : EndIf
EndIf
Next
If dir$ <> ""
If #EnableDebug : Debug "[DirFromArgOrFallback] from args: " + dir$ : EndIf
ProcedureReturn dir$
EndIf
Protected projFile$ = GetEnvironmentVariable("PB_TOOL_Project")
If projFile$ <> ""
Protected projDir$ = GetPathPart(projFile$)
If projDir$ <> ""
If #EnableDebug : Debug "[DirFromArgOrFallback] from PB_TOOL_Project: " + projDir$ : EndIf
ProcedureReturn projDir$
EndIf
EndIf
dir$ = GetPathPart(ProgramFilename())
If #EnableDebug : Debug "[DirFromArgOrFallback] fallback exe dir: " + dir$ : EndIf
ProcedureReturn dir$
EndProcedure
; ====== Opérations Git de base ======
Procedure.i DoInitRepo(repoDir$)
Protected gc.GitCall
gc\args = "init"
gc\workdir = repoDir$
If RunGit(@gc) = 0
MessageRequester("Git init", "Répertoire initialisé comme dépôt Git." + #LF$ + TrimNewlines(gc\output), #PB_MessageRequester_Info)
ProcedureReturn 1
EndIf
MessageRequester("Git init", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error)
ProcedureReturn 0
EndProcedure
Procedure.i DoStatus(repoDir$, out$)
Protected gc.GitCall
gc\args = "status --porcelain"
gc\workdir = repoDir$
If RunGit(@gc) <> 0
out$ = ""
MessageRequester("Git status", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error)
ProcedureReturn 0
EndIf
out$ = gc\output
ProcedureReturn 1
EndProcedure
Procedure.i DoCommit(repoDir$, message$, doPush.i, remote$, branch$)
Protected gc.GitCall
; add -A
gc\args = "add -A"
gc\workdir = repoDir$
If RunGit(@gc) <> 0
MessageRequester("Git add", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error)
ProcedureReturn 0
EndIf
; commit
gc\args = "commit -m " + Chr(34) + message$ + Chr(34)
If RunGit(@gc) <> 0
MessageRequester("Git commit", "Échec ou rien à valider: " + #LF$ + TrimNewlines(gc\errors) + #LF$ + TrimNewlines(gc\output), #PB_MessageRequester_Warning)
If doPush = 0
ProcedureReturn 0
EndIf
Else
MessageRequester("Git commit", "OK:" + #LF$ + TrimNewlines(gc\output), #PB_MessageRequester_Info)
EndIf
If doPush
ProcedureReturn DoPush(repoDir$, remote$, branch$)
EndIf
ProcedureReturn 1
EndProcedure
Procedure.i DoPush(repoDir$, remote$, branch$)
Protected gc.GitCall
gc\args = "push " + remote$ + " " + branch$
gc\workdir = repoDir$
If RunGit(@gc) = 0
MessageRequester("Git push", "OK:" + #LF$ + TrimNewlines(gc\output), #PB_MessageRequester_Info)
ProcedureReturn 1
EndIf
MessageRequester("Git push", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error)
ProcedureReturn 0
EndProcedure
Procedure.i DoPull(repoDir$, remote$, branch$)
Protected gc.GitCall
gc\args = "pull " + remote$ + " " + branch$
gc\workdir = repoDir$
If RunGit(@gc) = 0
MessageRequester("Git pull", "OK:" + #LF$ + TrimNewlines(gc\output), #PB_MessageRequester_Info)
ProcedureReturn 1
EndIf
MessageRequester("Git pull", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error)
ProcedureReturn 0
EndProcedure
Procedure.i ListBranches(repoDir$, List branchesList.s())
; Remplit la liste 'branchesList()' avec les noms de branches
ClearList(branchesList())
Protected gc.GitCall
gc\args = "branch --list --format='%(refname:short)'"
gc\workdir = repoDir$
If RunGit(@gc) <> 0
AddElement(branchesList()) : branchesList() = "main"
AddElement(branchesList()) : branchesList() = "master"
ProcedureReturn 0
EndIf
Protected out$ = ReplaceString(gc\output, "'", "")
Protected i.i, n.i = CountString(out$, #LF$) + 1
For i = 1 To n
Protected b$ = Trim(StringField(out$, i, #LF$))
If b$ <> ""
AddElement(branchesList())
branchesList() = b$
EndIf
Next i
ProcedureReturn 1
EndProcedure
; ====== Interface simple ======
; Gadgets IDs (sans underscores)
#GWindow = 1
#GLabelRepo = 10
#GStringRepo = 11
#GButtonBrowse = 12
#GListStatus = 13
#GRefresh = 14
#GLabelMsg = 15
#GStringMsg = 16
#GCheckPush = 17
#GLabelRemote = 18
#GStringRemote = 19
#GLabelBranch = 20
#GComboBranch = 21
#GSavePrefs = 22
#GInit = 23
#GCommit = 24
#GPull = 25
#GPush = 26
Procedure.i OpenGUI(initialDir$, prefsPath$)
Protected repoDir$ = DetectRepoRoot(initialDir$)
Protected remote$ = "", branch$ = ""
LoadPrefs(prefsPath$, remote$, branch$)
If OpenWindow(#GWindow, 0, 0, 680, 520, "PBIDE-GitTool — Git simplifié pour PureBasic", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
TextGadget(#GLabelRepo, 10, 12, 60, 22, "Dépôt :")
StringGadget(#GStringRepo, 80, 10, 480, 24, repoDir$)
ButtonGadget(#GButtonBrowse, 570, 10, 100, 24, "Parcourir…")
ListViewGadget(#GListStatus, 10, 46, 660, 250)
ButtonGadget(#GRefresh, 10, 302, 90, 26, "Rafraîchir")
ButtonGadget(#GInit, 110, 302, 90, 26, "Init repo")
TextGadget(#GLabelMsg, 10, 342, 100, 22, "Message :")
StringGadget(#GStringMsg, 110, 340, 560, 24, "")
CheckBoxGadget(#GCheckPush, 10, 372, 120, 22, "Pousser après")
SetGadgetState(#GCheckPush, #True)
TextGadget(#GLabelRemote, 150, 372, 60, 22, "Remote :")
StringGadget(#GStringRemote, 210, 370, 120, 24, remote$)
TextGadget(#GLabelBranch, 340, 372, 60, 22, "Branche :")
ComboBoxGadget(#GComboBranch, 400, 370, 150, 24)
ButtonGadget(#GSavePrefs, 560, 370, 110, 24, "Sauver défauts")
ButtonGadget(#GCommit, 10, 410, 120, 30, "Add + Commit")
ButtonGadget(#GPush, 140, 410, 120, 30, "Push")
ButtonGadget(#GPull, 270, 410, 120, 30, "Pull")
; Remplir branches via la LISTE
NewList branchItems.s()
ListBranches(repoDir$, branchItems())
ClearGadgetItems(#GComboBranch)
ForEach branchItems()
AddGadgetItem(#GComboBranch, -1, branchItems())
Next
If branch$ <> ""
SetGadgetText(#GComboBranch, branch$)
EndIf
; Status initial
Protected stat$, line$, n.i, i.i
If DoStatus(repoDir$, stat$)
ClearGadgetItems(#GListStatus)
n = CountString(stat$, #LF$) + 1
For i = 1 To n
line$ = StringField(stat$, i, #LF$)
If Trim(line$) <> ""
AddGadgetItem(#GListStatus, -1, line$)
EndIf
Next i
EndIf
Repeat
Protected ev.i = WaitWindowEvent()
Select ev
Case #PB_Event_Gadget
Select EventGadget()
Case #GButtonBrowse
Protected newDir$ = PathRequester("Choisir le répertoire du dépôt", repoDir$)
If newDir$ <> ""
repoDir$ = newDir$
SetGadgetText(#GStringRepo, repoDir$)
; Recharger la liste des branches
ClearList(branchItems())
ListBranches(repoDir$, branchItems())
ClearGadgetItems(#GComboBranch)
ForEach branchItems()
AddGadgetItem(#GComboBranch, -1, branchItems())
Next
EndIf
Case #GRefresh
repoDir$ = GetGadgetText(#GStringRepo)
If DoStatus(repoDir$, stat$)
ClearGadgetItems(#GListStatus)
n = CountString(stat$, #LF$) + 1
For i = 1 To n
line$ = StringField(stat$, i, #LF$)
If Trim(line$) <> ""
AddGadgetItem(#GListStatus, -1, line$)
EndIf
Next i
EndIf
Case #GInit
repoDir$ = GetGadgetText(#GStringRepo)
DoInitRepo(repoDir$)
Case #GSavePrefs
remote$ = GetGadgetText(#GStringRemote)
branch$ = GetGadgetText(#GComboBranch)
If SavePrefs(prefsPath$, remote$, branch$)
MessageRequester("Préférences", "Valeurs par défaut enregistrées.", #PB_MessageRequester_Info)
Else
MessageRequester("Préférences", "Échec d'enregistrement.", #PB_MessageRequester_Error)
EndIf
Case #GCommit
repoDir$ = GetGadgetText(#GStringRepo)
remote$ = GetGadgetText(#GStringRemote)
branch$ = GetGadgetText(#GComboBranch)
Protected msg$ = GetGadgetText(#GStringMsg)
If Trim(msg$) = ""
MessageRequester("Commit", "Merci de saisir un message.", #PB_MessageRequester_Warning)
Else
DoCommit(repoDir$, msg$, GetGadgetState(#GCheckPush), remote$, branch$)
EndIf
Case #GPush
repoDir$ = GetGadgetText(#GStringRepo)
remote$ = GetGadgetText(#GStringRemote)
branch$ = GetGadgetText(#GComboBranch)
DoPush(repoDir$, remote$, branch$)
Case #GPull
repoDir$ = GetGadgetText(#GStringRepo)
remote$ = GetGadgetText(#GStringRemote)
branch$ = GetGadgetText(#GComboBranch)
DoPull(repoDir$, remote$, branch$)
EndSelect
EndSelect
Until ev = #PB_Event_CloseWindow
ProcedureReturn 1
EndIf
ProcedureReturn 0
EndProcedure
; ====== Entrée ======
If EnsureGitAvailable() = 0
End
EndIf
; Emplacement pour les prefs (dossier utilisateur PBIDE-GitTool)
Define baseDoc$ = GetUserDirectory(#PB_Directory_Documents)
If Right(baseDoc$, 1) <> #PathSep$
baseDoc$ + #PathSep$
EndIf
Define prefsDir$ = baseDoc$ + "PBIDE-GitTool" + #PathSep$
If FileSize(prefsDir$) <> -2
CreateDirectory(prefsDir$)
EndIf
Define prefsPath$ = prefsDir$ + "settings.prefs"
; Mode CLI (optionnel):
; --repo <dir> --status | --init | --commit "message" [--push] [--remote <r>] [--branch <b>] | --push [--remote <r>] [--branch <b>] | --pull [...]
Define argCount.i = CountProgramParameters()
Define useGui.i = 1
Define repoArg$ = "", msgArg$ = "", remoteArg$ = "", branchArg$ = ""
Define wantStatus.i = 0, wantInit.i = 0, wantCommit.i = 0, wantPush.i = 0, wantPull.i = 0
Define w.i
For w = 0 To argCount - 1
Define a$ = ProgramParameter(w)
Select LCase(a$)
Case "--repo" : If w + 1 < argCount : repoArg$ = ProgramParameter(w + 1) : EndIf
Case "--status" : wantStatus = 1 : useGui = 0
Case "--init" : wantInit = 1 : useGui = 0
Case "--commit" : wantCommit = 1 : useGui = 0 : If w + 1 < argCount : msgArg$ = ProgramParameter(w + 1) : EndIf
Case "--push" : wantPush = 1 : useGui = 0
Case "--pull" : wantPull = 1 : useGui = 0
Case "--remote" : If w + 1 < argCount : remoteArg$ = ProgramParameter(w + 1) : EndIf
Case "--branch" : If w + 1 < argCount : branchArg$ = ProgramParameter(w + 1) : EndIf
EndSelect
Next w
If useGui
Define dir$ = repoArg$
If dir$ = "" : dir$ = DirFromArgOrFallback() : EndIf
OpenGUI(dir$, prefsPath$)
Else
; Exécution non interactive
Define remote$ = "", branch$ = ""
LoadPrefs(prefsPath$, remote$, branch$)
If remoteArg$ <> "" : remote$ = remoteArg$ : EndIf
If branchArg$ <> "" : branch$ = branchArg$ : EndIf
Define baseDir$ = repoArg$
If baseDir$ = "" : baseDir$ = DirFromArgOrFallback() : EndIf
baseDir$ = DetectRepoRoot(baseDir$)
If wantStatus
Define st$, ok.i = DoStatus(baseDir$, st$)
If ok : PrintN(st$) : EndIf
ElseIf wantInit
DoInitRepo(baseDir$)
ElseIf wantCommit
If msgArg$ = "" : msgArg$ = "update" : EndIf
DoCommit(baseDir$, msgArg$, wantPush, remote$, branch$)
ElseIf wantPush
DoPush(baseDir$, remote$, branch$)
ElseIf wantPull
DoPull(baseDir$, remote$, branch$)
EndIf
EndIf
; IDE Options = PureBasic 6.21 (Windows - x64)
; CursorPosition = 32
; Folding = ---
; EnableXP
; DPIAware
; Executable = ..\PBIDE-GitTool.exe