; PBIDE-GitTool.pb — Outil externe Git pour l’IDE PureBasic ; - Init, Status, Add+Commit (sélectif), Push/Pull ; - UI : liste avec cases à cocher + actions Inclure/Exclure/Tout ; - Fenêtre "Avancé…" : switch/restore de branche ; - Assistant d’installation IDE si lancé sans paramètre ; Contraintes respectées : pas d'underscore dans nos identifiants, ; Protected uniquement dans les procédures, pas de IIf, déclarations = implémentations, ; comparaisons uniquement dans If/While/Until/For, etc. EnableExplicit #EnableDebug = #True ; ====== Séparateur de chemin (portable) ====== CompilerIf #PB_Compiler_OS = #PB_OS_Windows #PathSep$ = "\" ; Chemin Git forcé pour Windows : #GitExe$ = "C:\Program Files\Git\cmd\git.exe" CompilerElse #PathSep$ = "/" ; Sur macOS/Linux on garde 'git' dans le PATH : #GitExe$ = "git" CompilerEndIf ; ====== IDs UI (placer ce bloc AVANT toutes les procédures) ====== ; Fenêtre principale #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 #GInclude = 27 #GExclude = 28 #GIncludeAll = 29 #GExcludeAll = 30 #GAdvanced = 31 ; Fenêtre avancée (branches) #WAdv = 200 #GAdvLabel = 210 #GAdvCombo = 211 #GAdvSwitch = 212 #GAdvRestore = 213 #GAdvClose = 214 ; Assistant d’installation #WInstall = 100 #GLabelIde = 110 #GStringIde = 111 #GButtonIde = 112 #GLabelTools = 113 #GStringTools = 114 #GButtonTools = 115 #GLabelTheme = 116 #GStringTheme = 117 #GButtonTheme = 118 #GInstallGo = 119 #GInstallCancel = 120 #GInstallNote = 121 ; --- Nouveaux gadgets --- #GDiff = 32 #GMakeIgnore = 33 #GConfig = 34 #GGuide = 35 ; --- Fenêtre Diff --- #WDiff = 300 #GDiffText = 301 #GDiffClose = 302 ; Fenêtre de sortie Git #WOut = 400 #GOutText = 401 #GOutCopy = 402 #GOutClose = 403 ; ====== Structures ====== Structure GitCall args.s workdir.s output.s errors.s exitcode.i EndStructure Structure FileRow stat.s ; 2 lettres porcelain (" M", "A ", "??", etc.) file.s ; chemin relatif include.i ; 1=coché (inclus), 0=exclu 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()) Declare.i LoadStatusRows(repoDir$, List rows.FileRow()) Declare.i FillStatusList(List rows.FileRow()) Declare.i ToggleIncludeAt(index.i, List rows.FileRow()) Declare.i CollectIncludedFiles(List rows.FileRow(), List files.s()) Declare.i DoCommitSelected(repoDir$, message$, doPush.i, remote$, branch$, List files.s()) Declare.i OpenAdvancedWindow(repoDir$) Declare.i SwitchToBranch(repoDir$, branch$) Declare.i RestoreFromBranch(repoDir$, branch$) Declare.i InstallPBGitInIDE(ideExe$, toolsPrefs$, themeZip$) Declare.i OpenInstallWizard() Declare.s PorcelainToLabel(code$) Declare.i OpenDiffWindow(repoDir$, List rows.FileRow()) Declare.i ConfigIdentityWizard(repoDir$) Declare.i MakeDefaultGitignore(repoDir$) Declare.i UpdateGuide(repoDir$, List rows.FileRow(), branch$, remote$) Declare.s GetLocalConfig(repoDir$, key$) Declare.i IsGitRepo(dir$) Declare.i UpdateGuide(repoDir$, List rows.FileRow(), branch$, remote$) ; ====== Utils ====== Procedure.s TrimNewlines(text$) Protected s$ = text$ While Right(s$, 1) = #LF$ Or Right(s$, 1) = #CR$ s$ = Left(s$, Len(s$)-1) Wend ProcedureReturn s$ EndProcedure ; Détecte si 'remote$' ressemble à une URL (http(s):// ou git@…) Procedure.i IsUrlRemote(remote$) If FindString(remote$, "://", 1) Or FindString(remote$, "@", 1) ProcedureReturn 1 EndIf ProcedureReturn 0 EndProcedure ; Récupère le nom de branche courante (HEAD → renvoie "" si détachée) Procedure.s GetCurrentBranch(repoDir$) Protected gc.GitCall gc\workdir = repoDir$ gc\args = "rev-parse --abbrev-ref HEAD" If RunGit(@gc) = 0 Protected b$ = TrimNewlines(gc\output) If b$ <> "" And LCase(b$) <> "head" ProcedureReturn b$ EndIf EndIf ProcedureReturn "" EndProcedure ; Teste si un remote nommé existe Procedure.i RemoteExists(repoDir$, remoteName$) Protected gc.GitCall, out$, i.i, n.i, line$ gc\workdir = repoDir$ gc\args = "remote" If RunGit(@gc) <> 0 ProcedureReturn 0 EndIf out$ = gc\output n = CountString(out$, #LF$) + 1 For i = 1 To n line$ = Trim(StringField(out$, i, #LF$)) If line$ <> "" And LCase(line$) = LCase(remoteName$) ProcedureReturn 1 EndIf Next ProcedureReturn 0 EndProcedure Procedure.i ShowGitOutput(title$, stdOut$, stdErr$) Protected full$ full$ + "=== STDOUT ===" + #LF$ + stdOut$ + #LF$ + "=== STDERR ===" + #LF$ + stdErr$ If OpenWindow(#WOut, 0, 0, 820, 520, title$, #PB_Window_SystemMenu | #PB_Window_ScreenCentered) EditorGadget(#GOutText, 10, 10, 800, 460) ButtonGadget(#GOutCopy, 600, 480, 100, 28, "Copier") ButtonGadget(#GOutClose, 710, 480, 100, 28, "Fermer") SetGadgetText(#GOutText, full$) Repeat Protected ev.i = WaitWindowEvent() If ev = #PB_Event_Gadget Select EventGadget() Case #GOutCopy SetClipboardText(full$) Case #GOutClose CloseWindow(#WOut) Break EndSelect EndIf Until ev = #PB_Event_CloseWindow EndIf ProcedureReturn 1 EndProcedure ; Ajoute un remote Procedure.i AddRemote(repoDir$, remoteName$, remoteUrl$) Protected gc.GitCall gc\workdir = repoDir$ gc\args = "remote add " + remoteName$ + " " + Chr(34) + remoteUrl$ + Chr(34) If RunGit(@gc) = 0 ProcedureReturn 1 EndIf ShowGitOutput("Git remote add — erreur", "", gc\errors) ProcedureReturn 0 EndProcedure ; Exécute git avec capture stdout/stderr (structure passée par pointeur) Procedure.i RunGit(*call.GitCall) Protected prg.i, line$,lineError$, out$, err$ If #EnableDebug Debug "[RunGit] " + #GitExe$ + " " + *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 lineError$ = ReadProgramError(prg) ; renvoie "" si rien à lire (non bloquant) If lineError$ <> "" : err$ + lineError$ + #LF$ : EndIf Wend Delay(5) Wend While AvailableProgramOutput(prg) line$ = ReadProgramString(prg) If line$ <> "" : out$ + line$ + #LF$ : EndIf Wend 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$) 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 ; Détermine un répertoire pertinent (project → env → --repo → exe) Procedure.s DirFromArgOrFallback() Protected n.i = CountProgramParameters() Protected gotNextProject.i = 0 Protected gotNextRepo.i = 0 Protected dir$ = "", 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 "[Dir] from args: " + dir$ : EndIf ProcedureReturn dir$ EndIf Protected projFile$ = GetEnvironmentVariable("PB_TOOL_Project") If projFile$ <> "" Protected projDir$ = GetPathPart(projFile$) If projDir$ <> "" If #EnableDebug : Debug "[Dir] from PB_TOOL_Project: " + projDir$ : EndIf ProcedureReturn projDir$ EndIf EndIf dir$ = GetPathPart(ProgramFilename()) If #EnableDebug : Debug "[Dir] 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é." + #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, code.i gc\workdir = repoDir$ gc\args = "add -A" code = RunGit(@gc) If code <> 0 MessageRequester("Git add", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error) ProcedureReturn 0 EndIf gc\args = "commit -m " + Chr(34) + message$ + Chr(34) code = RunGit(@gc) If code <> 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 ; Push amélioré : ; - Accepte 'Remote' comme URL : crée/emploie 'origin' automatiquement ; - Si pas d'upstream : retente avec 'push -u ' ; - Affiche sorties complètes en cas d'erreur Procedure.i DoPush(repoDir$, remote$, branch$) Protected gc.GitCall, code.i Protected remoteName$ = remote$ Protected remoteUrl$ = "" Protected curBranch$ ; Remote vide → suppose 'origin' If Trim(remoteName$) = "" remoteName$ = "origin" EndIf ; Si l'utilisateur a saisi une URL comme 'remote', crée/emploie 'origin' If IsUrlRemote(remoteName$) remoteUrl$ = remoteName$ remoteName$ = "origin" If RemoteExists(repoDir$, remoteName$) = 0 If AddRemote(repoDir$, remoteName$, remoteUrl$) = 0 ProcedureReturn 0 EndIf EndIf EndIf ; Branche à utiliser : champ ou branche courante If Trim(branch$) = "" curBranch$ = GetCurrentBranch(repoDir$) Else curBranch$ = branch$ EndIf If curBranch$ = "" MessageRequester("Git push", "Branche courante introuvable (HEAD détachée ?). Sélectionnez une branche.", #PB_MessageRequester_Warning) ProcedureReturn 0 EndIf ; Tentative de push normal gc\workdir = repoDir$ gc\args = "push " + remoteName$ + " " + curBranch$ code = RunGit(@gc) If code = 0 MessageRequester("Git push", "OK:" + #LF$ + TrimNewlines(gc\output), #PB_MessageRequester_Info) ProcedureReturn 1 EndIf ; Si pas d'upstream, retente avec -u (premier push) If FindString(LCase(gc\errors), "no upstream branch", 1) Or FindString(LCase(gc\errors), "set the remote as upstream", 1) gc\args = "push -u " + remoteName$ + " " + curBranch$ code = RunGit(@gc) If code = 0 MessageRequester("Git push (premier envoi)", "Upstream configuré et push effectué." + #LF$ + TrimNewlines(gc\output), #PB_MessageRequester_Info) ProcedureReturn 1 EndIf EndIf ; Autre erreur → fenêtre complète ShowGitOutput("Git push — erreur", gc\output, gc\errors) 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()) 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 ; ====== Status → lignes et gestion des coches ====== Procedure.i LoadStatusRows(repoDir$, List rows.FileRow()) Protected gc.GitCall, text$, line$, n.i, i.i gc\args = "status --porcelain" gc\workdir = repoDir$ If RunGit(@gc) <> 0 MessageRequester("Git status", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error) ProcedureReturn 0 EndIf text$ = gc\output n = CountString(text$, #LF$) + 1 For i = 1 To n line$ = Trim(StringField(text$, i, #LF$)) If line$ <> "" AddElement(rows()) rows()\stat = Left(line$, 2) rows()\file = Trim(Mid(line$, 4)) rows()\include = 0 EndIf Next i ProcedureReturn ListSize(rows()) EndProcedure Procedure.i FillStatusList(List rows.FileRow()) ClearGadgetItems(#GListStatus) Protected idx.i = 0 Protected label$ ForEach rows() label$ = PorcelainToLabel(rows()\stat) AddGadgetItem(#GListStatus, -1, label$ + #LF$ + rows()\file) If rows()\include SetGadgetItemState(#GListStatus, idx, #PB_ListIcon_Checked) Else SetGadgetItemState(#GListStatus, idx, 0) EndIf idx + 1 Next ProcedureReturn CountGadgetItems(#GListStatus) EndProcedure Procedure.i ToggleIncludeAt(index.i, List rows.FileRow()) If index < 0 : ProcedureReturn 0 : EndIf Protected c.i = CountGadgetItems(#GListStatus) If index >= c : ProcedureReturn 0 : EndIf Protected state.i = GetGadgetItemState(#GListStatus, index) Protected want.i If state & #PB_ListIcon_Checked : want = 1 : Else : want = 0 : EndIf Protected j.i = 0 ForEach rows() If j = index rows()\include = want Break EndIf j + 1 Next ProcedureReturn 1 EndProcedure Procedure.i CollectIncludedFiles(List rows.FileRow(), List files.s()) ClearList(files()) ForEach rows() If rows()\include AddElement(files()) files() = rows()\file EndIf Next ProcedureReturn ListSize(files()) EndProcedure ; Commit sélectif si des fichiers sont cochés Procedure.i DoCommitSelected(repoDir$, message$, doPush.i, remote$, branch$, List files.s()) If ListSize(files()) = 0 ProcedureReturn DoCommit(repoDir$, message$, doPush, remote$, branch$) EndIf Protected gc.GitCall, code.i, argsAdd$, q$ gc\workdir = repoDir$ q$ = Chr(34) argsAdd$ = "add" ForEach files() argsAdd$ + " " + q$ + files() + q$ Next gc\args = argsAdd$ code = RunGit(@gc) If code <> 0 MessageRequester("Git add", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error) ProcedureReturn 0 EndIf gc\args = "commit -m " + Chr(34) + message$ + Chr(34) code = RunGit(@gc) If code <> 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 ; ====== Fenêtre Avancé… (branches) ====== #WAdv = 200 #GAdvLabel = 210 #GAdvCombo = 211 #GAdvSwitch = 212 #GAdvRestore = 213 #GAdvClose = 214 Procedure.i OpenAdvancedWindow(repoDir$) Protected b$ NewList blist.s() ListBranches(repoDir$, blist()) If OpenWindow(#WAdv, 0, 0, 440, 160, "Actions avancées — Branches", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) TextGadget(#GAdvLabel, 10, 14, 140, 24, "Branche :") ComboBoxGadget(#GAdvCombo, 160, 12, 270, 24) ForEach blist() : AddGadgetItem(#GAdvCombo, -1, blist()) : Next GadgetToolTip(#GAdvCombo, "Sélectionnez la branche cible.") ButtonGadget(#GAdvSwitch, 10, 60, 170, 30, "Basculer sur la branche") GadgetToolTip(#GAdvSwitch, "git switch (ou git checkout).") ButtonGadget(#GAdvRestore, 190, 60, 170, 30, "Restaurer depuis la branche") GadgetToolTip(#GAdvRestore, "git restore --source -- . (remplace le contenu de travail).") ButtonGadget(#GAdvClose, 370, 60, 60, 30, "Fermer") Repeat Protected ev.i = WaitWindowEvent() If ev = #PB_Event_Gadget Select EventGadget() Case #GAdvSwitch b$ = GetGadgetText(#GAdvCombo) If b$ <> "" If SwitchToBranch(repoDir$, b$) MessageRequester("Branche", "Basculé sur '" + b$ + "'.", #PB_MessageRequester_Info) EndIf EndIf Case #GAdvRestore b$ = GetGadgetText(#GAdvCombo) If b$ <> "" If MessageRequester("Restauration", "Cette action va restaurer le contenu depuis '" + b$ + "'." + #LF$ + "Continuer ?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes If RestoreFromBranch(repoDir$, b$) MessageRequester("Restauration", "Contenu restauré depuis '" + b$ + "'.", #PB_MessageRequester_Info) EndIf EndIf EndIf Case #GAdvClose CloseWindow(#WAdv) ProcedureReturn 1 EndSelect EndIf Until ev = #PB_Event_CloseWindow ProcedureReturn 1 EndIf ProcedureReturn 0 EndProcedure Procedure.i SwitchToBranch(repoDir$, branch$) Protected gc.GitCall gc\workdir = repoDir$ gc\args = "switch " + Chr(34) + branch$ + Chr(34) If RunGit(@gc) = 0 : ProcedureReturn 1 : EndIf gc\args = "checkout " + Chr(34) + branch$ + Chr(34) ; fallback versions anciennes If RunGit(@gc) = 0 : ProcedureReturn 1 : EndIf MessageRequester("Branche", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error) ProcedureReturn 0 EndProcedure Procedure.i RestoreFromBranch(repoDir$, branch$) Protected gc.GitCall gc\workdir = repoDir$ gc\args = "restore --source " + Chr(34) + branch$ + Chr(34) + " -- ." If RunGit(@gc) = 0 : ProcedureReturn 1 : EndIf MessageRequester("Restauration", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error) ProcedureReturn 0 EndProcedure Procedure.s PorcelainToLabel(code$) Protected x$ = Left(code$, 1) Protected y$ = Right(code$, 1) If code$ = "??" ProcedureReturn "Nouveau (non suivi)" EndIf If code$ = "!!" ProcedureReturn "Ignoré" EndIf ; Colonne X = indexé (staged) If x$ = "M" : ProcedureReturn "Modifié (indexé)" : EndIf If x$ = "A" : ProcedureReturn "Ajouté (indexé)" : EndIf If x$ = "D" : ProcedureReturn "Supprimé (indexé)" : EndIf If x$ = "R" : ProcedureReturn "Renommé (indexé)" : EndIf If x$ = "C" : ProcedureReturn "Copié (indexé)" : EndIf ; Colonne Y = non indexé (worktree) If y$ = "M" : ProcedureReturn "Modifié (non indexé)" : EndIf If y$ = "A" : ProcedureReturn "Ajouté (non indexé)" : EndIf If y$ = "D" : ProcedureReturn "Supprimé (non indexé)" : EndIf ProcedureReturn "Changement" EndProcedure Procedure.i OpenDiffWindow(repoDir$, List rows.FileRow()) Protected idx.i = GetGadgetState(#GListStatus) If idx < 0 MessageRequester("Diff", "Sélectionnez un fichier dans la liste.", #PB_MessageRequester_Info) ProcedureReturn 0 EndIf Protected j.i = 0, target$ ForEach rows() If j = idx target$ = rows()\file Break EndIf j + 1 Next If target$ = "" MessageRequester("Diff", "Aucun fichier sélectionné.", #PB_MessageRequester_Info) ProcedureReturn 0 EndIf Protected gc.GitCall gc\workdir = repoDir$ gc\args = "diff -- " + Chr(34) + target$ + Chr(34) If RunGit(@gc) <> 0 MessageRequester("Diff", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error) ProcedureReturn 0 EndIf If OpenWindow(#WDiff, 0, 0, 800, 500, "Diff — " + target$, #PB_Window_SystemMenu | #PB_Window_ScreenCentered) EditorGadget(#GDiffText, 10, 10, 780, 440) ButtonGadget(#GDiffClose, 690, 460, 100, 28, "Fermer") SetGadgetText(#GDiffText, gc\output) Repeat Protected ev.i = WaitWindowEvent() If ev = #PB_Event_Gadget And EventGadget() = #GDiffClose CloseWindow(#WDiff) Break EndIf Until ev = #PB_Event_CloseWindow EndIf ProcedureReturn 1 EndProcedure Procedure.s GetLocalConfig(repoDir$, key$) Protected gc.GitCall gc\workdir = repoDir$ gc\args = "config --get " + key$ If RunGit(@gc) = 0 ProcedureReturn TrimNewlines(gc\output) EndIf ProcedureReturn "" EndProcedure Procedure.i ConfigIdentityWizard(repoDir$) Protected curName$ = GetLocalConfig(repoDir$, "user.name") Protected curMail$ = GetLocalConfig(repoDir$, "user.email") Protected name$ = InputRequester("Identité Git", "Nom d’auteur (user.name)", curName$) If name$ = "" : ProcedureReturn 0 : EndIf Protected mail$ = InputRequester("Identité Git", "Email (user.email)", curMail$) If mail$ = "" Or FindString(mail$, "@", 1) = 0 MessageRequester("Identité Git", "Email invalide.", #PB_MessageRequester_Warning) ProcedureReturn 0 EndIf Protected gc.GitCall gc\workdir = repoDir$ gc\args = "config user.name " + Chr(34) + name$ + Chr(34) If RunGit(@gc) <> 0 MessageRequester("Git config", "Échec user.name: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error) ProcedureReturn 0 EndIf gc\args = "config user.email " + Chr(34) + mail$ + Chr(34) If RunGit(@gc) <> 0 MessageRequester("Git config", "Échec user.email: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error) ProcedureReturn 0 EndIf MessageRequester("Identité Git", "Identité locale configurée.", #PB_MessageRequester_Info) ProcedureReturn 1 EndProcedure Procedure.i MakeDefaultGitignore(repoDir$) Protected base$ = repoDir$ If Right(base$, 1) <> #PathSep$ : base$ + #PathSep$ : EndIf Protected path$ = base$ + ".gitignore" If FileSize(path$) > 0 If MessageRequester(".gitignore", "Un .gitignore existe déjà. Le remplacer ?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_No ProcedureReturn 0 EndIf EndIf Protected txt$ = "" txt$ + "# PureBasic / build artefacts" + #LF$ txt$ + "*.exe" + #LF$ txt$ + "*.dll" + #LF$ txt$ + "*.so" + #LF$ txt$ + "*.dylib" + #LF$ txt$ + "*.o" + #LF$ txt$ + "*.obj" + #LF$ txt$ + "*.pdb" + #LF$ txt$ + "*.res" + #LF$ txt$ + "*.a" + #LF$ txt$ + "*.lib" + #LF$ txt$ + "*.d" + #LF$ txt$ + "*.map" + #LF$ txt$ + "*.dbg" + #LF$ txt$ + "*.log" + #LF$ txt$ + "*.temp" + #LF$ txt$ + "*.bak" + #LF$ txt$ + "*.cache" + #LF$ txt$ + #LF$ txt$ + "# IDE / OS" + #LF$ txt$ + ".DS_Store" + #LF$ txt$ + "Thumbs.db" + #LF$ txt$ + ".idea" + #LF$ txt$ + ".vscode" + #LF$ Protected ok.i If CreateFile(0, path$) WriteString(0, txt$) : CloseFile(0) : ok = 1 EndIf If ok MessageRequester(".gitignore", "Fichier .gitignore créé.", #PB_MessageRequester_Info) ProcedureReturn 1 EndIf MessageRequester(".gitignore", "Échec de création.", #PB_MessageRequester_Error) ProcedureReturn 0 EndProcedure ; Vérifie si 'dir$' est un dépôt Git ; - essaie via 'git rev-parse --is-inside-work-tree' ; - fallback: présence du dossier .git Procedure.i IsGitRepo(dir$) Protected gc.GitCall Protected isRepo.i = 0 Protected dotGitDir$ = dir$ If Right(dotGitDir$, 1) <> #PathSep$ dotGitDir$ + #PathSep$ EndIf dotGitDir$ + ".git" gc\workdir = dir$ gc\args = "rev-parse --is-inside-work-tree" If RunGit(@gc) = 0 If FindString(LCase(TrimNewlines(gc\output)), "true", 1) isRepo = 1 EndIf Else ; Fallback : dossier .git présent ? If FileSize(dotGitDir$) = -2 isRepo = 1 EndIf EndIf If #EnableDebug Debug "[IsGitRepo] " + dir$ + " -> " + Str(isRepo) EndIf ProcedureReturn isRepo EndProcedure ; Met à jour le panneau "Guide" avec un pas-à-pas adapté ; - Inclut l’étape “Init repo” si le dossier n’est pas encore un dépôt ; - Rappelle la différence Commit / Push pour débutants Procedure.i UpdateGuide(repoDir$, List rows.FileRow(), branch$, remote$) Protected tips$ = "" Protected hasChanges.i = 0 Protected name$ = GetLocalConfig(repoDir$, "user.name") Protected mail$ = GetLocalConfig(repoDir$, "user.email") Protected repoReady.i = IsGitRepo(repoDir$) ; Y a-t-il des lignes dans le status ? ForEach rows() hasChanges = 1 Break Next tips$ + "Bienvenue !" + #LF$ tips$ + "- Ce panneau vous guide pas à pas." + #LF$ + #LF$ If repoReady = 0 ; ---- Dépôt non initialisé : on commence par git init ---- tips$ + "Étapes de départ :" + #LF$ tips$ + "1) " + Chr(149) + " Cliquez sur le bouton 'Init repo' pour INITIALISER le dépôt dans ce dossier." + #LF$ tips$ + "2) " + Chr(149) + " (Recommandé) Créez un '.gitignore' avec le bouton dédié, pour ignorer les binaires/artefacts." + #LF$ tips$ + "3) " + Chr(149) + " Configurez votre identité (bouton 'Configurer identité…')." + #LF$ tips$ + "4) " + Chr(149) + " Cochez les fichiers à inclure, saisissez un message, puis 'Add + Commit'." + #LF$ tips$ + "5) " + Chr(149) + " Pour partager en ligne : créez un dépôt distant (GitHub/GitLab…) puis utilisez 'Push'." + #LF$ + #LF$ Else ; ---- Dépôt déjà prêt ---- tips$ + "Étapes :" + #LF$ tips$ + "1) " + Chr(149) + " (Optionnel) Créez/complétez un '.gitignore' si nécessaire." + #LF$ tips$ + "2) " + Chr(149) + " Vérifiez l’identité Git locale." + #LF$ If name$ = "" Or mail$ = "" tips$ + " → Identité : INCOMPLÈTE (utilisez 'Configurer identité…')." + #LF$ Else tips$ + " → Identité : " + name$ + " <" + mail$ + ">" + #LF$ EndIf tips$ + "3) " + Chr(149) + " Cochez les fichiers à inclure au prochain commit (bouton 'Diff…' pour voir les détails)." + #LF$ If hasChanges tips$ + " → Des modifications sont détectées ci-dessus." + #LF$ Else tips$ + " → Aucune modification détectée pour l’instant." + #LF$ EndIf tips$ + "4) " + Chr(149) + " Saisissez un message, cliquez 'Add + Commit' (et cochez 'Pousser après' si vous voulez envoyer au serveur)." + #LF$ tips$ + "5) " + Chr(149) + " Pour changer/restaurer une branche : 'Avancé…'." + #LF$ + #LF$ EndIf tips$ + "Infos :" + #LF$ tips$ + "• Remote : " + remote$ + " • Branche : " + branch$ + #LF$ tips$ + "• 'Diff…' affiche le détail d’un fichier sélectionné." + #LF$ + #LF$ ; Rappel pédagogique (commit vs push) tips$ + "Rappel : différence entre Commit et Push" + #LF$ tips$ + "• Commit : enregistre localement un instantané de vos fichiers (dans l’historique du dépôt sur votre machine)." + #LF$ tips$ + "• Push : envoie vos commits locaux vers un serveur distant (GitHub, GitLab, etc.)." + #LF$ tips$ + "→ On peut faire des commits hors-ligne ; le push nécessite un dépôt distant configuré (ex. 'origin') et une branche (ex. 'main')." + #LF$ SetGadgetText(#GGuide, tips$) If #EnableDebug Debug "[UpdateGuide] repoReady=" + Str(repoReady) + " changes=" + Str(hasChanges) EndIf ProcedureReturn 1 EndProcedure ; Affiche stdout + stderr dans une fenêtre (évite le texte tronqué) ; ====== UI principale ====== ; Gadgets IDs (inclut les NOUVEAUX boutons) #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 #GInclude = 27 #GExclude = 28 #GIncludeAll = 29 #GExcludeAll = 30 #GAdvanced = 31 Procedure.i OpenGUI(initialDir$, prefsPath$) Protected repoDir$ = DetectRepoRoot(initialDir$) Protected remote$ = "", branch$ = "" LoadPrefs(prefsPath$, remote$, branch$) If OpenWindow(#GWindow, 0, 0, 820, 640, "PBIDE-GitTool — Git (mode simplifié)", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) TextGadget(#GLabelRepo, 10, 12, 60, 22, "Dépôt :") StringGadget(#GStringRepo, 80, 10, 620, 24, repoDir$) ButtonGadget(#GButtonBrowse, 710, 10, 100, 24, "Parcourir…") ; Liste avec cases à cocher ListIconGadget(#GListStatus, 10, 46, 800, 300, "Statut", 180, #PB_ListIcon_CheckBoxes | #PB_ListIcon_FullRowSelect) AddGadgetColumn(#GListStatus, 1, "Fichier", 600) ; Ligne actions liste ButtonGadget(#GRefresh, 10, 352, 90, 26, "Rafraîchir") ButtonGadget(#GInit, 110, 352, 90, 26, "Init repo") ButtonGadget(#GInclude, 210, 352, 90, 26, "Inclure") ButtonGadget(#GExclude, 310, 352, 90, 26, "Exclure") ButtonGadget(#GIncludeAll, 410, 352, 110, 26, "Tout inclure") ButtonGadget(#GExcludeAll, 530, 352, 110, 26, "Tout exclure") ButtonGadget(#GDiff, 650, 352, 70, 26, "Diff…") ButtonGadget(#GAdvanced, 730, 352, 80, 26, "Avancé…") ; Zone commit / push TextGadget(#GLabelMsg, 10, 388, 100, 22, "Message :") StringGadget(#GStringMsg, 110, 386, 620, 24, "") CheckBoxGadget(#GCheckPush, 10, 418, 140, 22, "Pousser après") SetGadgetState(#GCheckPush, #True) TextGadget(#GLabelRemote, 160, 418, 60, 22, "Remote :") StringGadget(#GStringRemote, 220, 416, 120, 24, remote$) TextGadget(#GLabelBranch, 350, 418, 60, 22, "Branche :") ComboBoxGadget(#GComboBranch, 410, 416, 170, 24) ButtonGadget(#GSavePrefs, 590, 416, 220, 24, "Sauver défauts") ButtonGadget(#GCommit, 10, 450, 120, 30, "Add + Commit") ButtonGadget(#GPush, 140, 450, 120, 30, "Push") ButtonGadget(#GPull, 270, 450, 120, 30, "Pull") ButtonGadget(#GConfig, 400, 450, 170, 30, "Configurer identité…") ButtonGadget(#GMakeIgnore, 580, 450, 230, 30, "Créer .gitignore (recommandé)") ; Panneau Guide EditorGadget(#GGuide, 10, 490, 800, 140) ;DisableGadget(#GGuide, 1) ; Infobulles GadgetToolTip(#GListStatus, "Cochez pour inclure un fichier dans le prochain commit. Double-cliquez pour sélectionner, bouton 'Diff…' pour le détail.") GadgetToolTip(#GDiff, "Afficher les différences du fichier sélectionné.") GadgetToolTip(#GConfig, "Configurer user.name et user.email localement pour ce dépôt.") GadgetToolTip(#GMakeIgnore, "Créer un .gitignore avec des règles recommandées pour PureBasic.") ; Branches NewList branchItems.s() ListBranches(repoDir$, branchItems()) ClearGadgetItems(#GComboBranch) ForEach branchItems() : AddGadgetItem(#GComboBranch, -1, branchItems()) : Next If branch$ <> "" : SetGadgetText(#GComboBranch, branch$) : EndIf ; Status NewList rows.FileRow() LoadStatusRows(repoDir$, rows()) FillStatusList(rows()) UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) ; Boucle 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$) ClearList(branchItems()) ListBranches(repoDir$, branchItems()) ClearGadgetItems(#GComboBranch) ForEach branchItems() : AddGadgetItem(#GComboBranch, -1, branchItems()) : Next ClearList(rows()) LoadStatusRows(repoDir$, rows()) FillStatusList(rows()) UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) EndIf Case #GRefresh repoDir$ = GetGadgetText(#GStringRepo) ClearList(rows()) LoadStatusRows(repoDir$, rows()) FillStatusList(rows()) UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) Case #GInit repoDir$ = GetGadgetText(#GStringRepo) DoInitRepo(repoDir$) ClearList(rows()) LoadStatusRows(repoDir$, rows()) FillStatusList(rows()) UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) Case #GInclude Protected idx.i = GetGadgetState(#GListStatus) If idx >= 0 SetGadgetItemState(#GListStatus, idx, #PB_ListIcon_Checked) ToggleIncludeAt(idx, rows()) EndIf UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) Case #GExclude idx = GetGadgetState(#GListStatus) If idx >= 0 SetGadgetItemState(#GListStatus, idx, 0) ToggleIncludeAt(idx, rows()) EndIf UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) Case #GIncludeAll Protected c.i = CountGadgetItems(#GListStatus) For idx = 0 To c - 1 : SetGadgetItemState(#GListStatus, idx, #PB_ListIcon_Checked) : Next ForEach rows() : rows()\include = 1 : Next UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) Case #GExcludeAll c = CountGadgetItems(#GListStatus) For idx = 0 To c - 1 : SetGadgetItemState(#GListStatus, idx, 0) : Next ForEach rows() : rows()\include = 0 : Next UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) Case #GDiff repoDir$ = GetGadgetText(#GStringRepo) OpenDiffWindow(repoDir$, rows()) Case #GAdvanced repoDir$ = GetGadgetText(#GStringRepo) OpenAdvancedWindow(repoDir$) ClearList(rows()) LoadStatusRows(repoDir$, rows()) FillStatusList(rows()) UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) 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 UpdateGuide(repoDir$, rows(), branch$, remote$) Case #GConfig repoDir$ = GetGadgetText(#GStringRepo) If ConfigIdentityWizard(repoDir$) UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) EndIf Case #GMakeIgnore repoDir$ = GetGadgetText(#GStringRepo) If MakeDefaultGitignore(repoDir$) ; optionnel : refresh ClearList(rows()) LoadStatusRows(repoDir$, rows()) FillStatusList(rows()) 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 NewList files.s() CollectIncludedFiles(rows(), files()) If ListSize(files()) > 0 DoCommitSelected(repoDir$, msg$, GetGadgetState(#GCheckPush), remote$, branch$, files()) Else DoCommit(repoDir$, msg$, GetGadgetState(#GCheckPush), remote$, branch$) EndIf ClearList(rows()) LoadStatusRows(repoDir$, rows()) FillStatusList(rows()) UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) 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$) Case #GListStatus idx = GetGadgetState(#GListStatus) If idx >= 0 : ToggleIncludeAt(idx, rows()) : EndIf UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) EndSelect EndSelect Until ev = #PB_Event_CloseWindow ProcedureReturn 1 EndIf ProcedureReturn 0 EndProcedure ; ====== Installation IDE ====== ; Fenêtre assistant #WInstall = 100 #GLabelIde = 110 #GStringIde = 111 #GButtonIde = 112 #GLabelTools = 113 #GStringTools = 114 #GButtonTools = 115 #GLabelTheme = 116 #GStringTheme = 117 #GButtonTheme = 118 #GInstallGo = 119 #GInstallCancel = 120 #GInstallNote = 121 Procedure.i InstallPBGitInIDE(ideExe$, toolsPrefs$, themeZip$) Protected ok.i = 1, ideHome$, themesDir$, destZip$, args$, prg.i, copyok.i If FileSize(ideExe$) <= 0 Or FileSize(toolsPrefs$) <= 0 Or FileSize(themeZip$) <= 0 MessageRequester("Installation", "Chemins invalides. Sélectionnez PureBasic.exe, le fichier d’outils et le thème.", #PB_MessageRequester_Error) ProcedureReturn 0 EndIf ideHome$ = GetPathPart(ideExe$) themesDir$ = ideHome$ + "Themes" + #PathSep$ destZip$ = themesDir$ + GetFilePart(themeZip$) If FileSize(themesDir$) <> -2 If CreateDirectory(themesDir$) = 0 MessageRequester("Installation", "Impossible de créer le dossier Themes : " + themesDir$, #PB_MessageRequester_Error) ProcedureReturn 0 EndIf EndIf copyok = CopyFile(themeZip$, destZip$) If copyok = 0 MessageRequester("Installation", "Échec de copie du thème vers : " + destZip$, #PB_MessageRequester_Error) ok = 0 EndIf If ok args$ = "/A " + Chr(34) + toolsPrefs$ + Chr(34) prg = RunProgram(ideExe$, args$, "", #PB_Program_Hide | #PB_Program_Wait) If prg = 0 MessageRequester("Installation", "Échec de l’import des outils (/A).", #PB_MessageRequester_Error) ok = 0 EndIf EndIf If ok MessageRequester("Installation", "Intégration importée !" + #LF$ + "- Thème copié : " + destZip$ + #LF$ + "- Outils importés via /A", #PB_MessageRequester_Info) EndIf ProcedureReturn ok EndProcedure Procedure.i OpenInstallWizard() Protected exeDefault$ = "" Protected dirExe$ = GetPathPart(ProgramFilename()) Protected prefsDefault$ = dirExe$ + "git_tools.prefs" Protected themeDefault$ = dirExe$ + "PBGitTheme.zip" If OpenWindow(#WInstall, 0, 0, 680, 240, "PBIDE-GitTool — Assistant d’installation IDE", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) TextGadget(#GLabelIde, 10, 16, 140, 22, "PureBasic.exe :") StringGadget(#GStringIde,160, 14, 420, 24, exeDefault$) ButtonGadget(#GButtonIde,590, 14, 80, 24, "Parcourir…") TextGadget(#GLabelTools, 10, 56, 140, 22, "Fichier outils :") StringGadget(#GStringTools,160, 54, 420, 24, prefsDefault$) ButtonGadget(#GButtonTools,590, 54, 80, 24, "Parcourir…") TextGadget(#GLabelTheme, 10, 96, 140, 22, "Thème icônes :") StringGadget(#GStringTheme,160, 94, 420, 24, themeDefault$) ButtonGadget(#GButtonTheme,590, 94, 80, 24, "Parcourir…") ButtonGadget(#GInstallGo, 380, 150, 140, 30, "Installer") ButtonGadget(#GInstallCancel, 530, 150, 140, 30, "Annuler") TextGadget(#GInstallNote, 10, 190, 660, 40, "Astuce : placez 'git_tools.prefs' et 'PBGitTheme.zip' à côté de PBIDE-GitTool.exe pour auto-renseignement." + #LF$ + "Ensuite choisissez le thème (Préférences → Themes) et ajoutez les boutons 'Run tool' dans la Toolbar.") GadgetToolTip(#GStringIde, "Chemin de PureBasic.exe (IDE).") GadgetToolTip(#GButtonIde, "Parcourir pour sélectionner l’exécutable de l’IDE PureBasic.") GadgetToolTip(#GStringTools, "Fichier d’outils externes à importer (/A).") GadgetToolTip(#GButtonTools, "Parcourir le fichier 'git_tools.prefs'.") GadgetToolTip(#GStringTheme, "Archive ZIP du thème d’icônes pour la Toolbar.") GadgetToolTip(#GButtonTheme, "Parcourir le fichier 'PBGitTheme.zip'.") GadgetToolTip(#GInstallGo, "Lancer l’installation (copie du thème + import des outils).") GadgetToolTip(#GInstallCancel, "Fermer l’assistant sans rien modifier.") Repeat Protected ev.i = WaitWindowEvent() If ev = #PB_Event_Gadget Select EventGadget() Case #GButtonIde Protected pickExe$ = OpenFileRequester("Choisir PureBasic.exe", GetPathPart(GetGadgetText(#GStringIde)), "Exécutable|*.exe;*|Tous|*.*", 0) If pickExe$ <> "" : SetGadgetText(#GStringIde, pickExe$) : EndIf Case #GButtonTools Protected pickPrefs$ = OpenFileRequester("Choisir le fichier d'outils", GetGadgetText(#GStringTools), "Prefs|*.prefs;*.txt|Tous|*.*", 0) If pickPrefs$ <> "" : SetGadgetText(#GStringTools, pickPrefs$) : EndIf Case #GButtonTheme Protected pickZip$ = OpenFileRequester("Choisir le thème (zip)", GetGadgetText(#GStringTheme), "Zip|*.zip|Tous|*.*", 0) If pickZip$ <> "" : SetGadgetText(#GStringTheme, pickZip$) : EndIf Case #GInstallGo Protected ok.i = InstallPBGitInIDE(GetGadgetText(#GStringIde), GetGadgetText(#GStringTools), GetGadgetText(#GStringTheme)) If ok : CloseWindow(#WInstall) : ProcedureReturn 1 : EndIf Case #GInstallCancel CloseWindow(#WInstall) ProcedureReturn 0 EndSelect EndIf Until ev = #PB_Event_CloseWindow ProcedureReturn 0 EndIf ProcedureReturn 0 EndProcedure ; ====== Entrée ====== ; On ne coupe pas si Git manque, pour permettre l’assistant d’installation If EnsureGitAvailable() = 0 ; Averti, mais continue. EndIf ; Préférences locales de l’outil 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" ; Paramètres CLI 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 ; Si aucun paramètre : proposer l’installation IDE If argCount = 0 If MessageRequester("PBIDE-GitTool", "Aucun paramètre détecté." + #LF$ + "Souhaitez-vous installer l’intégration IDE (outils + thème) maintenant ?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes OpenInstallWizard() End EndIf EndIf For w = 0 To argCount - 1 Define a$ = ProgramParameter(w) Select LCase(a$) Case "--project" : If w + 1 < argCount : repoArg$ = ProgramParameter(w + 1) : EndIf 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 ; Mode non interactif 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$, okstat.i = DoStatus(baseDir$, st$) If okstat : 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 = 212 ; FirstLine = 181 ; Folding = ------- ; EnableXP ; DPIAware ; Executable = ..\PBIDE-GitTool.exe ; CompileSourceDirectory