; 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