diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c85d443 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# PureBasic / build artefacts +*.exe +*.dll +*.so +*.dylib +*.o +*.obj +*.pdb +*.res +*.a +*.lib +*.d +*.map +*.dbg +*.log +*.temp +*.bak +*.cache + +# IDE / OS +.DS_Store +Thumbs.db +.idea +.vscode diff --git a/PBIDE-GitTool.pb b/PBIDE-GitTool.pb index f8eaeb2..8f74021 100644 --- a/PBIDE-GitTool.pb +++ b/PBIDE-GitTool.pb @@ -31,7 +31,6 @@ CompilerEndIf #GRefresh = 14 #GLabelMsg = 15 #GStringMsg = 16 -#GCheckPush = 17 #GLabelRemote = 18 #GStringRemote = 19 #GLabelBranch = 20 @@ -50,6 +49,12 @@ CompilerEndIf #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 @@ -76,7 +81,6 @@ CompilerEndIf ; --- Nouveaux gadgets --- #GDiff = 32 -#GMakeIgnore = 33 #GConfig = 34 #GGuide = 35 @@ -101,6 +105,18 @@ CompilerEndIf #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 @@ -117,12 +133,21 @@ Structure FileRow 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() @@ -158,7 +183,14 @@ 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.s PorcelainToLabel(code$) ; (remplacer par la version complète ci-dessous) +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$) + ; ====== Utils ====== ; Convertit le chemin pour Git (Windows ok) : @@ -236,6 +268,18 @@ Procedure.i IsUrlRemote(remote$) 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 @@ -395,6 +439,301 @@ Procedure.i SavePrefs(prefsPath$, remote$, branch$) 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" @@ -717,33 +1056,32 @@ 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() - ; 1) Texte de la 1re colonne (état lisible) 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$) - - ; 2) Texte de la 2e colonne (chemin) - file$ = rows()\file SetGadgetItemText(#GListStatus, idx, file$, 1) - ; 3) Case à cocher selon include If rows()\include SetGadgetItemState(#GListStatus, idx, #PB_ListIcon_Checked) Else SetGadgetItemState(#GListStatus, idx, 0) EndIf - ; 4) Debug (utile pour vérifier ce qui est réellement affiché) - If #EnableDebug - Debug "[Fill] idx=" + Str(idx) + " file='" + file$ + "' readback='" + GetGadgetItemText(#GListStatus, idx, 1) + "'" - EndIf - idx + 1 Next @@ -752,6 +1090,7 @@ EndProcedure + Procedure.i ToggleIncludeAt(index.i, List rows.FileRow()) If index < 0 : ProcedureReturn 0 : EndIf Protected c.i = CountGadgetItems(#GListStatus) @@ -904,26 +1243,28 @@ 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 - ; 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 @@ -1307,7 +1648,11 @@ Procedure.i BuildFullFileList(repoDir$, showClean.i, showIgnored.i, List rows.Fi ClearList(rows()) - ; --- 1) status --porcelain --ignored → map fichier -> code (??, !!, " M", etc.) + ; 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" @@ -1324,7 +1669,6 @@ Procedure.i BuildFullFileList(repoDir$, showClean.i, showIgnored.i, List rows.Fi code$ = Left(line$, 2) - ; --- parsing robuste du chemin (tout ce qui suit les espaces/tabs après col.2) --- start = 3 While start <= Len(line$) ch$ = Mid(line$, start, 1) @@ -1334,19 +1678,16 @@ Procedure.i BuildFullFileList(repoDir$, showClean.i, showIgnored.i, List rows.Fi 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 - ; normalisation simple If Left(file$, 2) = "./" : file$ = Mid(file$, 3) : EndIf file$ = ReplaceString(file$, "/", #PathSep$) - ; ------------------------------------------------------------------------------- AddMapElement(statusMap(), file$) statusMap() = code$ Next - ; --- 2) ls-files → tous les suivis + ; 2) ls-files → suivis NewMap trackedMap.i() gc\args = "ls-files" If RunGit(@gc) = 0 @@ -1361,7 +1702,7 @@ Procedure.i BuildFullFileList(repoDir$, showClean.i, showIgnored.i, List rows.Fi Next EndIf - ; --- 3) Ajouter les suivis (propres/modifiés) + ; 3) Ajout suivis (propres/modifiés) ForEach trackedMap() file$ = MapKey(trackedMap()) If FindMapElement(statusMap(), file$) @@ -1379,8 +1720,12 @@ Procedure.i BuildFullFileList(repoDir$, showClean.i, showIgnored.i, List rows.Fi AddElement(rows()) rows()\file = file$ - If exists = 0 And isDelete = 0 - rows()\stat = "NF" ; Introuvable (FS) + + 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$ @@ -1388,7 +1733,7 @@ Procedure.i BuildFullFileList(repoDir$, showClean.i, showIgnored.i, List rows.Fi EndIf Next - ; --- 4) Ajouter non suivis (!!/??) qui ne sont pas dans trackedMap + ; 4) Ajouter non suivis (??/!!) hors trackedMap ForEach statusMap() file$ = MapKey(statusMap()) code$ = statusMap() @@ -1399,7 +1744,11 @@ Procedure.i BuildFullFileList(repoDir$, showClean.i, showIgnored.i, List rows.Fi AddElement(rows()) rows()\file = file$ - If exists = 0 And Left(code$, 1) <> "D" And Right(code$, 1) <> "D" + + 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 @@ -1430,232 +1779,347 @@ EndProcedure ; - corrige le cas où la 1re lettre saute (ex: "PBIDE..." → "BIDE...") ; - normalise le chemin (GitPath), suit les renommages (--follow) + fallback --all +; ------------------------------------------------------------------ +; Ouvre l'interface principale (avec auto-save remote/branch par dépôt) +; ------------------------------------------------------------------ +; ------------------------------------------------------------------ +; Ouvre l'interface principale (boutons Inclure/Exclure retirés, +; bouton "Restaurer…" présent, auto-save remote/branch par dépôt) +; ------------------------------------------------------------------ +; ------------------------------------------------------------------ +; Ouvre l'interface principale (Push manuel, pas de bouton .gitignore, +; guide scrollable en lecture seule, options d’affichage clarifiées) +; ------------------------------------------------------------------ +; ------------------------------------------------------------------ +; Ouvre l'interface principale (Push manuel, pas de bouton .gitignore, +; guide scrollable en lecture seule, options d’affichage clarifiées) +; ------------------------------------------------------------------ +; ------------------------------------------------------------------ +; Ouvre l'interface principale (Push manuel, guide scrollable, +; prefs dépôt correctement rechargées & auto-sauvegardées) +; ------------------------------------------------------------------ Procedure.i OpenGUI(initialDir$, prefsPath$) Protected repoDir$ = DetectRepoRoot(initialDir$) - Protected remote$ = "", branch$ = "" - LoadPrefs(prefsPath$, remote$, branch$) - If OpenWindow(#GWindow, 0, 0, 900, 660, "PBIDE-GitTool — Git (mode simplifié)", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) + ; Défauts globaux + Protected defRemote$ = "" + Protected defBranch$ = "" + LoadPrefs(prefsPath$, defRemote$, defBranch$) + + ; Prefs du dépôt (via structure pour bien remonter les valeurs) + Protected rp.RepoPrefs + rp\remote = defRemote$ + rp\branch = defBranch$ + LoadRepoPrefs(repoDir$, @rp) + + ; Sélection persistante pour RememberSel/RestoreSel + 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, 720, 24, repoDir$) - ButtonGadget(#GButtonBrowse, 810, 10, 80, 24, "Parcourir…") + StringGadget(#GStringRepo, 80, 10, 740, 24, repoDir$) + ButtonGadget(#GButtonBrowse, 830, 10, 80, 24, "Parcourir…") - ; Options d’affichage - CheckBoxGadget(#GShowClean, 10, 40, 180, 20, "Afficher suivis à jour") - SetGadgetState(#GShowClean, #True) ; par défaut : on veut tout voir - CheckBoxGadget(#GShowIgnored, 200, 40, 200, 20, "Inclure ignorés (.gitignore)") + ; --- 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) - ; Liste avec cases à cocher - ListIconGadget(#GListStatus, 10, 66, 880, 300, "État", 180, #PB_ListIcon_CheckBoxes | #PB_ListIcon_FullRowSelect) - AddGadgetColumn(#GListStatus, 1, "Fichier", 680) + 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'.") - ; Ligne actions liste - ButtonGadget(#GRefresh, 10, 372, 90, 26, "Rafraîchir") - ButtonGadget(#GInit, 110, 372, 90, 26, "Init repo") - ButtonGadget(#GInclude, 210, 372, 90, 26, "Inclure") - ButtonGadget(#GExclude, 310, 372, 90, 26, "Exclure") - ButtonGadget(#GIncludeAll, 410, 372, 110, 26, "Tout inclure") - ButtonGadget(#GExcludeAll, 530, 372, 110, 26, "Tout exclure") - ButtonGadget(#GDiff, 650, 372, 80, 26, "Diff…") - ButtonGadget(#GRestoreFile,740, 372, 150, 26, "Restaurer fichier…") + ; --- Liste des fichiers --- + ListIconGadget(#GListStatus, 10, 66, 900, 320, "État", 240, #PB_ListIcon_CheckBoxes | #PB_ListIcon_FullRowSelect) + AddGadgetColumn(#GListStatus, 1, "Fichier", 640) - ; Zone commit / push - TextGadget(#GLabelMsg, 10, 408, 100, 22, "Message :") - StringGadget(#GStringMsg, 110, 406, 620, 24, "") - CheckBoxGadget(#GCheckPush, 10, 438, 140, 22, "Pousser après") - SetGadgetState(#GCheckPush, #True) + ; --- 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…") - TextGadget(#GLabelRemote, 160, 438, 60, 22, "Remote :") - StringGadget(#GStringRemote, 220, 436, 120, 24, remote$) - TextGadget(#GLabelBranch, 350, 438, 60, 22, "Branche :") - ComboBoxGadget(#GComboBranch, 410, 436, 170, 24) - ButtonGadget(#GSavePrefs, 590, 436, 220, 24, "Sauver défauts") - ButtonGadget(#GAdvanced, 820, 436, 70, 24, "Avancé…") + ; --- Zone message / remote / branche --- + TextGadget(#GLabelMsg, 10, 428, 100, 22, "Message :") + StringGadget(#GStringMsg, 110, 426, 620, 24, "") - ButtonGadget(#GCommit, 10, 470, 120, 30, "Add + Commit") - ButtonGadget(#GPush, 140, 470, 120, 30, "Push") - ButtonGadget(#GPull, 270, 470, 120, 30, "Pull") - ButtonGadget(#GConfig, 400, 470, 170, 30, "Configurer identité…") - ButtonGadget(#GMakeIgnore, 580, 470, 230, 30, "Créer .gitignore (recommandé)") + TextGadget(#GLabelRemote, 10, 456, 60, 22, "Remote :") + StringGadget(#GStringRemote, 70, 454, 210, 24, rp\remote) - ; Panneau Guide - EditorGadget(#GGuide, 10, 510, 880, 140) - DisableGadget(#GGuide, 1) + TextGadget(#GLabelBranch, 290, 456, 60, 22, "Branche :") + ComboBoxGadget(#GComboBranch, 350, 454, 210, 24) - ; Infobulles clés - GadgetToolTip(#GShowClean, "Afficher aussi les fichiers déjà suivis et sans changement (pratique pour restaurer).") - GadgetToolTip(#GShowIgnored, "Inclure les fichiers ignorés par .gitignore (lecture seule pour info).") - GadgetToolTip(#GListStatus, "Cochez pour inclure un fichier au commit. 'Diff…' pour voir le détail. 'Restaurer fichier…' pour revenir à un commit.") + ButtonGadget(#GSavePrefs, 570, 454, 90, 24, "Défauts") + ButtonGadget(#GAddBranch, 670, 454, 110, 24, "Ajouter br.…") + ButtonGadget(#GReloadBranches, 790, 454, 120, 24, "Actualiser br.") - ; Branches + ; --- 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 branch$ <> "" : SetGadgetText(#GComboBranch, branch$) : EndIf + ForEach branchItems() + AddGadgetItem(#GComboBranch, -1, branchItems()) + Next + If rp\branch <> "" : SetGadgetText(#GComboBranch, rp\branch) : EndIf - ; Fichiers (liste complète) + ; --- Fichiers : construction & 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 FillStatusList(rows()) UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) - ; Boucle + ; =================== 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$) + + ; recharger prefs dépôt (rp repart des défauts globaux si rien) + rp\remote = GetGadgetText(#GStringRemote) + rp\branch = GetGadgetText(#GComboBranch) + 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 FillStatusList(rows()) + RestoreSel() UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) EndIf - Case #GShowClean, #GShowIgnored - ; Refiltrer l’affichage à la volée - ClearList(rows()) - BuildFullFileList(repoDir$, GetGadgetState(#GShowClean), GetGadgetState(#GShowIgnored), rows()) - FillStatusList(rows()) - UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) - - Case #GRefresh + ; ---- 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 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 FillStatusList(rows()) + RestoreSel() UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) - Case #GInclude + ; ---- Exclusion permanente / ré-inclusion ---- + Case #GExcludeForever Protected idx.i = GetGadgetState(#GListStatus) If idx >= 0 - SetGadgetItemState(#GListStatus, idx, #PB_ListIcon_Checked) - ToggleIncludeAt(idx, rows()) + 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 + FillStatusList(rows()) + RestoreSel() + EndIf + EndIf EndIf - UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) - Case #GExclude + Case #GReincludeForever idx = GetGadgetState(#GListStatus) If idx >= 0 - SetGadgetItemState(#GListStatus, idx, 0) - ToggleIncludeAt(idx, rows()) + 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 + FillStatusList(rows()) + RestoreSel() + EndIf + EndIf 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)) + ; ---- 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 FillStatusList(rows()) + RestoreSel() UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) EndIf - Case #GAdvanced - OpenAdvancedWindow(repoDir$) - ClearList(rows()) - BuildFullFileList(repoDir$, GetGadgetState(#GShowClean), GetGadgetState(#GShowIgnored), rows()) - FillStatusList(rows()) - UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) + ; ---- 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 + ; auto-save dépôt après création/suivi + SaveRepoPrefs(repoDir$, GetGadgetText(#GStringRemote), GetGadgetText(#GComboBranch)) + EndIf + EndIf + + ; ---- Sauver défauts globaux (optionnel) ---- 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) + 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 - UpdateGuide(repoDir$, rows(), branch$, remote$) - Case #GConfig - If ConfigIdentityWizard(repoDir$) - UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) + ; ---- 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 #GMakeIgnore - If MakeDefaultGitignore(repoDir$) - ClearList(rows()) - BuildFullFileList(repoDir$, GetGadgetState(#GShowClean), GetGadgetState(#GShowIgnored), rows()) - FillStatusList(rows()) + Case #GComboBranch + If EventType() = #PB_EventType_Change + SaveRepoPrefs(repoDir$, GetGadgetText(#GStringRemote), GetGadgetText(#GComboBranch)) EndIf + ; ---- Commit / Push / Pull ---- Case #GCommit - remote$ = GetGadgetText(#GStringRemote) - branch$ = GetGadgetText(#GComboBranch) 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$, GetGadgetState(#GCheckPush), remote$, branch$, files()) + DoCommitSelected(repoDir$, msg$, pushAfter, remote$, branch$, files()) Else - DoCommit(repoDir$, msg$, GetGadgetState(#GCheckPush), remote$, branch$) + 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 FillStatusList(rows()) + RestoreSel() UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) EndIf Case #GPush - remote$ = GetGadgetText(#GStringRemote) - branch$ = GetGadgetText(#GComboBranch) - DoPush(repoDir$, remote$, branch$) + DoPush(repoDir$, GetGadgetText(#GStringRemote), GetGadgetText(#GComboBranch)) Case #GPull - remote$ = GetGadgetText(#GStringRemote) - branch$ = GetGadgetText(#GComboBranch) - DoPull(repoDir$, remote$, branch$) + DoPull(repoDir$, GetGadgetText(#GStringRemote), GetGadgetText(#GComboBranch)) + ; ---- Clic sur la liste (cocher/décocher = inclure/exclure) ---- Case #GListStatus - idx = GetGadgetState(#GListStatus) - If idx >= 0 : ToggleIncludeAt(idx, rows()) : EndIf - UpdateGuide(repoDir$, rows(), GetGadgetText(#GComboBranch), GetGadgetText(#GStringRemote)) + Protected idx2.i = GetGadgetState(#GListStatus) + If idx2 >= 0 + RememberSel() + ToggleIncludeAt(idx2, rows()) + FillStatusList(rows()) + RestoreSel() + EndIf EndSelect + + Case #PB_Event_CloseWindow + ; Dernière sauvegarde des prefs dépôt à la fermeture + 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 @@ -1855,9 +2319,9 @@ Else EndIf ; IDE Options = PureBasic 6.21 (Windows - x64) -; CursorPosition = 1413 -; FirstLine = 1375 -; Folding = -------- +; CursorPosition = 2121 +; FirstLine = 2083 +; Folding = ---------- ; EnableXP ; DPIAware ; Executable = ..\PBIDE-GitTool.exe diff --git a/PureBasic_Compilation0.exe b/PureBasic_Compilation0.exe deleted file mode 100644 index 4e6842c..0000000 Binary files a/PureBasic_Compilation0.exe and /dev/null differ diff --git a/PureBasic_Compilation1.exe b/PureBasic_Compilation1.exe deleted file mode 100644 index 009eb6f..0000000 Binary files a/PureBasic_Compilation1.exe and /dev/null differ diff --git a/git_icons_pack.zip b/git_icons_pack.zip deleted file mode 100644 index 62a692f..0000000 Binary files a/git_icons_pack.zip and /dev/null differ