commit ef0568f8462b19366f80bf1b8c88dbd3a4855500 Author: Yann LEBRUN Date: Thu Aug 14 22:04:43 2025 +0200 Version 0.1 diff --git a/PBIDE-GitTool.pb b/PBIDE-GitTool.pb new file mode 100644 index 0000000..9c45984 --- /dev/null +++ b/PBIDE-GitTool.pb @@ -0,0 +1,555 @@ +; PBIDE-GitTool.pb — Outil externe Git pour l’IDE 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 "" si fourni (via %PROJECT) + ; 2) PB_TOOL_Project -> GetPathPart() + ; 3) --repo "" 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 --status | --init | --commit "message" [--push] [--remote ] [--branch ] | --push [--remote ] [--branch ] | --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 \ No newline at end of file