Files
PBIDE-GitTool/PBIDE-GitTool.pb
2025-08-15 08:19:27 +02:00

1577 lines
52 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.

; PBIDE-GitTool.pb — Outil externe Git pour lIDE 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 dinstallation 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 dinstallation
#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
; --- Bouton "Restaurer fichier…" dans la fenêtre principale ---
#GRestoreFile = 36
; --- Fenêtre de restauration dun fichier vers un commit ---
#WRestore = 500
#GRestList = 501
#GRestOK = 502
#GRestCancel = 503
#GRestInfo = 504
; ====== 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$)
Declare.i OpenRestoreFileWindow(repoDir$, List rows.FileRow())
Declare.i RestoreFileFromCommit(repoDir$, file$, commit$)
; ====== 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é à lemplacement 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 <remote> <branch>'
; - 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 <branche> (ou git checkout).")
ButtonGadget(#GAdvRestore, 190, 60, 170, 30, "Restaurer depuis la branche")
GadgetToolTip(#GAdvRestore, "git restore --source <branche> -- . (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 dauteur (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
; Restaure un fichier à létat dun commit précis
; - Essaye 'git restore --source <commit> -- <file>'
; - Fallback 'git checkout <commit> -- <file>' (pour Git anciens)
Procedure.i RestoreFileFromCommit(repoDir$, file$, commit$)
Protected gc.GitCall
gc\workdir = repoDir$
gc\args = "restore --source " + Chr(34) + commit$ + Chr(34) + " -- " + Chr(34) + file$ + Chr(34)
If #EnableDebug
Debug "[RestoreFile] " + gc\args + " (wd=" + repoDir$ + ")"
EndIf
If RunGit(@gc) = 0
MessageRequester("Restaurer", "Le fichier a été restauré depuis le commit " + commit$ + ".", #PB_MessageRequester_Info)
ProcedureReturn 1
EndIf
; Fallback pour compatibilité
gc\args = "checkout " + Chr(34) + commit$ + Chr(34) + " -- " + Chr(34) + file$ + Chr(34)
If RunGit(@gc) = 0
MessageRequester("Restaurer", "Le fichier a été restauré (fallback checkout) depuis " + commit$ + ".", #PB_MessageRequester_Info)
ProcedureReturn 1
EndIf
MessageRequester("Restaurer", "Échec : " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error)
ProcedureReturn 0
EndProcedure
; Ouvre une fenêtre listant les commits du fichier sélectionné,
; puis restaure le fichier vers le commit choisi.
Procedure.i OpenRestoreFileWindow(repoDir$, List rows.FileRow())
Protected idx.i = GetGadgetState(#GListStatus)
If idx < 0
MessageRequester("Restaurer", "Sélectionnez d'abord un fichier dans la liste.", #PB_MessageRequester_Info)
ProcedureReturn 0
EndIf
; Récupère le chemin du fichier choisi dans rows()
Protected j.i = 0, target$
ForEach rows()
If j = idx
target$ = rows()\file
Break
EndIf
j + 1
Next
If target$ = ""
MessageRequester("Restaurer", "Aucun fichier sélectionné.", #PB_MessageRequester_Info)
ProcedureReturn 0
EndIf
; Récupère lhistorique des commits de ce fichier (50 derniers)
Protected gc.GitCall, out$, line$, n.i, i.i
gc\workdir = repoDir$
gc\args = "log --date=short --pretty=format:%h%x09%ad%x09%s -n 50 -- " + Chr(34) + target$ + Chr(34)
If RunGit(@gc) <> 0
MessageRequester("Restaurer", "Échec du log : " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error)
ProcedureReturn 0
EndIf
out$ = gc\output
If Trim(out$) = ""
MessageRequester("Restaurer", "Aucun commit trouvé pour ce fichier.", #PB_MessageRequester_Info)
ProcedureReturn 0
EndIf
; Prépare une liste parallèle des hash pour retrouver le commit sélectionné
NewList hashes.s()
; Fenêtre de sélection
If OpenWindow(#WRestore, 0, 0, 760, 420, "Restaurer : " + target$, #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
TextGadget(#GRestInfo, 10, 10, 740, 22, "Choisissez le commit vers lequel restaurer le fichier.")
ListIconGadget(#GRestList, 10, 40, 740, 330, "Commit", 100, #PB_ListIcon_FullRowSelect)
AddGadgetColumn(#GRestList, 1, "Date", 90)
AddGadgetColumn(#GRestList, 2, "Message", 520)
ButtonGadget(#GRestOK, 540, 380, 100, 28, "Restaurer")
ButtonGadget(#GRestCancel, 650, 380, 100, 28, "Annuler")
; Remplit la liste
n = CountString(out$, #LF$) + 1
For i = 1 To n
line$ = StringField(out$, i, #LF$)
If Trim(line$) <> ""
; Format: %h<TAB>%ad<TAB>%s
Protected h$ = StringField(line$, 1, #TAB$)
Protected d$ = StringField(line$, 2, #TAB$)
Protected s$ = StringField(line$, 3, #TAB$)
AddGadgetItem(#GRestList, -1, h$ + #LF$ + d$ + #LF$ + s$)
AddElement(hashes()) : hashes() = h$
EndIf
Next
; Boucle fenêtre
Repeat
Protected ev.i = WaitWindowEvent()
If ev = #PB_Event_Gadget
Select EventGadget()
Case #GRestOK
idx = GetGadgetState(#GRestList)
If idx < 0
MessageRequester("Restaurer", "Sélectionnez un commit.", #PB_MessageRequester_Warning)
Else
; Récupère le hash à lindex idx
j = 0
Protected chosen$
ForEach hashes()
If j = idx : chosen$ = hashes() : Break : EndIf
j + 1
Next
If chosen$ <> ""
If RestoreFileFromCommit(repoDir$, target$, chosen$)
CloseWindow(#WRestore)
ProcedureReturn 1
EndIf
EndIf
EndIf
Case #GRestCancel
CloseWindow(#WRestore)
ProcedureReturn 0
EndSelect
EndIf
Until ev = #PB_Event_CloseWindow
ProcedureReturn 0
EndIf
ProcedureReturn 0
EndProcedure
; Met à jour le panneau "Guide" avec un pas-à-pas adapté
; - Inclut létape “Init repo” si le dossier nest 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 lidentité 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 linstant." + #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 dun 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 lhistorique 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$)
; On élargit légèrement pour caser le nouveau bouton
If OpenWindow(#GWindow, 0, 0, 900, 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, 720, 24, repoDir$)
ButtonGadget(#GButtonBrowse, 810, 10, 80, 24, "Parcourir…")
; Liste avec cases à cocher
ListIconGadget(#GListStatus, 10, 46, 880, 300, "Statut", 180, #PB_ListIcon_CheckBoxes | #PB_ListIcon_FullRowSelect)
AddGadgetColumn(#GListStatus, 1, "Fichier", 680)
; 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, 80, 26, "Diff…")
ButtonGadget(#GRestoreFile,740, 352, 150, 26, "Restaurer fichier…")
; 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(#GAdvanced, 820, 416, 70, 24, "Avancé…")
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, 880, 140)
DisableGadget(#GGuide, 1)
; Infobulles utiles
GadgetToolTip(#GRestoreFile, "Restaurer le fichier sélectionné à un commit précis (historique).")
GadgetToolTip(#GDiff, "Afficher les différences du fichier sélectionné.")
; 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 #GRestoreFile
repoDir$ = GetGadgetText(#GStringRepo)
If OpenRestoreFileWindow(repoDir$, rows())
; Après restauration : rafraîchir létat
ClearList(rows())
LoadStatusRows(repoDir$, rows())
FillStatusList(rows())
UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote))
EndIf
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$)
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 doutils 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 limport 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 dinstallation 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 lexécutable de lIDE PureBasic.")
GadgetToolTip(#GStringTools, "Fichier doutils externes à importer (/A).")
GadgetToolTip(#GButtonTools, "Parcourir le fichier 'git_tools.prefs'.")
GadgetToolTip(#GStringTheme, "Archive ZIP du thème dicônes pour la Toolbar.")
GadgetToolTip(#GButtonTheme, "Parcourir le fichier 'PBGitTheme.zip'.")
GadgetToolTip(#GInstallGo, "Lancer linstallation (copie du thème + import des outils).")
GadgetToolTip(#GInstallCancel, "Fermer lassistant 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 lassistant dinstallation
If EnsureGitAvailable() = 0
; Averti, mais continue.
EndIf
; Préférences locales de loutil
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 linstallation IDE
If argCount = 0
If MessageRequester("PBIDE-GitTool", "Aucun paramètre détecté." + #LF$ + "Souhaitez-vous installer linté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 = 1370
; FirstLine = 1329
; Folding = -------
; EnableXP
; DPIAware
; Executable = ..\PBIDE-GitTool.exe
; CompileSourceDirectory