2405 lines
80 KiB
Plaintext
2405 lines
80 KiB
Plaintext
; 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
|
||
#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
|
||
; Options d’affichage
|
||
#GShowClean = 39 ; Afficher les fichiers suivis à jour (propres)
|
||
#GShowIgnored = 40 ; Afficher aussi les fichiers ignorés (.gitignore)
|
||
|
||
#GAddBranch = 42
|
||
#GReloadBranches= 43
|
||
#GShowPermanent = 44
|
||
#GExcludeForever = 45
|
||
#GReincludeForever= 46
|
||
|
||
|
||
; 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
|
||
#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 d’un fichier vers un commit ---
|
||
#WRestore = 500
|
||
#GRestList = 501
|
||
#GRestOK = 502
|
||
#GRestCancel = 503
|
||
#GRestInfo = 504
|
||
|
||
; Mémoriser / restaurer la sélection autour d'un refresh de la liste
|
||
Macro RememberSel()
|
||
keepIdx.i = GetGadgetState(#GListStatus)
|
||
keepFile$ = ""
|
||
If keepIdx >= 0
|
||
keepFile$ = GetGadgetItemText(#GListStatus, keepIdx, 1)
|
||
EndIf
|
||
EndMacro
|
||
|
||
Macro RestoreSel()
|
||
RestoreSelection(keepIdx, keepFile$)
|
||
EndMacro
|
||
|
||
; ====== 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
|
||
|
||
Structure RepoPrefs
|
||
remote.s
|
||
branch.s
|
||
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 SaveRepoPrefs(repoDir$, remote$, branch$)
|
||
Declare.i LoadRepoPrefs(repoDir$, *prefs.RepoPrefs)
|
||
Declare.i OpenGUI(initialDir$, prefsPath$)
|
||
Declare.i CreateOrTrackBranch(repoDir$, remote$, name$)
|
||
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$)
|
||
Declare.i BuildFullFileList(repoDir$, showClean.i, showIgnored.i, List rows.FileRow())
|
||
Declare.s FileFromRowsByIndex(index.i, List rows.FileRow())
|
||
Declare.i OpenRestoreFileWindow(repoDir$, List rows.FileRow())
|
||
Declare.s NormalizeGitFilePath(repoDir$, raw$)
|
||
Declare.i RepoFileExists(repoDir$, rel$)
|
||
Declare.i BuildFullFileList(repoDir$, showClean.i, showIgnored.i, List rows.FileRow())
|
||
Declare.i RestoreSelection(oldIndex.i, oldFile$)
|
||
Declare.s LocalExcludePath(repoDir$)
|
||
Declare.i EnsureToolFilesExcluded(repoDir$)
|
||
Declare.i LoadLocalExcludes(repoDir$, List excl.s())
|
||
Declare.i IsPermanentlyExcluded(file$, List excl.s())
|
||
Declare.i AddPermanentExclude(repoDir$, file$)
|
||
Declare.i RemovePermanentExclude(repoDir$, file$)
|
||
Declare.i SortRowsByInterest(List rows.FileRow())
|
||
|
||
|
||
; ====== Utils ======
|
||
; Convertit le chemin pour Git (Windows ok) :
|
||
; - remplace "\" par "/"
|
||
; - retire un éventuel préfixe "./"
|
||
Procedure.s GitPath(file$)
|
||
Protected p$ = file$
|
||
p$ = ReplaceString(p$, "\", "/")
|
||
If Left(p$, 2) = "./"
|
||
p$ = Mid(p$, 3)
|
||
EndIf
|
||
ProcedureReturn p$
|
||
EndProcedure
|
||
|
||
; Normalise un chemin renvoyé par Git :
|
||
; - enlève espaces/quotes superflus
|
||
; - garde la destination en cas de renommage "old -> new"
|
||
; - remplace les "/" par le séparateur OS
|
||
; - retire un éventuel "./" de tête
|
||
Procedure.s NormalizeGitFilePath(repoDir$, raw$)
|
||
Protected p$ = Trim(raw$)
|
||
; retirer quotes simples/doubles éventuelles autour
|
||
If Left(p$, 1) = Chr(34) And Right(p$, 1) = Chr(34)
|
||
p$ = Mid(p$, 2, Len(p$)-2)
|
||
EndIf
|
||
If Left(p$, 1) = "'" And Right(p$, 1) = "'"
|
||
p$ = Mid(p$, 2, Len(p$)-2)
|
||
EndIf
|
||
|
||
; cas "old -> new" : on garde "new"
|
||
Protected pos.i = FindString(p$, "->", 1)
|
||
If pos > 0
|
||
p$ = Trim(Mid(p$, pos + 2))
|
||
EndIf
|
||
|
||
; retirer "./"
|
||
If Left(p$, 2) = "./"
|
||
p$ = Mid(p$, 3)
|
||
EndIf
|
||
|
||
; slashes vers séparateur OS
|
||
p$ = ReplaceString(p$, "/", #PathSep$)
|
||
|
||
ProcedureReturn p$
|
||
EndProcedure
|
||
|
||
; Vérifie l’existence d’un fichier RELATIF au repo (fichier ou dossier)
|
||
; renvoie 1 si présent, 0 sinon
|
||
Procedure.i RepoFileExists(repoDir$, rel$)
|
||
Protected base$ = repoDir$
|
||
If Right(base$, 1) <> #PathSep$ : base$ + #PathSep$ : EndIf
|
||
Protected abs$ = base$ + rel$
|
||
Protected s.q = FileSize(abs$)
|
||
If s >= 0 Or s = -2
|
||
ProcedureReturn 1
|
||
EndIf
|
||
ProcedureReturn 0
|
||
EndProcedure
|
||
|
||
|
||
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
|
||
|
||
; Lit tout le fichier texte et renvoie son contenu (avec #LF$ entre les lignes)
|
||
Procedure.s ReadAllText(path$)
|
||
Protected out$ = ""
|
||
If ReadFile(0, path$)
|
||
While Eof(0) = 0
|
||
out$ + ReadString(0) + #LF$
|
||
Wend
|
||
CloseFile(0)
|
||
EndIf
|
||
ProcedureReturn out$
|
||
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
|
||
|
||
; Sauvegarde remote/branch pour CE dépôt (.pbide-gittool.prefs à la racine du repo)
|
||
Procedure.i SaveRepoPrefs(repoDir$, remote$, branch$)
|
||
Protected base$ = repoDir$
|
||
If Right(base$, 1) <> #PathSep$ : base$ + #PathSep$ : EndIf
|
||
Protected p$ = base$ + ".pbide-gittool.prefs"
|
||
|
||
If CreatePreferences(p$)
|
||
PreferenceGroup("git")
|
||
WritePreferenceString("remote", remote$)
|
||
WritePreferenceString("branch", branch$)
|
||
ClosePreferences()
|
||
If #EnableDebug
|
||
Debug "[SaveRepoPrefs] " + p$ + " remote=" + remote$ + " branch=" + branch$
|
||
EndIf
|
||
ProcedureReturn 1
|
||
EndIf
|
||
|
||
If #EnableDebug : Debug "[SaveRepoPrefs] ERROR cannot write " + p$ : EndIf
|
||
ProcedureReturn 0
|
||
EndProcedure
|
||
|
||
|
||
; Charge remote/branch du dépôt dans *prefs (retourne 1 si trouvé)
|
||
Procedure.i LoadRepoPrefs(repoDir$, *prefs.RepoPrefs)
|
||
If *prefs = 0 : ProcedureReturn 0 : EndIf
|
||
|
||
Protected base$ = repoDir$
|
||
If Right(base$, 1) <> #PathSep$ : base$ + #PathSep$ : EndIf
|
||
Protected p$ = base$ + ".pbide-gittool.prefs"
|
||
|
||
If OpenPreferences(p$)
|
||
PreferenceGroup("git")
|
||
*prefs\remote = ReadPreferenceString("remote", *prefs\remote)
|
||
*prefs\branch = ReadPreferenceString("branch", *prefs\branch)
|
||
ClosePreferences()
|
||
If #EnableDebug
|
||
Debug "[LoadRepoPrefs] " + p$ + " remote=" + *prefs\remote + " branch=" + *prefs\branch
|
||
EndIf
|
||
ProcedureReturn 1
|
||
EndIf
|
||
|
||
If #EnableDebug : Debug "[LoadRepoPrefs] none for repo " + repoDir$ : EndIf
|
||
ProcedureReturn 0
|
||
EndProcedure
|
||
|
||
|
||
; Restaure la sélection après un FillStatusList()/rebuild
|
||
; Essaie d'abord l'index, puis cherche par nom de fichier (colonne 1).
|
||
Procedure.i RestoreSelection(oldIndex.i, oldFile$)
|
||
Protected count.i = CountGadgetItems(#GListStatus)
|
||
If count = 0
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
If oldIndex >= 0 And oldIndex < count
|
||
If GetGadgetItemText(#GListStatus, oldIndex, 1) = oldFile$
|
||
SetGadgetState(#GListStatus, oldIndex)
|
||
ProcedureReturn 1
|
||
EndIf
|
||
EndIf
|
||
|
||
Protected i.i, name$
|
||
For i = 0 To count - 1
|
||
name$ = GetGadgetItemText(#GListStatus, i, 1)
|
||
If name$ = oldFile$
|
||
SetGadgetState(#GListStatus, i)
|
||
ProcedureReturn 1
|
||
EndIf
|
||
Next
|
||
|
||
ProcedureReturn 0
|
||
EndProcedure
|
||
|
||
; Chemin de .git/info/exclude
|
||
Procedure.s LocalExcludePath(repoDir$)
|
||
Protected base$ = repoDir$
|
||
If Right(base$, 1) <> #PathSep$ : base$ + #PathSep$ : EndIf
|
||
ProcedureReturn base$ + ".git" + #PathSep$ + "info" + #PathSep$ + "exclude"
|
||
EndProcedure
|
||
|
||
; Ajoute notre fichier prefs interne aux exclusions locales si absent
|
||
; Ajoute .pbide-gittool.prefs dans .git/info/exclude si absent
|
||
Procedure.i EnsureToolFilesExcluded(repoDir$)
|
||
Protected excl$ = LocalExcludePath(repoDir$)
|
||
Protected line$ = ".pbide-gittool.prefs"
|
||
Protected txt$ = ReadAllText(excl$)
|
||
Protected found.i = 0
|
||
|
||
If txt$ <> ""
|
||
If FindString(#LF$ + txt$ + #LF$, #LF$ + line$ + #LF$, 1) > 0
|
||
found = 1
|
||
EndIf
|
||
EndIf
|
||
|
||
If found = 0
|
||
If CreateFile(0, excl$)
|
||
; réécrit l’existant tel quel
|
||
If txt$ <> ""
|
||
WriteString(0, txt$)
|
||
If Right(txt$, 1) <> #LF$ : WriteString(0, #LF$) : EndIf
|
||
EndIf
|
||
; ajoute notre ligne
|
||
WriteString(0, line$ + #LF$)
|
||
CloseFile(0)
|
||
If #EnableDebug
|
||
Debug "[EnsureExclude] added " + line$
|
||
EndIf
|
||
ProcedureReturn 1
|
||
Else
|
||
If #EnableDebug
|
||
Debug "[EnsureExclude] cannot write " + excl$
|
||
EndIf
|
||
ProcedureReturn 0
|
||
EndIf
|
||
EndIf
|
||
|
||
ProcedureReturn 1
|
||
EndProcedure
|
||
|
||
|
||
; Charge .git/info/exclude (lignes non vides / non commentées)
|
||
Procedure.i LoadLocalExcludes(repoDir$, List excl.s())
|
||
ClearList(excl())
|
||
Protected excl$ = LocalExcludePath(repoDir$), l$
|
||
If ReadFile(0, excl$)
|
||
While Eof(0) = 0
|
||
l$ = Trim(ReadString(0))
|
||
If l$ <> "" And Left(l$, 1) <> "#"
|
||
AddElement(excl()) : excl() = ReplaceString(l$, "/", #PathSep$)
|
||
EndIf
|
||
Wend
|
||
CloseFile(0)
|
||
ProcedureReturn ListSize(excl())
|
||
EndIf
|
||
ProcedureReturn 0
|
||
EndProcedure
|
||
|
||
; Test simple : exclusion par égalité exacte (sans wildcards)
|
||
Procedure.i IsPermanentlyExcluded(file$, List excl.s())
|
||
ForEach excl()
|
||
If excl() = file$
|
||
ProcedureReturn 1
|
||
EndIf
|
||
Next
|
||
ProcedureReturn 0
|
||
EndProcedure
|
||
|
||
; Ajoute file$ dans .git/info/exclude (si suivi: rm --cached d'abord)
|
||
; Ajoute file$ à .git/info/exclude (et le retire de l’index si suivi)
|
||
Procedure.i AddPermanentExclude(repoDir$, file$)
|
||
Protected q$ = Chr(34)
|
||
Protected gc.GitCall
|
||
Protected excl$ = LocalExcludePath(repoDir$)
|
||
Protected txt$ = ReadAllText(excl$)
|
||
Protected found.i = 0
|
||
|
||
; 1) Si suivi → le retirer de l’index
|
||
gc\workdir = repoDir$
|
||
gc\args = "ls-files -- " + q$ + file$ + q$
|
||
If RunGit(@gc) = 0 And Trim(gc\output) <> ""
|
||
gc\args = "rm --cached -- " + q$ + file$ + q$
|
||
If RunGit(@gc) <> 0
|
||
MessageRequester("Exclusion permanente", "Échec du retrait de l'index : " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
EndIf
|
||
|
||
; 2) Si déjà présent dans exclude → rien à faire
|
||
If txt$ <> ""
|
||
If FindString(#LF$ + txt$ + #LF$, #LF$ + file$ + #LF$, 1) > 0
|
||
found = 1
|
||
EndIf
|
||
EndIf
|
||
If found
|
||
MessageRequester("Exclusion permanente", "Déjà présent dans .git/info/exclude.", #PB_MessageRequester_Info)
|
||
ProcedureReturn 1
|
||
EndIf
|
||
|
||
; 3) Écrire
|
||
If CreateFile(0, excl$)
|
||
If txt$ <> ""
|
||
WriteString(0, txt$)
|
||
If Right(txt$, 1) <> #LF$ : WriteString(0, #LF$) : EndIf
|
||
EndIf
|
||
WriteString(0, file$ + #LF$)
|
||
CloseFile(0)
|
||
MessageRequester("Exclusion permanente", "Ajouté à .git/info/exclude.", #PB_MessageRequester_Info)
|
||
ProcedureReturn 1
|
||
EndIf
|
||
|
||
MessageRequester("Exclusion permanente", "Impossible d'écrire " + excl$, #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndProcedure
|
||
|
||
; Retire file$ de .git/info/exclude et le ré-inclut (git add -f)
|
||
; Retire file$ de .git/info/exclude et force la ré-inclusion (git add -f)
|
||
Procedure.i RemovePermanentExclude(repoDir$, file$)
|
||
Protected excl$ = LocalExcludePath(repoDir$)
|
||
Protected out$ = ""
|
||
Protected l$, removed.i = 0
|
||
|
||
; On relit ligne à ligne et on réécrit sans la ligne ciblée
|
||
If ReadFile(0, excl$) = 0
|
||
MessageRequester("Ré-inclure (permanent)", "Fichier d'exclusion introuvable : " + excl$, #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
While Eof(0) = 0
|
||
l$ = Trim(ReadString(0))
|
||
If l$ <> "" And Left(l$, 1) <> "#"
|
||
If l$ = file$
|
||
removed = 1
|
||
Else
|
||
out$ + l$ + #LF$
|
||
EndIf
|
||
Else
|
||
; on conserve les lignes vides/commentées telles quelles
|
||
out$ + l$ + #LF$
|
||
EndIf
|
||
Wend
|
||
CloseFile(0)
|
||
|
||
If removed = 0
|
||
MessageRequester("Ré-inclure (permanent)", "Le fichier n'était pas dans .git/info/exclude.", #PB_MessageRequester_Info)
|
||
ProcedureReturn 1
|
||
EndIf
|
||
|
||
If CreateFile(0, excl$)
|
||
WriteString(0, out$)
|
||
CloseFile(0)
|
||
Else
|
||
MessageRequester("Ré-inclure (permanent)", "Impossible d'écrire " + excl$, #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; Force l’ajout même s’il y a des règles d’ignore ailleurs
|
||
Protected gc.GitCall, q$ = Chr(34)
|
||
gc\workdir = repoDir$
|
||
gc\args = "add -f -- " + q$ + file$ + q$
|
||
If RunGit(@gc) <> 0
|
||
MessageRequester("Ré-inclure (permanent)", "Avertissement : git add -f a échoué : " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Warning)
|
||
Else
|
||
MessageRequester("Ré-inclure (permanent)", "Le fichier a été ré-inclus.", #PB_MessageRequester_Info)
|
||
EndIf
|
||
|
||
ProcedureReturn 1
|
||
EndProcedure
|
||
|
||
; Crée une branche locale ou suit une branche distante "remote/branch"
|
||
; - name$ = "featureX" → git switch -c featureX
|
||
; - name$ = "origin/main" → git fetch origin + git switch -c main --track origin/main
|
||
Procedure.i CreateOrTrackBranch(repoDir$, remote$, name$)
|
||
Protected gc.GitCall, local$, rem$, br$, pos.i, q$ = Chr(34)
|
||
|
||
If Trim(name$) = "" : ProcedureReturn 0 : EndIf
|
||
|
||
; Détection "remote/branch"
|
||
pos = FindString(name$, "/", 1)
|
||
If pos > 0
|
||
rem$ = Left(name$, pos - 1)
|
||
br$ = Mid(name$, pos + 1)
|
||
local$ = br$
|
||
If #EnableDebug : Debug "[Branch] track " + rem$ + "/" + br$ + " as " + local$ : EndIf
|
||
|
||
; fetch d'abord pour avoir la ref
|
||
gc\workdir = repoDir$
|
||
gc\args = "fetch " + rem$
|
||
If RunGit(@gc) <> 0
|
||
MessageRequester("Branche", "Échec fetch: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; création locale en suivant la remote
|
||
gc\args = "switch -c " + q$ + local$ + q$ + " --track " + rem$ + "/" + br$
|
||
If RunGit(@gc) = 0
|
||
MessageRequester("Branche", "Branche locale '" + local$ + "' suivant " + rem$ + "/" + br$ + ".", #PB_MessageRequester_Info)
|
||
ProcedureReturn 1
|
||
EndIf
|
||
MessageRequester("Branche", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; Création d’une branche locale simple
|
||
gc\workdir = repoDir$
|
||
gc\args = "switch -c " + q$ + name$ + q$
|
||
If RunGit(@gc) = 0
|
||
MessageRequester("Branche", "Branche '" + name$ + "' créée et sélectionnée.", #PB_MessageRequester_Info)
|
||
ProcedureReturn 1
|
||
EndIf
|
||
|
||
MessageRequester("Branche", "Échec: " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error)
|
||
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 <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
|
||
|
||
; ------------------------------------------------------------------
|
||
; Ouvre une fenêtre affichant le diff du fichier sélectionné
|
||
; ------------------------------------------------------------------
|
||
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
|
||
|
||
; Retrouver le chemin du fichier depuis la liste rows()
|
||
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
|
||
|
||
; Exécuter 'git diff -- "<fichier>"'
|
||
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
|
||
|
||
Protected diff$ = gc\output
|
||
If Trim(diff$) = ""
|
||
diff$ = "Aucune différence détectée pour ce fichier."
|
||
EndIf
|
||
|
||
; Fenêtre d’affichage
|
||
Protected title$ = "Diff — " + target$
|
||
If OpenWindow(#WDiff, 0, 0, 800, 500, title$, #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
|
||
EditorGadget(#GDiffText, 10, 10, 780, 440)
|
||
ButtonGadget(#GDiffClose, 690, 460, 100, 28, "Fermer")
|
||
SetGadgetText(#GDiffText, diff$)
|
||
|
||
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.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 ======
|
||
; Charge le status dans une liste de lignes (stat, file, include=0)
|
||
; Parsing robuste : on prend tout ce qui suit le premier séparateur (espace/tab)
|
||
; après les 2 lettres de statut ; gère aussi "old -> new" (renommage).
|
||
Procedure.i LoadStatusRows(repoDir$, List rows.FileRow())
|
||
Protected gc.GitCall, text$, line$, code$, file$
|
||
Protected i.i, n.i, start.i, ch$, pos.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
|
||
ClearList(rows())
|
||
|
||
For i = 1 To n
|
||
line$ = StringField(text$, i, #LF$)
|
||
line$ = Trim(line$)
|
||
If line$ = "" : Continue : EndIf
|
||
|
||
; 2 premières colonnes = statut (XY)
|
||
code$ = Left(line$, 2)
|
||
|
||
; Cherche le début du chemin : après les espaces/tabs suivant la colonne 3
|
||
start = 3
|
||
While start <= Len(line$)
|
||
ch$ = Mid(line$, start, 1)
|
||
If ch$ <> " " And ch$ <> #TAB$
|
||
Break
|
||
EndIf
|
||
start + 1
|
||
Wend
|
||
|
||
file$ = Mid(line$, start)
|
||
; Renommage "old -> new" : garder la destination
|
||
pos = FindString(file$, "->", 1)
|
||
If pos > 0
|
||
file$ = Trim(Mid(file$, pos + 2))
|
||
EndIf
|
||
|
||
; (Optionnel) normalisation simple
|
||
If Left(file$, 2) = "./" : file$ = Mid(file$, 3) : EndIf
|
||
file$ = ReplaceString(file$, "/", #PathSep$)
|
||
|
||
AddElement(rows())
|
||
rows()\stat = code$
|
||
rows()\file = file$
|
||
rows()\include = 0
|
||
Next i
|
||
|
||
ProcedureReturn ListSize(rows())
|
||
EndProcedure
|
||
|
||
|
||
; Remplit #GListStatus sans #LF$ (évite la perte du 1er caractère en col. 2)
|
||
; Remplit la liste (colonne 0 = état lisible, colonne 1 = chemin)
|
||
; Ajoute " — Exclu" si l’élément a des changements mais n’est pas coché
|
||
Procedure.i FillStatusList(List rows.FileRow())
|
||
ClearGadgetItems(#GListStatus)
|
||
Protected idx.i = 0
|
||
Protected label$, file$
|
||
|
||
ForEach rows()
|
||
label$ = PorcelainToLabel(rows()\stat)
|
||
; Si ce n’est pas un fichier "OK/ignoré" et que include=0, on indique "Exclu"
|
||
If rows()\stat <> "OK" And rows()\stat <> "!!"
|
||
If rows()\include = 0
|
||
label$ + " — Exclu"
|
||
EndIf
|
||
EndIf
|
||
|
||
AddGadgetItem(#GListStatus, -1, label$)
|
||
file$ = rows()\file
|
||
SetGadgetItemText(#GListStatus, idx, file$, 1)
|
||
|
||
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$ = "EX"
|
||
ProcedureReturn "Exclu (permanent)"
|
||
EndIf
|
||
If code$ = "NF"
|
||
ProcedureReturn "Introuvable (FS)"
|
||
EndIf
|
||
If code$ = "OK"
|
||
ProcedureReturn "À jour (suivi)"
|
||
EndIf
|
||
If code$ = "??"
|
||
ProcedureReturn "Nouveau (non suivi)"
|
||
EndIf
|
||
If code$ = "!!"
|
||
ProcedureReturn "Ignoré (.gitignore)"
|
||
EndIf
|
||
|
||
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
|
||
|
||
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.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
|
||
|
||
; Restaure un fichier à l’état d’un commit précis
|
||
; - Essaye 'git restore --source <commit> -- <file>'
|
||
; - Fallback 'git checkout <commit> -- <file>' (pour Git anciens)
|
||
; Restaure un fichier à l’état d’un commit précis
|
||
; - utilise GitPath() pour la robustesse
|
||
; - d’abord 'git restore', fallback 'git checkout'
|
||
Procedure.i RestoreFileFromCommit(repoDir$, file$, commit$)
|
||
Protected gc.GitCall, q$ = Chr(34)
|
||
Protected path$ = GitPath(file$)
|
||
|
||
gc\workdir = repoDir$
|
||
gc\args = "restore --source " + q$ + commit$ + q$ + " -- " + q$ + path$ + q$
|
||
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 (compat versions Git)
|
||
gc\args = "checkout " + q$ + commit$ + q$ + " -- " + q$ + path$ + q$
|
||
If #EnableDebug
|
||
Debug "[RestoreFile fallback] " + gc\args
|
||
EndIf
|
||
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.
|
||
; Ouvre une fenêtre listant les commits du fichier sélectionné,
|
||
; puis restaure le fichier vers le commit choisi.
|
||
; Améliorations :
|
||
; - lit le chemin depuis la 2e colonne du gadget (source de vérité)
|
||
; - normalise le chemin pour Git via GitPath()
|
||
; - utilise --follow et fallback --all pour couvrir les renommages / autres branches
|
||
; Fenêtre de sélection de commit puis restauration d’un fichier
|
||
; Robuste :
|
||
; - lit le nom depuis le gadget ET depuis rows()
|
||
; - corrige le cas où la 1re lettre saute (ex: "PBIDE..." → "BIDE...")
|
||
; - normalise le chemin (GitPath), suit les renommages (--follow) + fallback --all
|
||
; Fenêtre de sélection de commit puis restauration d’un fichier
|
||
; ➜ Affiche maintenant date + heure (format ISO, avec fuseau)
|
||
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érer le nom de fichier depuis la liste (colonne 1 = chemin)
|
||
Protected target$ = GetGadgetItemText(#GListStatus, idx, 1)
|
||
If target$ = ""
|
||
MessageRequester("Restaurer", "Aucun fichier sélectionné.", #PB_MessageRequester_Info)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; Normaliser pour Git (slashs, ./)
|
||
Protected fileArg$ = GitPath(target$)
|
||
Protected gc.GitCall, out$, line$, n.i, i.i, q$ = Chr(34)
|
||
|
||
; 1) Historique avec heure (—date=iso affiche YYYY-MM-DD HH:MM:SS +ZZZZ)
|
||
; Note: si tu préfères l’heure locale stricte (Git récent), tu peux essayer --date=iso-strict-local
|
||
gc\workdir = repoDir$
|
||
gc\args = "log --follow --date=iso --pretty=format:%h%x09%ad%x09%s -n 200 -- " + q$ + fileArg$ + q$
|
||
If #EnableDebug : Debug "[Restore log] " + gc\args : EndIf
|
||
If RunGit(@gc) <> 0
|
||
MessageRequester("Restaurer", "Échec du log : " + #LF$ + TrimNewlines(gc\errors), #PB_MessageRequester_Error)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
out$ = gc\output
|
||
|
||
; 2) Fallback : toutes les refs (utile si l’historique est sur une autre branche)
|
||
If Trim(out$) = ""
|
||
gc\args = "log --all --follow --date=iso --pretty=format:%h%x09%ad%x09%s -n 200 -- " + q$ + fileArg$ + q$
|
||
If #EnableDebug : Debug "[Restore log fallback --all] " + gc\args : EndIf
|
||
If RunGit(@gc) = 0
|
||
out$ = gc\output
|
||
EndIf
|
||
EndIf
|
||
|
||
If Trim(out$) = ""
|
||
MessageRequester("Restaurer", "Aucun commit trouvé pour ce fichier." + #LF$ +
|
||
"Vérifiez que le fichier est (ou a été) suivi par Git et qu’il a déjà été committé.",
|
||
#PB_MessageRequester_Info)
|
||
ProcedureReturn 0
|
||
EndIf
|
||
|
||
; 3) UI : liste des commits avec Date/Heure
|
||
NewList hashes.s()
|
||
If OpenWindow(#WRestore, 0, 0, 780, 440, "Restaurer : " + target$, #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
|
||
TextGadget(#GRestInfo, 10, 10, 760, 22, "Choisissez le commit vers lequel restaurer le fichier.")
|
||
ListIconGadget(#GRestList, 10, 40, 760, 340, "Commit", 110, #PB_ListIcon_FullRowSelect)
|
||
AddGadgetColumn(#GRestList, 1, "Date / Heure", 190) ; élargi pour l’heure et le fuseau
|
||
AddGadgetColumn(#GRestList, 2, "Message", 440)
|
||
ButtonGadget(#GRestOK, 550, 390, 100, 28, "Restaurer")
|
||
ButtonGadget(#GRestCancel, 660, 390, 100, 28, "Annuler")
|
||
|
||
n = CountString(out$, #LF$) + 1
|
||
For i = 1 To n
|
||
line$ = StringField(out$, i, #LF$)
|
||
If Trim(line$) <> ""
|
||
Protected h$ = StringField(line$, 1, #TAB$) ; hash abrégé
|
||
Protected d$ = StringField(line$, 2, #TAB$) ; date + heure (+ZZZZ)
|
||
Protected s$ = StringField(line$, 3, #TAB$) ; message
|
||
AddGadgetItem(#GRestList, -1, h$ + #LF$ + d$ + #LF$ + s$)
|
||
AddElement(hashes()) : hashes() = h$
|
||
If #EnableDebug : Debug "[Restore list] " + h$ + " | " + d$ + " | " + s$ : EndIf
|
||
EndIf
|
||
Next
|
||
|
||
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érer le hash choisi
|
||
Protected k.i = 0, chosen$
|
||
ForEach hashes()
|
||
If k = idx : chosen$ = hashes() : Break : EndIf
|
||
k + 1
|
||
Next
|
||
If chosen$ <> ""
|
||
If RestoreFileFromCommit(repoDir$, fileArg$, 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 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é)
|
||
|
||
|
||
; Construit la liste rows() avec tous les fichiers (selon options), SANS correction de nom
|
||
; - showClean=1 → inclut les fichiers suivis "propres" (OK)
|
||
; - showIgnored=1 → inclut aussi les fichiers ignorés (!!)
|
||
; - Vérifie l’existence côté disque ; si absent et pas un delete, marque "NF" (Introuvable)
|
||
; - rows()\include = 1 pour les éléments potentiellement à committer (≠ OK/!!)
|
||
Procedure.i BuildFullFileList(repoDir$, showClean.i, showIgnored.i, List rows.FileRow())
|
||
Protected gc.GitCall
|
||
Protected text$, line$, code$, file$
|
||
Protected i.i, n.i, start.i, ch$, pos.i, exists.i, isDelete.i
|
||
|
||
ClearList(rows())
|
||
|
||
; Charger exclusions permanentes locales
|
||
NewList excl.s()
|
||
LoadLocalExcludes(repoDir$, excl())
|
||
|
||
; 1) status --porcelain --ignored
|
||
NewMap statusMap.s()
|
||
gc\workdir = repoDir$
|
||
gc\args = "status --porcelain --ignored"
|
||
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$ = "" : Continue : EndIf
|
||
|
||
code$ = Left(line$, 2)
|
||
|
||
start = 3
|
||
While start <= Len(line$)
|
||
ch$ = Mid(line$, start, 1)
|
||
If ch$ <> " " And ch$ <> #TAB$
|
||
Break
|
||
EndIf
|
||
start + 1
|
||
Wend
|
||
file$ = Mid(line$, start)
|
||
pos = FindString(file$, "->", 1)
|
||
If pos > 0 : file$ = Trim(Mid(file$, pos + 2)) : EndIf
|
||
If Left(file$, 2) = "./" : file$ = Mid(file$, 3) : EndIf
|
||
file$ = ReplaceString(file$, "/", #PathSep$)
|
||
|
||
AddMapElement(statusMap(), file$)
|
||
statusMap() = code$
|
||
Next
|
||
|
||
; 2) ls-files → suivis
|
||
NewMap trackedMap.i()
|
||
gc\args = "ls-files"
|
||
If RunGit(@gc) = 0
|
||
text$ = gc\output
|
||
n = CountString(text$, #LF$) + 1
|
||
For i = 1 To n
|
||
file$ = Trim(StringField(text$, i, #LF$))
|
||
If file$ = "" : Continue : EndIf
|
||
If Left(file$, 2) = "./" : file$ = Mid(file$, 3) : EndIf
|
||
file$ = ReplaceString(file$, "/", #PathSep$)
|
||
AddMapElement(trackedMap(), file$) : trackedMap() = 1
|
||
Next
|
||
EndIf
|
||
|
||
; 3) Ajout suivis (propres/modifiés)
|
||
ForEach trackedMap()
|
||
file$ = MapKey(trackedMap())
|
||
If FindMapElement(statusMap(), file$)
|
||
code$ = statusMap()
|
||
Else
|
||
code$ = "OK"
|
||
EndIf
|
||
|
||
If code$ = "OK" And showClean = 0 : Continue : EndIf
|
||
If code$ = "!!" And showIgnored = 0 : Continue : EndIf
|
||
|
||
exists = RepoFileExists(repoDir$, file$)
|
||
isDelete = 0
|
||
If Left(code$, 1) = "D" Or Right(code$, 1) = "D" : isDelete = 1 : EndIf
|
||
|
||
AddElement(rows())
|
||
rows()\file = file$
|
||
|
||
If IsPermanentlyExcluded(file$, excl())
|
||
rows()\stat = "EX"
|
||
rows()\include = 0
|
||
ElseIf exists = 0 And isDelete = 0
|
||
rows()\stat = "NF"
|
||
rows()\include = 0
|
||
Else
|
||
rows()\stat = code$
|
||
rows()\include = Bool(code$ <> "OK" And code$ <> "!!")
|
||
EndIf
|
||
Next
|
||
|
||
; 4) Ajouter non suivis (??/!!) hors trackedMap
|
||
ForEach statusMap()
|
||
file$ = MapKey(statusMap())
|
||
code$ = statusMap()
|
||
If FindMapElement(trackedMap(), file$) = 0
|
||
If code$ = "!!" And showIgnored = 0 : Continue : EndIf
|
||
|
||
exists = RepoFileExists(repoDir$, file$)
|
||
|
||
AddElement(rows())
|
||
rows()\file = file$
|
||
|
||
If IsPermanentlyExcluded(file$, excl())
|
||
rows()\stat = "EX"
|
||
rows()\include = 0
|
||
ElseIf exists = 0 And Left(code$, 1) <> "D" And Right(code$, 1) <> "D"
|
||
rows()\stat = "NF"
|
||
rows()\include = 0
|
||
Else
|
||
rows()\stat = code$
|
||
rows()\include = 1
|
||
EndIf
|
||
EndIf
|
||
Next
|
||
|
||
ProcedureReturn ListSize(rows())
|
||
EndProcedure
|
||
|
||
; Récupère le nom de fichier de la ligne 'index' depuis la liste interne
|
||
Procedure.s FileFromRowsByIndex(index.i, List rows.FileRow())
|
||
Protected j.i = 0
|
||
ForEach rows()
|
||
If j = index
|
||
ProcedureReturn rows()\file
|
||
EndIf
|
||
j + 1
|
||
Next
|
||
ProcedureReturn ""
|
||
EndProcedure
|
||
|
||
; Trie la liste rows() par "intérêt" (cf. ordre ci-dessus) puis par chemin.
|
||
; Utilise des seaux (buckets) puis recolle le tout. Pas de underscore, pas de IIf.
|
||
Procedure.i SortRowsByInterest(List rows.FileRow())
|
||
; Catégorisation locale
|
||
Protected ProcedureReturnValue.i = 0
|
||
Protected cat.i
|
||
Protected x$, y$
|
||
|
||
; Seaux
|
||
NewList b0.FileRow() ; conflits (U)
|
||
NewList b1.FileRow() ; changements indexés (X)
|
||
NewList b2.FileRow() ; changements non indexés (Y)
|
||
NewList b3.FileRow() ; nouveaux (??)
|
||
NewList b4.FileRow() ; OK
|
||
NewList b5.FileRow() ; ignorés (!!)
|
||
NewList b6.FileRow() ; exclus permanents (EX)
|
||
NewList b7.FileRow() ; introuvables (NF)
|
||
|
||
; Helper: pousse un FileRow dans un seau
|
||
Macro PushTo(bucket)
|
||
AddElement(bucket())
|
||
bucket()\file = rows()\file
|
||
bucket()\stat = rows()\stat
|
||
bucket()\include = rows()\include
|
||
EndMacro
|
||
|
||
; 1) Répartition dans les seaux
|
||
ForEach rows()
|
||
cat = -1
|
||
x$ = Left(rows()\stat, 1)
|
||
y$ = Right(rows()\stat, 1)
|
||
|
||
If FindString(rows()\stat, "U", 1) > 0
|
||
cat = 0
|
||
ElseIf rows()\stat = "??"
|
||
cat = 3
|
||
ElseIf rows()\stat = "OK"
|
||
cat = 4
|
||
ElseIf rows()\stat = "!!"
|
||
cat = 5
|
||
ElseIf rows()\stat = "EX"
|
||
cat = 6
|
||
ElseIf rows()\stat = "NF"
|
||
cat = 7
|
||
Else
|
||
; autres codes porcelain XY
|
||
If x$ <> " "
|
||
cat = 1
|
||
ElseIf y$ <> " "
|
||
cat = 2
|
||
Else
|
||
; par sécurité, considère comme non indexé
|
||
cat = 2
|
||
EndIf
|
||
EndIf
|
||
|
||
Select cat
|
||
Case 0 : PushTo(b0)
|
||
Case 1 : PushTo(b1)
|
||
Case 2 : PushTo(b2)
|
||
Case 3 : PushTo(b3)
|
||
Case 4 : PushTo(b4)
|
||
Case 5 : PushTo(b5)
|
||
Case 6 : PushTo(b6)
|
||
Default: PushTo(b7)
|
||
EndSelect
|
||
Next
|
||
|
||
; 2) Tri alphabétique par chemin dans chaque seau
|
||
SortStructuredList(b0(), #PB_Sort_Ascending, OffsetOf(FileRow\file), #PB_String)
|
||
SortStructuredList(b1(), #PB_Sort_Ascending, OffsetOf(FileRow\file), #PB_String)
|
||
SortStructuredList(b2(), #PB_Sort_Ascending, OffsetOf(FileRow\file), #PB_String)
|
||
SortStructuredList(b3(), #PB_Sort_Ascending, OffsetOf(FileRow\file), #PB_String)
|
||
SortStructuredList(b4(), #PB_Sort_Ascending, OffsetOf(FileRow\file), #PB_String)
|
||
SortStructuredList(b5(), #PB_Sort_Ascending, OffsetOf(FileRow\file), #PB_String)
|
||
SortStructuredList(b6(), #PB_Sort_Ascending, OffsetOf(FileRow\file), #PB_String)
|
||
SortStructuredList(b7(), #PB_Sort_Ascending, OffsetOf(FileRow\file), #PB_String)
|
||
|
||
; 3) Recomposition de rows() dans l'ordre des seaux
|
||
ClearList(rows())
|
||
|
||
Macro AppendBucket(bucket2)
|
||
ForEach bucket2()
|
||
AddElement(rows())
|
||
rows()\file = bucket2()\file
|
||
rows()\stat = bucket2()\stat
|
||
rows()\include = bucket2()\include
|
||
ProcedureReturnValue + 1
|
||
Next
|
||
EndMacro
|
||
|
||
AppendBucket(b0)
|
||
AppendBucket(b1)
|
||
AppendBucket(b2)
|
||
AppendBucket(b3)
|
||
AppendBucket(b4)
|
||
AppendBucket(b5)
|
||
AppendBucket(b6)
|
||
AppendBucket(b7)
|
||
|
||
If #EnableDebug
|
||
Debug "[SortRows] total=" + Str(ProcedureReturnValue)
|
||
EndIf
|
||
|
||
ProcedureReturn ProcedureReturnValue
|
||
EndProcedure
|
||
|
||
; ------------------------------------------------------------------
|
||
; Ouvre l'interface principale (Push manuel, guide scrollable,
|
||
; prefs dépôt correctement rechargées & auto-sauvegardées)
|
||
; ------------------------------------------------------------------
|
||
; ------------------------------------------------------------------
|
||
; Ouvre l'interface principale (tri par intérêt avant affichage)
|
||
; ------------------------------------------------------------------
|
||
Procedure.i OpenGUI(initialDir$, prefsPath$)
|
||
Protected repoDir$ = DetectRepoRoot(initialDir$)
|
||
|
||
; Défauts globaux
|
||
Protected defRemote$ = ""
|
||
Protected defBranch$ = ""
|
||
LoadPrefs(prefsPath$, defRemote$, defBranch$)
|
||
|
||
; Prefs du dépôt (via structure)
|
||
Protected rp.RepoPrefs
|
||
rp\remote = defRemote$
|
||
rp\branch = defBranch$
|
||
LoadRepoPrefs(repoDir$, @rp)
|
||
|
||
; Sélection persistante
|
||
Protected keepIdx.i
|
||
Protected keepFile$
|
||
|
||
; Ignorer automatiquement notre prefs locale
|
||
EnsureToolFilesExcluded(repoDir$)
|
||
|
||
If OpenWindow(#GWindow, 0, 0, 920, 720, "PBIDE-GitTool — Git (mode simplifié)", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
|
||
|
||
; --- En-tête dépôt ---
|
||
TextGadget(#GLabelRepo, 10, 12, 60, 22, "Dépôt :")
|
||
StringGadget(#GStringRepo, 80, 10, 740, 24, repoDir$)
|
||
ButtonGadget(#GButtonBrowse, 830, 10, 80, 24, "Parcourir…")
|
||
|
||
; --- Options d'affichage ---
|
||
CheckBoxGadget(#GShowClean, 10, 40, 220, 20, "Afficher suivis à jour")
|
||
SetGadgetState(#GShowClean, #True)
|
||
CheckBoxGadget(#GShowIgnored, 240, 40, 240, 20, "Afficher ignorés (.gitignore)")
|
||
SetGadgetState(#GShowIgnored, #False)
|
||
CheckBoxGadget(#GShowPermanent, 490, 40, 240, 20, "Afficher exclus permanents")
|
||
SetGadgetState(#GShowPermanent, #True)
|
||
|
||
GadgetToolTip(#GShowIgnored, "Montre les fichiers ignorés par règles (.gitignore, exclude global) — statut '!!'.")
|
||
GadgetToolTip(#GShowPermanent, "Montre les fichiers exclus via l’outil (.git/info/exclude) — statut 'EX'.")
|
||
|
||
; --- Liste des fichiers ---
|
||
ListIconGadget(#GListStatus, 10, 66, 900, 320, "État", 240, #PB_ListIcon_CheckBoxes | #PB_ListIcon_FullRowSelect)
|
||
AddGadgetColumn(#GListStatus, 1, "Fichier", 640)
|
||
|
||
; --- Bande d’actions sur la liste ---
|
||
ButtonGadget(#GRefresh, 10, 392, 100, 26, "Rafraîchir")
|
||
ButtonGadget(#GInit, 120, 392, 100, 26, "Init repo")
|
||
ButtonGadget(#GExcludeForever, 230, 392, 150, 26, "Exclure (permanent)")
|
||
ButtonGadget(#GReincludeForever, 390, 392, 170, 26, "Ré-inclure (permanent)")
|
||
ButtonGadget(#GDiff, 570, 392, 90, 26, "Diff…")
|
||
ButtonGadget(#GRestoreFile, 670, 392, 130, 26, "Restaurer…")
|
||
|
||
; --- Zone message / remote / branche ---
|
||
TextGadget(#GLabelMsg, 10, 428, 100, 22, "Message :")
|
||
StringGadget(#GStringMsg, 110, 426, 620, 24, "")
|
||
|
||
TextGadget(#GLabelRemote, 10, 456, 60, 22, "Remote :")
|
||
StringGadget(#GStringRemote, 70, 454, 210, 24, rp\remote)
|
||
|
||
TextGadget(#GLabelBranch, 290, 456, 60, 22, "Branche :")
|
||
ComboBoxGadget(#GComboBranch, 350, 454, 210, 24)
|
||
|
||
ButtonGadget(#GSavePrefs, 570, 454, 90, 24, "Défauts")
|
||
ButtonGadget(#GAddBranch, 670, 454, 110, 24, "Ajouter br.…")
|
||
ButtonGadget(#GReloadBranches, 790, 454, 120, 24, "Actualiser br.")
|
||
|
||
; --- Actions principales ---
|
||
ButtonGadget(#GCommit, 10, 490, 160, 30, "Add + Commit (cochés)")
|
||
ButtonGadget(#GPush, 180, 490, 120, 30, "Push")
|
||
ButtonGadget(#GPull, 310, 490, 120, 30, "Pull")
|
||
ButtonGadget(#GAdvanced, 440, 490, 120, 30, "Avancé…")
|
||
ButtonGadget(#GConfig, 570, 490, 170, 30, "Configurer identité…")
|
||
|
||
GadgetToolTip(#GCommit, "Valide SEULEMENT les lignes cochées.")
|
||
GadgetToolTip(#GPush, "Pousse les commits locaux vers le dépôt distant (manuel).")
|
||
GadgetToolTip(#GRestoreFile, "Restaurer le fichier sélectionné à un commit précis.")
|
||
GadgetToolTip(#GDiff, "Afficher les différences du fichier sélectionné.")
|
||
|
||
; --- Guide (scrollable en lecture seule) ---
|
||
EditorGadget(#GGuide, 10, 530, 900, 170)
|
||
SetGadgetAttribute(#GGuide, #PB_Editor_ReadOnly, 1)
|
||
|
||
; --- Branches locales ---
|
||
NewList branchItems.s()
|
||
ListBranches(repoDir$, branchItems())
|
||
ClearGadgetItems(#GComboBranch)
|
||
ForEach branchItems()
|
||
AddGadgetItem(#GComboBranch, -1, branchItems())
|
||
Next
|
||
If rp\branch <> "" : SetGadgetText(#GComboBranch, rp\branch) : EndIf
|
||
|
||
; --- Fichiers : construction, tri, affichage ---
|
||
NewList rows.FileRow()
|
||
BuildFullFileList(repoDir$, GetGadgetState(#GShowClean), GetGadgetState(#GShowIgnored), rows())
|
||
If GetGadgetState(#GShowPermanent) = 0
|
||
ForEach rows() : If rows()\stat = "EX" : DeleteElement(rows()) : EndIf : Next
|
||
EndIf
|
||
SortRowsByInterest(rows())
|
||
FillStatusList(rows())
|
||
UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote))
|
||
|
||
; =================== Boucle événements ===================
|
||
Repeat
|
||
Protected ev.i = WaitWindowEvent()
|
||
Select ev
|
||
|
||
Case #PB_Event_Gadget
|
||
Select EventGadget()
|
||
|
||
; ---- Changement de dépôt ----
|
||
Case #GButtonBrowse
|
||
Protected newDir$ = PathRequester("Choisir le répertoire du dépôt", repoDir$)
|
||
If newDir$ <> ""
|
||
repoDir$ = newDir$
|
||
SetGadgetText(#GStringRepo, repoDir$)
|
||
EnsureToolFilesExcluded(repoDir$)
|
||
|
||
rp\remote = defRemote$
|
||
rp\branch = defBranch$
|
||
LoadRepoPrefs(repoDir$, @rp)
|
||
SetGadgetText(#GStringRemote, rp\remote)
|
||
|
||
ClearList(branchItems())
|
||
ListBranches(repoDir$, branchItems())
|
||
ClearGadgetItems(#GComboBranch)
|
||
ForEach branchItems() : AddGadgetItem(#GComboBranch, -1, branchItems()) : Next
|
||
If rp\branch <> "" : SetGadgetText(#GComboBranch, rp\branch) : EndIf
|
||
|
||
RememberSel()
|
||
ClearList(rows())
|
||
BuildFullFileList(repoDir$, GetGadgetState(#GShowClean), GetGadgetState(#GShowIgnored), rows())
|
||
If GetGadgetState(#GShowPermanent) = 0
|
||
ForEach rows() : If rows()\stat = "EX" : DeleteElement(rows()) : EndIf : Next
|
||
EndIf
|
||
SortRowsByInterest(rows())
|
||
FillStatusList(rows())
|
||
RestoreSel()
|
||
UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote))
|
||
EndIf
|
||
|
||
; ---- Filtres d'affichage ----
|
||
Case #GShowClean, #GShowIgnored, #GShowPermanent, #GRefresh
|
||
RememberSel()
|
||
ClearList(rows())
|
||
BuildFullFileList(repoDir$, GetGadgetState(#GShowClean), GetGadgetState(#GShowIgnored), rows())
|
||
If GetGadgetState(#GShowPermanent) = 0
|
||
ForEach rows() : If rows()\stat = "EX" : DeleteElement(rows()) : EndIf : Next
|
||
EndIf
|
||
SortRowsByInterest(rows())
|
||
FillStatusList(rows())
|
||
RestoreSel()
|
||
UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote))
|
||
|
||
; ---- Init dépôt ----
|
||
Case #GInit
|
||
DoInitRepo(repoDir$)
|
||
RememberSel()
|
||
ClearList(rows())
|
||
BuildFullFileList(repoDir$, GetGadgetState(#GShowClean), GetGadgetState(#GShowIgnored), rows())
|
||
If GetGadgetState(#GShowPermanent) = 0
|
||
ForEach rows() : If rows()\stat = "EX" : DeleteElement(rows()) : EndIf : Next
|
||
EndIf
|
||
SortRowsByInterest(rows())
|
||
FillStatusList(rows())
|
||
RestoreSel()
|
||
UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote))
|
||
|
||
; ---- Exclusion permanente / ré-inclusion ----
|
||
Case #GExcludeForever
|
||
Protected idx.i = GetGadgetState(#GListStatus)
|
||
If idx >= 0
|
||
Protected target$ = GetGadgetItemText(#GListStatus, idx, 1)
|
||
If MessageRequester("Exclusion permanente", "Exclure définitivement de Git ?" + #LF$ + target$, #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
|
||
If AddPermanentExclude(repoDir$, target$)
|
||
RememberSel()
|
||
ClearList(rows())
|
||
BuildFullFileList(repoDir$, GetGadgetState(#GShowClean), GetGadgetState(#GShowIgnored), rows())
|
||
If GetGadgetState(#GShowPermanent) = 0
|
||
ForEach rows() : If rows()\stat = "EX" : DeleteElement(rows()) : EndIf : Next
|
||
EndIf
|
||
SortRowsByInterest(rows())
|
||
FillStatusList(rows())
|
||
RestoreSel()
|
||
EndIf
|
||
EndIf
|
||
EndIf
|
||
|
||
Case #GReincludeForever
|
||
idx = GetGadgetState(#GListStatus)
|
||
If idx >= 0
|
||
target$ = GetGadgetItemText(#GListStatus, idx, 1)
|
||
If MessageRequester("Ré-inclure (permanent)", "Retirer des exclusions et ré-inclure ?" + #LF$ + target$, #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
|
||
If RemovePermanentExclude(repoDir$, target$)
|
||
RememberSel()
|
||
ClearList(rows())
|
||
BuildFullFileList(repoDir$, GetGadgetState(#GShowClean), GetGadgetState(#GShowIgnored), rows())
|
||
If GetGadgetState(#GShowPermanent) = 0
|
||
ForEach rows() : If rows()\stat = "EX" : DeleteElement(rows()) : EndIf : Next
|
||
EndIf
|
||
SortRowsByInterest(rows())
|
||
FillStatusList(rows())
|
||
RestoreSel()
|
||
EndIf
|
||
EndIf
|
||
EndIf
|
||
|
||
; ---- Diff / Restaurer ----
|
||
Case #GDiff
|
||
OpenDiffWindow(repoDir$, rows())
|
||
|
||
Case #GRestoreFile
|
||
If OpenRestoreFileWindow(repoDir$, rows())
|
||
RememberSel()
|
||
ClearList(rows())
|
||
BuildFullFileList(repoDir$, GetGadgetState(#GShowClean), GetGadgetState(#GShowIgnored), rows())
|
||
If GetGadgetState(#GShowPermanent) = 0
|
||
ForEach rows() : If rows()\stat = "EX" : DeleteElement(rows()) : EndIf : Next
|
||
EndIf
|
||
SortRowsByInterest(rows())
|
||
FillStatusList(rows())
|
||
RestoreSel()
|
||
UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote))
|
||
EndIf
|
||
|
||
; ---- Branches : maj / ajout ----
|
||
Case #GReloadBranches
|
||
ClearList(branchItems())
|
||
ListBranches(repoDir$, branchItems())
|
||
ClearGadgetItems(#GComboBranch)
|
||
ForEach branchItems() : AddGadgetItem(#GComboBranch, -1, branchItems()) : Next
|
||
|
||
Case #GAddBranch
|
||
Protected name$ = InputRequester("Ajouter une branche", "Nom (ex: featureX) ou remote/branche (ex: origin/main)", "")
|
||
If name$ <> ""
|
||
If CreateOrTrackBranch(repoDir$, GetGadgetText(#GStringRemote), name$)
|
||
ClearList(branchItems())
|
||
ListBranches(repoDir$, branchItems())
|
||
ClearGadgetItems(#GComboBranch)
|
||
ForEach branchItems() : AddGadgetItem(#GComboBranch, -1, branchItems()) : Next
|
||
If FindString(name$, "/", 1) = 0
|
||
SetGadgetText(#GComboBranch, name$)
|
||
EndIf
|
||
SaveRepoPrefs(repoDir$, GetGadgetText(#GStringRemote), GetGadgetText(#GComboBranch))
|
||
EndIf
|
||
EndIf
|
||
|
||
; ---- Sauver défauts globaux (optionnel) ----
|
||
Case #GSavePrefs
|
||
defRemote$ = GetGadgetText(#GStringRemote)
|
||
defBranch$ = GetGadgetText(#GComboBranch)
|
||
If SavePrefs(prefsPath$, defRemote$, defBranch$)
|
||
MessageRequester("Préférences", "Valeurs par défaut enregistrées (globales).", #PB_MessageRequester_Info)
|
||
Else
|
||
MessageRequester("Préférences", "Échec d'enregistrement.", #PB_MessageRequester_Error)
|
||
EndIf
|
||
|
||
; ---- Auto-save dépôt : remote et branche quand ils changent ----
|
||
Case #GStringRemote
|
||
If EventType() = #PB_EventType_Change
|
||
SaveRepoPrefs(repoDir$, GetGadgetText(#GStringRemote), GetGadgetText(#GComboBranch))
|
||
EndIf
|
||
|
||
Case #GComboBranch
|
||
If EventType() = #PB_EventType_Change
|
||
SaveRepoPrefs(repoDir$, GetGadgetText(#GStringRemote), GetGadgetText(#GComboBranch))
|
||
EndIf
|
||
|
||
; ---- Commit / Push / Pull ----
|
||
Case #GCommit
|
||
Protected msg$ = GetGadgetText(#GStringMsg)
|
||
If Trim(msg$) = ""
|
||
MessageRequester("Commit", "Merci de saisir un message.", #PB_MessageRequester_Warning)
|
||
Else
|
||
Protected remote$ = GetGadgetText(#GStringRemote)
|
||
Protected branch$ = GetGadgetText(#GComboBranch)
|
||
Protected pushAfter.i = 0 ; push manuel
|
||
NewList files.s()
|
||
CollectIncludedFiles(rows(), files())
|
||
If ListSize(files()) > 0
|
||
DoCommitSelected(repoDir$, msg$, pushAfter, remote$, branch$, files())
|
||
Else
|
||
DoCommit(repoDir$, msg$, pushAfter, remote$, branch$)
|
||
EndIf
|
||
|
||
RememberSel()
|
||
ClearList(rows())
|
||
BuildFullFileList(repoDir$, GetGadgetState(#GShowClean), GetGadgetState(#GShowIgnored), rows())
|
||
If GetGadgetState(#GShowPermanent) = 0
|
||
ForEach rows() : If rows()\stat = "EX" : DeleteElement(rows()) : EndIf : Next
|
||
EndIf
|
||
SortRowsByInterest(rows())
|
||
FillStatusList(rows())
|
||
RestoreSel()
|
||
UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote))
|
||
EndIf
|
||
|
||
Case #GPush
|
||
DoPush(repoDir$, GetGadgetText(#GStringRemote), GetGadgetText(#GComboBranch))
|
||
|
||
Case #GPull
|
||
DoPull(repoDir$, GetGadgetText(#GStringRemote), GetGadgetText(#GComboBranch))
|
||
|
||
; ---- Clic sur la liste (cocher/décocher = inclure/exclure) ----
|
||
Case #GListStatus
|
||
Protected idx2.i = GetGadgetState(#GListStatus)
|
||
If idx2 >= 0
|
||
RememberSel()
|
||
ToggleIncludeAt(idx2, rows())
|
||
SortRowsByInterest(rows())
|
||
FillStatusList(rows()) ; garde la sélection par la suite
|
||
RestoreSel()
|
||
EndIf
|
||
|
||
EndSelect
|
||
|
||
Case #PB_Event_CloseWindow
|
||
SaveRepoPrefs(repoDir$, GetGadgetText(#GStringRemote), GetGadgetText(#GComboBranch))
|
||
|
||
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 = 1835
|
||
; FirstLine = 1824
|
||
; Folding = -----------
|
||
; EnableXP
|
||
; DPIAware
|
||
; Executable = ..\PBIDE-GitTool.exe
|
||
; CompileSourceDirectory |